Alpha-Beta算法的剪枝原理
目录
- 01 Alpha-Beta算法的产生
- 02 Alpha-Beta算法的剪枝原理
- 03 Alpha-Beta算法的剪枝实现
- 04 Alpha-Beta算法的二次优化
内容
01 Alpha-Beta算法的产生
Alpha-Beta算法的原生是极大极小算法。在极大极小算法中,即使我们已经稍微优化了一下代码,使得极大极小算法进化成负值最大算法。但是仔细回想,负值最大算法并没有对算法的实质产生任何优化,只是对于代码设计的优化。同时也在谈论深度的时候说明了,其实极大极小算法实际上是以树为抽象结构进行的。仔细研究发现,我们可以通过树的Alpha-Beta剪枝来优化算法。
我们可以尝试继续模拟一下极大极小的例子:若深度为2,我方白棋,面对黑方已经下过的局面,寻找最佳落子点。
02 Alpha-Beta算法的剪枝原理
我以树为递归遍历的抽象数据类型,介绍遍历的过程:
- 模拟我(白)方下棋;
- 深度为2,递归程序;
- 模拟黑方下棋;
- 深度为1,递归程序;
- 模拟白方下棋;
- 深度为0,评估;
- 返回评估最大;
- 返回评估最小;
- ……
为了避免读者看图混淆,这里提醒一下:
接下来将举例极大极小算法的明显冗余现象,同时笔者将在图中注释,方便阅读。
极大值冗余
极小值冗余
03 Alpha-Beta算法的剪枝实现
我们已经知道,在极大极小算法中会存在最大最小值冗余,则Alpha-Beta剪枝的目的就在于去除掉那些徒劳的工作。我们可以得知,发生徒劳的具体的条件是:
不是迭代的头遍历,由上面两个例子来看,必须在该迭代之前就已经出现一个的值。那个值将决定之后的遍历是否舍弃。(极大值冗余中的白棋值6,极小值冗余的黑棋值4)为了方便书写,笔者暂时将这种值称为已知最大(小)值。
需要一个大小判断:
a. 若对于当前遍历是黑棋:那么判断 上层白棋中的已知最大值 是否大于等于 当前(黑棋)最小值。若成立则结束当前遍历,若不成立则继续遍历。
b. 若对于当前遍历是白棋:那么判断 上层黑棋中的已知最小值 是否小于等于 当前(白棋)最大值。若成立则结束当前遍历,若不成立则继续遍历。
冗余的情况仅发生在相邻两层的子递归中。
那么如何实现Alpha-Beta剪枝的呢?我们可以稍微推理一下:
传递一个已知大值和已知最小值时,只能通过递归传值。
其实对于上面条件的1,迭代的头遍历,我们可以将已知最大值设置为系统最小值,将已知最小值设置为系统最大值,这样就不需要将头遍历设置为一个额外的情况。然后在头遍历的时候将已知最大(小)值代替即可。
整个过程时基于极大极小算法的雏形推理的。
那么我们可以写出分离的Max和Min函数:
int MinMax(int depth, int KnownMax, int KnownMin) {
if (SideToMove() == Myself) return Max(depth, KnownMin);
else return Min(depth, KnownMax);
}
int Max(int depth, int KnownMin) {
int best = -INFINITY;
if (depth <= 0) return Evaluate();
GenerateLegalMoves();
while (hasLegalNext()) {
MakeNextMove();
val = Min(depth - 1, best);//将 当前最大值 传递成为 子递归的已知最大值
UnmakeMove();
if (val > best) best = val;//得到当前最大值
if (best >= KnownMin) return KnownMin;//与已知最大值比较,若已知最小值 小于等于 当前最大值,则返回已知最小值。
}
return best;
}
int Min(int depth, int KnownMax) {
int best = INFINITY;
if (depth <= 0) return Evaluate();
GenerateLegalMoves();
while (hasLegalNext()) {
MakeNextMove();
val = Max(depth - 1, best);//将 当前最小值 传递成为 子递归的已知最小值
UnmakeMove();
if (val < best) best = val;//得到当前最小值
if (best <= KnownMax) return KnownMax;//与已知最大值比较,若已知最大值 大于等于 当前最小值,则返回已知最大值
}
return best;
}
04 Alpha-Beta算法的二次优化
对于上面的结果,Alpha-Beta最终算法进行二次优化,二次优化有两个地方:
- 对于一些细节优化;
- 对于代码的设计优化;
细节优化
因为只是代码的细节优化,Max和Min都几乎相同,我这里以Max举例:
单独提出可以优化的代码块:
val = Min(...); if (val > best) best = val; if (best >= KnownMin) return KnownMin;
那么我们可以知道,若递归能够进行,那么best一定是小于KnownMin的,而val替代best的方法则是若大于best,那么其实类似于:
-------best-------val--------KnownMin-------->x轴//若能够继续递归,best、val、KnownMin的大小情况
那么代码可以优化成:
val = Min(...); if (val >= KnownMin) return KnownMin; if (val > best) best = val;
这样优化减少了val和best的逻辑判断,浅浅优化了时间。在任何的算法中,我们要秉持能优化的地方绝对不放弃的态度。
那么优化后的Max代码为:
int Max(int depth, int KnownMin) {
int best = -INFINITY;
if (depth <= 0) return Evaluate();
GenerateLegalMoves();
while (hasLegalNext()) {
MakeNextMove();
val = Min(depth - 1, best);//将 当前最大值 传递成为 子递归的已知最大值
UnmakeMove();
if (val >= KnownMin) return KnownMin;//当 当前值 大于等于 已知最小值,则返回已知最小值
if (val > best) best = val;//
}
return best;
}
接下的细节优化需要图示举例:
那么有价值的低位白棋的情况为:
那么可得,若当前的白棋有价值,则:
------KnownMax------val--------KnownMin---------->
- 1
那么我们需要在递归的时候将已知最大值传递下去,则算法变为:
int MinMax(int depth, int KnownMax, int KnownMin) {
if (SideToMove() == Myself) return Max(depth, KnownMax, KnownMin);
else return Min(depth, KnownMax, KnowMin);
}
int Max(int depth, int KnownMax, int KnownMin) {
if (depth <= 0) return Evaluate();
GenerateLegalMoves();
while (hasLegalNext()) {
MakeNextMove()
val = Min(depth - 1, best, KnownMin);//将 当前最大值 传递成为 子递归的已知最大值
UnmakeMove();
if (val >= KnownMin) return KnownMin;//当 当前值 大于等于 已知最小值,则返回已知最小值
if (val > KnownMax) KnowMax = val;
}
return best;
}
int Min(int depth, int KnownMax, int KnownMin) {
if (depth <= 0) return Evaluate();
GenerateLegalMoves();
while (hasLegalNext()) {
MakeNextMove();
val = Max(depth - 1, KnownMax, best);//将 当前最小值 传递成为 子递归的已知最小值
UnmakeMove();
if (val <= KnownMax) return KnownMax;//当 当前值 大于等于 已知最小值,则返回已知最小值
if (val < KnowMin) KnownMin = val;
}
return best;
}
最终算法实现了剪枝、减少变量、去掉额外的运算。
设计优化
在极大极小值算法中提到,我们可以将最终的算法变为负值极大值算法。同理,我们可以将上述的Alpha-Beta进化成为结构更加简单的算法。原理几乎相同,相反数交替双方的角色即可。同时将高层已知最大值(knownMax)变成Alpha,上层已知最小值(knowMin)变成Beta。下面是逻辑代码:
int AlphaBeta(int depth, int alpha, int beta) {
if (depth == 0) return Evaluate();
GenerateLegalMoves();
while (hasLegalNext()) {
MakeNextMove();
val = -AlphaBeta(depth - 1, -beta, -alpha);
UnmakeMove();
if (val >= beta) return beta;//第一次优化效果
if (val > alpha) alpha = val;//第二次优化效果,alpha经过两层反转成为原来的值。
}
return alpha;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2023-02-05 硬盘信息查看