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算法的剪枝原理

​我以树为递归遍历的抽象数据类型,介绍遍历的过程:

  1. 模拟我(白)方下棋;
  2. 深度为2,递归程序;
  3. 模拟黑方下棋;
  4. 深度为1,递归程序;
  5. 模拟白方下棋;
  6. 深度为0,评估;
  7. 返回评估最大;
  8. 返回评估最小;
  9. ……

为了避免读者看图混淆,这里提醒一下:

​接下来将举例极大极小算法的明显冗余现象,同时笔者将在图中注释,方便阅读。

极大值冗余

极小值冗余

在这里插入图片描述

03 Alpha-Beta算法的剪枝实现

​我们已经知道,在极大极小算法中会存在最大最小值冗余,则Alpha-Beta剪枝的目的就在于去除掉那些徒劳的工作。我们可以得知,发生徒劳的具体的条件是:

  1. 不是迭代的头遍历,由上面两个例子来看,必须在该迭代之前就已经出现一个的值。那个值将决定之后的遍历是否舍弃。(极大值冗余中的白棋值6,极小值冗余的黑棋值4)为了方便书写,笔者暂时将这种值称为已知最大(小)值。

  2. 需要一个大小判断:

    a. 若对于当前遍历是黑棋:那么判断 上层白棋中的已知最大值 是否大于等于 当前(黑棋)最小值。若成立则结束当前遍历,若不成立则继续遍历。

    b. 若对于当前遍历是白棋:那么判断 上层黑棋中的已知最小值 是否小于等于 当前(白棋)最大值。若成立则结束当前遍历,若不成立则继续遍历。

  3. 冗余的情况仅发生在相邻两层的子递归中。

​那么如何实现Alpha-Beta剪枝的呢?我们可以稍微推理一下:

  1. 传递一个已知大值和已知最小值时,只能通过递归传值。

  2. 其实对于上面条件的1,迭代的头遍历,我们可以将已知最大值设置为系统最小值,将已知最小值设置为系统最大值,这样就不需要将头遍历设置为一个额外的情况。然后在头遍历的时候将已知最大(小)值代替即可。

  3. 整个过程时基于极大极小算法的雏形推理的。

​那么我们可以写出分离的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最终算法进行二次优化,二次优化有两个地方:

  1. 对于一些细节优化;
  2. 对于代码的设计优化;
细节优化

​因为只是代码的细节优化,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;
}
  

posted on   zxddesk  阅读(30)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2023-02-05 硬盘信息查看

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示