贡献者: int256
本文中,使用孩子节点表示在树中、深度仅更深一层,并且与其直接相邻的节点(也就是进行深度优先搜索过程中在当前节点下,再向下搜索一层可以达到的节点)。使用叶子节点表示最深一层节点。
首先值得提出的一点,这个搜索算法并不适用于围棋的布局、中盘等局面,更适合末期或象棋(对于前期也不是很适合,尤其是分支有很多的时候)或 Tick-Tack-Toe(井字棋)等,读者可以在学习完本文后思考其原因。
在双方博弈类型的搜索中,例如棋类的最优化搜索,如果我们是 AI,我们会希望,走下这一步棋后,我们的最大的失败可能最小。为什么这里是 “最大的失败可能” 而失败可能不是一个定值呢?因为对方的可能下法不止一种,对于每一种对方的下法,我们也都有一个 “失败的可能性”(实际上这里是一个最小值,我们选择下最优的下法让我们失败的可能性最小),我们需要使用这些失败的可能性的总体的最大值来评估这步棋的好坏。
所以可以发现,这是一个使得某个最大值(失败的可能性)最小的算法,所以叫做极小化极大搜索(Minimax 搜索)。有的地方也叫做 Minmax 搜索。
那么实际上搜索进行的是,” 你一步我一步 “的方法。也就是,循环以下过程:
考虑一个经过一些常规剪枝技巧的、搜索树为二叉树的对弈。我们模拟搜索并且步骤也遵循递归的步骤,通过模拟的过程来学习 Minimax 搜索方法。
假若现在应当 AI 下棋,我们向后搜索
其中,圆角方框为 AI 需要下棋,圆形为对方需要下棋。
那么,在对方下棋(也就是圆形的节点)进行搜索的时候,对方如果是 “足够聪明的”,会选择让我们的胜率最小,也就是失败率最大的下法。也就是对所有这些可能胜率求最小值,模拟搜索过程进行更新后得到新的搜索树:
之后向上一步,轮到 AI 下棋,AI 会选择使自己胜率最大的,也就是对所有可能胜率求最大值,这次更新后为:
现在这轮,又轮到对方下棋,对方这时候下棋仍然会选择使我们胜率最低的下法,也就是求最小值,这一次更新后的搜索树为:
最后回到了搜索的根节点,这一步是 AI 下棋,选择使得自己胜率最大的方法下棋,也就是对所有这些胜率求最大值,得到最终的搜索树:
这就是朴素 Minimax 算法的搜索过程,当然实际搜索还会记录路径信息,也就是我们这一步棋要如何下。带更新路径信息的搜索树如下:
其中,绿色路径为最终选择路径,紫色为该节点可以选择的、这个节点该下棋的一方可以选择的最优下法。
需要注意的是,实际情况中,搜索不是一层一层进行的,而是按递归的顺序(节点本身-左孩子到右孩子-节点本身-回溯)进行遍历的。
不难发现,我们在交替取 min
和 max
,同时记录这最大(小)值是从哪个孩子节点回溯来的,于是可以写出 Minimax 搜索的朴素方法的代码:
Alpha-Beta 剪枝是这类搜索的一个常用技巧,其中 Alpha(
我们来观测一个一共
首先搜索到第一个叶子节点,假若这处计算出胜率为
接下来搜索到下一个叶子节点,假若这处计算出胜率为
可以发现,这个时候可以确定根节点的值应当是
接下来仍按递归的顺序进行搜索,轮到搜索第三个叶子节点了。假若这处计算出胜率为
我们发现,根节点是对所有其孩子节点求最大值,但当前这节点的取值范围的最大值小于了根节点的取值范围的最小值,所以无论怎样根节点都不会取这个节点的值了。那么这个节点的整个子树都可以剪枝掉,如图。
于是也就可以确定根节点的值是
这个过程其实就是一个形象的
首先,引入
每个节点都会有其
那么显然,一个节点及其子树要被剪枝会发生在其
为此重新绘制一个包含
最开始的时候搜索递归访问到根节点,初始
接下来递归访问到最左侧孩子节点,继承其父亲结点的
继续递归,访问到第一个叶子节点,叶子节点不继承信息、仅自更新其自己
这条搜索到底,向前回溯,回溯过程中传递孩子节点信息给父亲并更新父亲节点的
之后继续递归搜索,轮到下一个叶子节点,仍然不考虑继承、仅自更新
回溯,在 Min 层所以更新
继续回溯,回溯到了根节点,因为在 Max 层所以更新
接下来继续搜索,该递归访问根节点的下一子节点,继承
继续递归,访问到其第一个子节点,这是叶子节点,不继承信息并自更新
回溯,在 Min 层所以更新
此时发现这节点的
break
掉),所以不继续搜索这节点的子树:
回溯,因为剪枝了所以不传递信息,剪枝掉的节点的信息不会对其父节点产生贡献,所以可以不考虑传递信息的问题:
发现没有其他可以搜索的内容,根节点的所有子节点都已经访问过,搜索结束,同时可以更新根节点的胜率信息,得到为全体子节点的最大值(因为在 Max 层)