在上一篇咱们分析了对抗搜索的基本思想,本文在上一篇的基础上对算法部分步骤进行优化。首先咱们看一个基于对抗搜索创建的博弈树,对上篇的内容作一个简单的回顾。(本篇内容可使用上篇开头说的两本书做为参考)java
观察这颗博弈树,咱们可知它的固定深度是3,MAX先执行。换句话来讲就是,MAX为了决定第一步该走A、B仍是C,它要从根节点开始遍历深度为3的子树。再换句话来讲,这颗树其实只是为了得到MAX在第一步走的时候创建的博弈树;一样对于MIN方来讲,假如MAX第一步选择走A,那么以A节点为根节点,一样创建一颗深度为3的子树,经过遍历该子树,得到MIN在这一步的最佳选择,A一、A2或者A3(未画出该图)。算法
通俗的说,对抗搜索其实就是在有限深度内枚举双发在每一步的选择,经过比较最终节点的状态(最大深度时)优劣,来得到此时执行方的最优选择。函数
回归到原图,对MAX的选择进行一个详细的分析(请结合上一篇的MAX_VALUE和MIN_VALUE代码):优化
接下来咱们思考一个问题,咱们能够看到上述的算法是对全部的候选项都进行枚举遍历比较,这样若是深度比较大时,好比固定截取深度为10,那么对每一步来讲都要计算最大深度为10的子树,这样的计算量会很是大。有没有可能缩小比较范围?spa
首先咱们要清楚,max_value和min_value方法以DFS(深度优先)进行遍历。接下来咱们对以A节点为根节点的子树进行详细分析。首先从深度3开始往上回溯时,此时A1的最优选择为6;此时再往上回溯,A节点此时的最优选择为6;而后开始遍历A2节点,由于A节点此时的值是6,若是A2节点可以获得该值的信息的话,那么在以A2节点为根的深度遍历时,遇到比6大的值,其实能够放弃该子树的后续遍历了。缘由就是A2节点是MAX方来执行,若是A2的子节点有比6大的,那么MAX方确定会选择比6大的值,而A节点是MIN方来执行,它要选择的是A一、A二、A3的最小值,因此此时A节点确定不会选择A2做为最优选择。code
一样,在A的选择结束后,向上回溯到R,此时R的最优选择是6,因为R要选取最大值,那么在对剩余以B和C为根节点进行深度遍历时,若是有遇到比6小的值,那么该节点确定不是最优的选择。缘由就是MAX方要选择后继节点中的最大值,比6小的话,确定不是最优选择。blog
上述方法就是树结构最经常使用的优化方法--剪枝,这里的优化方法叫作Alpha-Beta方法。下面是经过Alpha-Beta剪枝方法对max_value方法和min_value方法的改进。递归
int max_value ( int dep , state s , int alpha , int beta ){ if ( terminal ( s )) return e ( s ); //终止状态 if ( dep == maxdepth ) return e ( s ); //深度截断,返回评价函数 v = - inf ; //初始化为负无穷 succ = make_successors ( s ); // succ [ i为第]个后继状态i for ( i = 0; i < succ . count ; i ++){ v = max (v , min_value ( succ [ i ] , alpha , beta )); //计算全部儿子的最大值 if ( v >= beta ) return v ; //β剪枝 alpha = max ( alpha , v ); //更新α为最大值 } return v ; }
int min_value ( int dep , state s, int alpha, int beta){ if ( terminal ( s )) return e ( s ); //终止状态 if ( dep == maxdepth ) return e ( s ); //深度截断,返回评价函数 v = inf ; //初始化为无穷大 succ = make_successors ( s ); // succ [ i为第]个后继状态i for ( i = 0; i < succ . count ; i ++){ v = min (v , max_value ( succ [ i ] , alpha , beta )); //计算全部儿子的最小值 if ( v <= alpha ) return v ; //α剪枝 beta = min ( beta , v ); //更新β为最小值 } return v ; }
接下来咱们按照改进后的max_value方法和min_value方法对上述的博弈树进行剪枝,方便你们理解。terminal
最终的裁剪图以下,标红的表明它的子树都被裁剪了。class
alpha表明的是MAX方选择的最小下界;
beta表明的是MIN方选择的最大上界;