MTD(f)算法
前面已经介绍过,零宽窗口搜索(也称为极小窗口搜索)的结果只有二种,要么估值在窗口之上,要么估值在窗口之下。因此当我们对某段区间进行搜索时,可以选取区间上的一点进行零宽窗口搜索试探,利用其结果一分为二的特点,缩小估值的取值区间。在确定新的估值区间后,我们还可以再次进行试探。通过反复试探,就可以不断地缩小搜索范围,最终找到局面估值。
在选择每一次的试探值时,通常的想法是采用二分法,即每次都选择估值区间的中点进行试探。采用二分法的效率确实比较高,但是荷兰计算机科学家Aske Plaat经研究发现,在多次搜索试探过程中,局面估值往往会出现在前一次搜索的返回值附近,因此机械地选取中点做为新的试探值并不是最高效的。于是他提出了一种选取前一次搜索返回值做为新试探值的MTD算法(Memory-enhanced Test Driver,缓存增强试探法)——MTD(f)。MTD(f)算法的代码如下:
int mtd(int f, int depth){
int g = f;
int beta;
//搜索区间上、下限
int lower = -INF_VALUE;
int upper = INF_VALUE;
do {
//确定试探值
if (g == lower) {
beta = g + 1;
} else {
beta = g;
}
//进行零宽窗口试探
g = alpha_beta(beta-1, beta, depth, 0);
//调整搜索区间
if (g < beta) {
upper = g;
} else {
lower = g;
}
} while (lower < upper);
return g;
}
MTD(f)算法十分简捷,却又十分高效。但由于MTD(f)算法需要对于同一局面多次进行搜索,因此必须采用散列表,否则无法体现其高效性,这也是MTD算法名称中缓存增强(Memory-enhanced)的含义所在。实践表明,在同样应用散列表的情况下,MTD(f)算法的搜索速度一般会比PVS算法略快些,MTD(f)算法的高效性已经得到普遍认可。
在应用MTD(f)算法时,需要一个初始试探值,一般可以简单地选取初始搜索区间的中点,当然也可以根据具体情况选择对搜索更有利的值。由于该算法必须采用散列表,散列表的性能对于整体算法的效率也是至关重要的。另外,估值的取值范围大小,也会影响MTD(f)算法的搜索速度。一般来说,估值越粗略(即取值范围较小),搜索速度会越快,这是因为试探的次数较少所致。
如果算法涉及最佳棋步的求解,还有一个问题值得注意。如果MTD(f)最后一次搜索的是alpha节点,那么alpha_beta()函数得到的结果将是估值上限,而其求得的“最佳棋步”也只是满足此上限条件的棋步,不一定是真正的最佳棋步。因此在alpha_beta()函数(或散列表中)中应采取相应的措施,以避免输出该虚假的最佳棋步。或者可以对上述MTD(f)代码加以修改,当出现上述情况时,就以beta节点再搜索一次,确保获得正确的最佳棋步:
……
} while (lower < upper);
//如果最后一次搜索得到的只是上限,需再搜索一次,确保获得正确的最佳棋步
if (g < beta) {
g = alpha_beta(g-1, g, depth, 0);
}
return value;
}
