获取主要变例

来源:互联网 发布:北京凶宅数据库 编辑:程序博客网 时间:2024/05/22 11:43

要点
 
  经常有人问,如何在搜索完成后提取主要变例。主要变例是程序认为的对双方来说都是最好的着法线路。它不会由未修改的“Alpha-Beta函数”来获得,所有的Alpha-Beta都只返回数值。
  我们需要做的是对普通的Alpha-Beta搜索作修改,使得它能获取主要变例。修改的部分用醒目的颜色标出:
 

typedef struct tagLINE { int cmove;       // 路线中着法的数量; MOVE argmove[moveMAX]; // 路线。} LINE; int AlphaBeta(int depth, int alpha, int beta, LINE *pline) { LINE line; if (depth == 0) {  pline->cmove = 0;  return Evaluate(); } GenerateLegalMoves(); while (MovesLeft()) {  MakeNextMove();  val = -AlphaBeta(depth - 1, -beta, -alpha, &line);  UnmakeMove();  if (val >= beta) {   return beta;  }  if (val > alpha) {   alpha = val;   pline->argmove[0] = ThisMove();   memcpy(pline->argmove + 1, line.argmove, line.cmove * sizeof(MOVE));   pline->cmove = line.cmove + 1;  } } return alpha;}

 
  这个函数或许效率很低,因为它用到很多的堆栈空间,但是代码工作起来并不慢。有了这些改动后,如果函数返回Alpha和Beta之间的值,那么它不仅仅返回一个值,还包括能产生这个值的路线(一系列预测的着法)。这对于搜索树的任何结点都是有效的,包括根结点(它是最值得这么做的)。你可以这么来调用Alpha-Beta:
 
val = AlphaBeta(depth, -INFINITY, INFINITY, &line);
 
  你得到的是局面的值,以及在“line”这个存储区里保存的预测路线。“line”的数据结构是一个着法数组,以构成一个路线,另有一个整数来告诉你路线中有多少着法。
  如果你用深度等于零去调用Alpha-Beta,那么这个函数只调用“Evaluate()”并返回它的值。一个着法也没搜索,因此一个着法也没选择,从而和分值一起返回的路线其长度为零。
  如果你用某个深度调用这个搜索函数,那么你可以找到一个着法其值在Alpha和Beta之间,而你得到的不仅仅是分值,而且包括能产生这个值的一系列着法。为了在Alpha-Beta过程中建立路线,你拿出这个搜索到的着法,把它存入传递的路线存储区中【译注:即函数中的“*pline”】,并把局部的路线存储区【即函数中的“line”】也加到传递的存储区中。
  你可能会问:“既然有传过来的路线存储区,为什么又要在每次递归的堆栈中新分配一个?”因为你在搜索树中找到一个局部的线路,那么原来的线路被推翻了,但你不能毁坏已经建立好的线路。如果你找到某个返回值在Alpha和Beta之间的结点,你就认为这个结点在搜索树的根结点的路线上,这是不对的。有很多零碎的主要变例是被丢弃的。
  让你们感到惊讶的是,我在循环内用了“memcpy”这个函数【事实上这也是个循环,因此可能会认为它的效率很低】,它几乎不花时间,因为它很少被执行。
 
另一个思想
 
  另一个一目了然的方法,就是在搜索值返回后,从主置换表中提取主要变例。置换表项中有一个区域存放这局面的最佳着法。由于每个PV结点都有一个值在Alpha和Beta之间,散列项中必定保存着“最佳着法”。
  从置换表中提取主要变例,可以沿着散列项组成的链来提取,这就像吃馅饼一样简单。
  我知道很多程序(包括一些专业程序)是这样做的,但是我没有试过。
  【情况可能会比想象中的复杂,因为置换表中的数据会被覆盖,这样做就必须选择合适的覆盖策略。显然根结点是不会被覆盖的(它总是最后一个写入置换表),因此至少能提取出一个着法(即程序要走的那步棋),但是后续着法就很难保证了。深度优先的覆盖策略会比较有利,除此之外,也可以考虑PV结点优先的策略,因为PV结点的数量比Alpha结点和Beta结点少得多,所以这个覆盖策略对置换表不会产生很大的影响。
  另外,直接从Alpha-Beta函数提取的主要变例,会因为置换表的运用而中断,除非置换表里有一项用来存储主要变例(这不是不可能的,因为PV结点的数量非常少)。如果要得到完整的主要变例,可以考虑不在置换表中写入PV结点(某些程序甚至只在置换表中写入Beta结点)。】

0 0
原创粉丝点击