一、引言
1、为什么需要对抗搜索
在过去,我们讨论的搜索问题都发生在一个“静态”或“可预测”的环境中。例如,在路径规划问题中,从城市A到城市B的道路成本是固定的,环境不会主动与我们作对。然而,在许多现实世界的问题中,我们必须面对一个或多个会做出反应、并试图阻碍我们达成目标的对手。这类问题被称为对抗搜索问题,最典型的例子就是博弈,如棋类游戏。
在这些博弈环境中,我们的每一个决策不仅取决于我们自己的目标,还必须考虑到对手的可能反应。对手的目标通常与我们的目标相反。例如,在二人零和游戏中,一方的收益就是另一方的损失。
本文将探讨在这种对抗性环境中进行决策的算法。我们将从最基础的Minimax(极小化极大)算法入手,它构成了所有对抗搜索算法的理论基石。然后,我们将介绍对其进行优化的Alpha-Beta剪枝技术,这是在实践中应用最为广泛的算法。
2、博弈的形式化定义
一个博弈可以被形式化地定义为以下几个组成部分:
- 初始状态:博弈开始时的布局,记为
。 - 玩家:定义了轮到哪个玩家行动的函数,例如
PLAYER(s)返回在状态下行动的玩家。我们通常考虑两个玩家,分别称为 MAX(我方,试图最大化分数)和 MIN(对手,试图最小化分数)。 - 行动:返回在状态
下所有合法行动的集合,记为 ACTIONS(s)。 - 转移模型:定义了执行一个行动后状态的变化,记为
RESULT(s, a),返回从状态执行行动 后的新状态。 - 终止测试:判断一个状态
是否为博弈的终止状态,记为 TERMINAL-TEST(s)。 - 效用函数:也称为收益函数(Payoff Function),它只对终止状态定义。
UTILITY(s)返回博弈在终止状态时的数值分数。对于MAX玩家来说,分数越高越好;对于MIN玩家,分数越低越好。
对抗搜索的目标是,给定当前状态,找到一个能导向最优结果的行动,这个“最优结果”是在假设对手也会采取最优行动的前提下得到的。
二、Minimax 算法
Minimax算法是解决二人零和博弈问题的基础算法。它的核心思想是:假设对手的每一步行动都是为了使你的得分最小化,而你的每一步行动都是为了使你的最终得分最大化。
1、核心思想
想象一下你正在下棋。你即将走出一步,你面前有几种选择。对于你的每一种选择,你的对手又会有几种回应。如此往复,直到棋局结束分出胜负。
Minimax算法正是模拟了这个向前看的思考过程。它会生成一棵博弈树,其中:
- 节点:代表博弈的状态。
- 边:代表合法的行动。
- 树根:是当前的博弈状态。
- 叶节点:是博弈的终止状态。
算法的目标是为根节点(当前状态)的所有子节点(即所有可能的下一步行动)计算一个“Minimax值”。这个值代表了从该子节点出发,双方都采取最优策略后,最终能达到的效用。然后,MAX玩家(我方)只需选择那个Minimax值最大的子节点对应的行动即可。
这个Minimax值是如何计算的呢?
- 对于一个终止状态(叶节点),其Minimax值就是该状态的效用。
- 对于一个MAX节点(轮到我方行动),其Minimax值是其所有子节点的Minimax值中的最大值。因为MAX玩家会选择对自己最有利的后续状态。
- 对于一个MIN节点(轮到对手行动),其Minimax值是其所有子节点的Minimax值中的最小值。因为MIN玩家会选择对MAX玩家最不利的后续状态。
这个过程是一个从叶节点向根节点递归计算的过程。
2、算法定义与伪代码
Minimax值可以被递归地定义如下:
定义主决策函数 MINIMAX-DECISION表示该函数在当前状态
递归函数的伪代码如下:
Function MINIMAX-VALUE(state):
- IF
TERMINAL-TEST(state)THEN - RETURN
UTILITY(state) - IF
PLAYER(state)isMAXTHEN v- FOR EACH
actioninACTIONS(state)DO v- RETURN
v - ELSE (
PLAYER(state)isMIN) v- FOR EACH
actioninACTIONS(state)DO v- RETURN
v
3、详细示例
假设我们有一棵博弈树,搜索深度为
博弈树结构:
A (MAX) / | \ / | \ B C D (MIN) / \ / \ / \ E F G H I J叶节点的效用值:
UTILITY(E) = 3UTILITY(F) = 12UTILITY(G) = 8UTILITY(H) = 2UTILITY(I) = 4UTILITY(J) = 6
Minimax值计算过程(自底向上):
-
处理MIN节点 B:
- B 的子节点是 E 和 F。
- 因为 E 和 F 是叶节点,其Minimax值就是其效用值。
- 这意味着,如果MAX选择了行动导致状态B,理性的MIN玩家会选择行动导向状态E,从而让MAX的最终得分是3。
-
处理MIN节点 C:
- C 的子节点是 G 和 H。
-
处理MIN节点 D:
- D 的子节点是 I 和 J。
-
处理MAX节点 A(根节点):
- 现在我们已经计算出 A 的所有子节点的Minimax值。
最终决策:根节点 A 的Minimax值是MINIMAX-DECISION 会返回导致状态 D 的那个行动。
这个结果的含义是,在当前状态 A,MAX玩家的最佳选择是执行导向状态 D 的行动。因为即使对手(MIN)做出对他最有利的选择(导向状态I,值为
4、现实中的挑战与评估函数
在真实的复杂博弈(如国际象棋)中,博弈树极其庞大,我们不可能搜索到所有的终止状态。例如,国际象棋的平均分支因子
为了解决这个问题,我们引入了有限深度搜索:
- 设定一个最大搜索深度
。 - 将深度达到
的节点视为“叶节点”,即使它们不是真正的终止状态。 - 引入一个评估函数(Evaluation Function),记为
EVAL(s),来估计在非终止状态 下,局面对MAX玩家的有利程度。
评估函数 EVAL(s) 扮演了效用函数 UTILITY(s) 在截断点(cutoff)的角色。一个好的评估函数应该:
- 在终止状态时,其值等于效用函数的值。
- 计算速度快。
- 能够准确反映当前局面的优劣势。
例如,在国际象棋中,一个简单的评估函数可以是:
其中
修改后的Minimax算法定义为:
三、Alpha-Beta 剪枝 (Alpha-Beta Pruning)
1、核心思想
Minimax 算法通过穷尽搜索博弈树来找到最优的行动,但其
例如,假设你正在考虑一个行动 A,你推演后发现,在最好的情况下,这个行动能让你得到
Alpha-Beta 剪枝就是将这种直观的推理系统化、形式化的算法。它在执行 Minimax 搜索的过程中,通过维护两个边界值——Alpha (
2、Alpha 和 Beta 的含义
Alpha-Beta 剪枝的核心在于传递两个值,
-
(Alpha):MAX 玩家(我方)在当前路径上已经找到的最好选择(能确保的最低得分)。 值在搜索过程中只会增加或保持不变。- 初始值为
。 - 对于 MAX 节点,它会用其子节点返回的值来更新自己。
- 对于 MIN 节点,它只是将父节点传来的
值继续向下传递。
-
(Beta):MIN 玩家(对手)在当前路径上已经找到的最好选择(能施加给 MAX 的最高得分上限)。 值在搜索过程中只会减小或保持不变。- 初始值为
。 - 对于 MIN 节点,它会用其子节点返回的值来更新自己。
- 对于 MAX 节点,它只是将父节点传来的
值继续向下传递。
搜索过程中的关键判断是比较
-
在 MIN 节点处剪枝: 如果一个 MIN 节点的子节点返回的值
使得 ,那么这个 MIN 节点的分支可以被剪掉。因为 MIN 玩家的目标是让分数更低,它已经找到了一个可以让 MAX 得分低至 的选择。而 MAX 玩家在更高层的某个祖先节点处已经有了一个保底分数为 的选择了。既然 ,MAX 玩家绝不会走上通往这个 MIN 节点的路径。因此,该 MIN 节点的其他子节点无需再探索。 -
在 MAX 节点处剪枝:如果一个 MAX 节点的子节点返回的值
使得 ,那么这个 MAX 节点的分支可以被剪掉。因为 MAX 玩家的目标是让分数更高,它已经找到了一个能让自己的得分达到 的选择。而 MIN 玩家在更高层的某个祖先节点处已经有了一个能将 MAX 得分限制在 以下的选择了。既然 ,MIN 玩家绝不会让 MAX 玩家走上通往这个 MAX 节点的路径。因此,该 MAX 节点的其他子节点无需再探索。
3、算法伪代码
Alpha-Beta 算法通过修改 Minimax 的递归函数来实现,将
Function ALPHA-BETA-SEARCH(state):
vMAX-VALUE(state, -∞, +∞)- RETURN the action in
ACTIONS(state)with valuev
Function MAX-VALUE(state, α, β):
- IF
TERMINAL-TEST(state)THEN RETURNUTILITY(state) v- FOR EACH
actioninACTIONS(state)DO v- IF
THEN RETURNv(Beta 剪枝) - RETURN
v
Function MIN-VALUE(state, α, β):
- IF
TERMINAL-TEST(state)THEN RETURNUTILITY(state) v- FOR EACH
actioninACTIONS(state)DO v- IF
THEN RETURNv(Alpha 剪枝) - RETURN
v
4、详细示例
我们使用博弈树,并逐步演示 Alpha-Beta 剪枝的搜索过程。搜索顺序为深度优先,从左到右。
初始状态:
在根节点 A 调用 MAX-VALUE(A, -∞, +∞)。
-
A
:- 探索第一个子节点 B。调用
MIN-VALUE(B, -∞, +∞)。
- 探索第一个子节点 B。调用
-
B
:- 探索第一个子节点 E。E 是叶节点,返回效用值 3。
- 在 B 中,
v更新为 。 - B 更新自己的
值: 。 - 此时 B 的状态是 (α=-∞, β=3)。剪枝条件
(即 ) 不满足。 - 探索第二个子节点 F。F 是叶节点,返回效用值 12。
- 在 B 中,
v更新为 。 - B 更新自己的
值: 。 - B 的所有子节点都已探索完毕。B 向其父节点 A 返回最终的
v值:3。
-
A
:- A 收到来自 B 的返回值 3。
- A 更新自己的
v值:v 。 - A 更新自己的
值: 。 - 此时 A 的状态是 (α=3, β=+∞)。剪枝条件
(即 ) 不满足。 - 探索第二个子节点 C。调用
MIN-VALUE(C, 3, +∞)。注意,A 的 值被传递下去了
-
C
:- 探索第一个子节点 G。G 是叶节点,返回效用值 8。
- 在 C 中,
v更新为 。 - C 更新自己的
值: 。 - 此时 C 的状态是 (α=3, β=8)。剪枝条件
(即 ) 不满足。 - 探索第二个子节点 H。H 是叶节点,返回效用值 2。
- 在 C 中,
v更新为 。 - 剪枝判断: 检查
是否成立。即 。条件成立 - 发生 Alpha 剪枝! MIN 节点 C 立即停止探索其剩余的子节点(虽然这里没有了),并向其父节点 A 返回当前的
v值:2。 - 剪枝的逻辑: 在 C 节点,MIN 玩家已经找到了一个方法(走 H)能把 MAX 的得分压到 2。而 MAX 玩家在 A 节点已经有一个保底选择(走 B),能确保得到 3 分。因此,MAX 玩家永远不会选择走 C 这条路,因为这最多只能得到 2 分。所以,C 后续还有什么棋步已经无关紧要了。
-
A
:- A 收到来自 C 的返回值
。 - A 更新自己的
v值:v 。 - A 更新自己的
值: 。 - 此时 A 的状态仍是 (α=3, β=+∞)。
- 探索第三个子节点 D。调用
MIN-VALUE(D, 3, +∞)。
- A 收到来自 C 的返回值
-
D
:- 探索第一个子节点 I。I 是叶节点,返回效用值 4。
- 在 D 中,
v更新为 。 - D 更新自己的
值: 。 - 此时 D 的状态是 (α=3, β=4)。剪枝条件
(即 ) 不满足。 - 探索第二个子节点 J。J 是叶节点,返回效用值 6。
- 在 D 中,
v更新为 。 - D 更新自己的
值: 。 - D 的所有子节点都已探索完毕。D 向其父节点 A 返回最终的
v值:4。
-
A
:- A 收到来自 D 的返回值 4。
- A 更新自己的
v值:v 。 - A 更新自己的
值: 。 - A 的所有子节点都已探索完毕。A 返回最终的
v值:4。
最终决策:Alpha-Beta 搜索返回的根节点值是 4,与 Minimax 算法的结果完全一致。但是,在这个例子中,我们通过剪枝避免了对某些节点的评估(虽然这个小例子中剪枝效果不明显,但在更深的树中效果显著)。