Alpha-Beta剪枝(Alpha Beta Pruning)

Alpha-Beta剪枝算法(Alpha Beta Pruning)

[说明] 本文基于<<CS 161 Recitation Notes - Minimax with Alpha Beta Pruning>>,文中的图片均来源于此笔记。


Alpha-Beta剪枝用于裁剪搜索树中没有意义的不需要搜索的树枝,以提高运算速度。


假设α为下界,β为上界,对于α ≤ N ≤ β:

若 α ≤ β  则N有解。

若 α > β 则N无解。


下面通过一个例子来说明Alpha-Beta剪枝算法。



上图为整颗搜索树。这里使用极小极大算法配合Alpha-Beta剪枝算法,正方形为自己(A),圆为对手(B)。

初始设置α为负无穷大,β为正无穷大。


对于B(第四层)而已,尽量使得A获利最小,因此当遇到使得A获利更小的情况,则需要修改β。这里3小于正无穷大,所以β修改为3。


(第四层)这里17大于3,不用修改β。


对于A(第三层)而言,自己获利越大越好,因此遇到利益值大于α的时候,需要α进行修改,这里3大于负无穷大,所以α修改为3


B(第四层)拥有一个方案使得A获利只有2,α=3,  β=2, α > β, 说明A(第三层)只要选择第二个方案, 则B必然可以使得A的获利少于A(第三层)的第一个方案,这样就不再需要考虑B(第四层)的其他候选方案了,因为A(第三层)根本不会选取第二个方案,多考虑也是浪费.


B(第二层)要使得A利益最小,则B(第二层)的第二个方案不能使得A的获利大于β, 也就是3. 但是若B(第二层)选择第二个方案, A(第三层)可以选择第一个方案使得A获利为15, α=15,  β=3, α β, 故不需要再考虑A(第三层)的第二个方案, 因为B(第二层)不会选择第二个方案.

A(第一层)使自己利益最大,也就是A(第一层)的第二个方案不能差于第一个方案, 但是A(第三层)的一个方案会导致利益为2, 小于3, 所以A(第三层)不会选择第一个方案, 因此B(第四层)也不用考虑第二个方案.


当A(第三层)考虑第二个方案时,发现获得利益为3,和A(第一层)使用第一个方案利益一样.如果根据上面的分析A(第一层)优先选择了第一个方案,那么B不再需要考虑第二种方案,如果A(第一层)还想进一步评估两个方案的优劣的话, B(第二层)则还需要考虑第二个方案,若B(第二层)的第二个方案使得A获利小于3,则A(第一层)只能选择第一个方案,若B(第二层)的第二个方案使得A获利大于3,则A(第一层)还需要根据其他因素来考虑最终选取哪种方案.





<think>嗯,用户想了解递归算法中的剪枝技术,以及如何在递归中实现剪枝优化。首先,我需要回忆一下剪枝的基本概念。剪枝,听起来像是修剪树枝,去掉不必要的部分,应用到算法里应该就是减少不必要的计算步骤吧。对吧?比如在搜索问题中,提前判断某些路径是否可行,如果不可行就停止继续递归,这样可以节省时间和空间。 那用户可能已经知道递归的基本原理,但想深入优化方法。可能需要先解释什么是剪枝,然后给出几种常见的剪枝技巧,比如可行性剪枝、最优性剪枝、记忆化剪枝,或者还有其他的分类?比如引用中的回溯算法用到了剪枝,比如在组合优化问题中,回溯算法构建解的过程中,如果发现当前路径不可能得到解,就剪掉这个分支。 接下来,我需要具体说明每种剪枝技巧的应用场景和实现方式。例如,可行性剪枝是在递归过程中提前判断当前路径是否符合条件,如果不符合就返回,不再继续。例如在迷宫问题中,如果当前位置已经出界了,就停止递归。最优性剪枝可能用于寻找最优解的情况,比如在路径问题中,如果当前路径的长度已经超过了已知的最短路径,就停止继续搜索。记忆化剪枝可能像动态规划中的记忆化搜索,保存中间结果避免重复计算,比如斐波那契数列的递归计算中,用数组保存已经计算过的值。 然后,用户可能需要示例代码来理解这些概念。比如,组合问题中的剪枝,或者二叉树剪枝的例子。例如引用中的二叉树剪枝,当节点值为0且子树没有1的时候,就剪掉这个节点。这可以用后序遍历的方式实现,先处理左右子树,再判断当前节点是否需要剪枝。 另外,用户提到的引用里有回溯算法中的剪枝技巧,这可能涉及到排列组合问题中的提前终止递归。比如生成全排列时,如果当前部分排列已经不可能满足条件,就提前返回。或者像八皇后问题,在放置皇后的时候检查是否有冲突,如果有冲突就不再继续放置,直接剪枝。 还有,用户可能关心剪枝的具体实现步骤,比如在递归函数中加入条件判断,或者在进入下一层递归之前检查某些条件。例如,在求组合数的时候,如果剩下的元素不足以填满需要的数量,就可以提前剪枝,减少递归次数。 然后,示例代码方面,可能需要用Python来展示,比如二叉树的剪枝代码,或者组合问题的剪枝代码。比如,LeetCode上的一些题目,像组合总和、子集问题等,常用剪枝优化。 还要注意引用中的例子,比如引用[3]提到的记忆化剪枝,使用数组保存中间结果,避免重复计算。这可能涉及到递归中的重复子问题,通过存储已经计算的结果来优化时间。 总结一下,回答的结构应该是:先解释剪枝的概念,然后分点介绍不同的剪枝技巧,每种技巧给出解释、应用场景和示例代码。最后可能还需要给出相关问题,帮助用户进一步学习。 可能需要注意用户提供的引用内容,比如引用[2]提到在二叉树中搜索时通过条件判断来剪枝,引用[3]中的记忆化优化。这些需要整合到回答中,并适当标注引用标识。 现在,要确保回答符合用户的要求:结构清晰,分步骤解释,使用中文,正确使用LaTeX格式的数学表达式,生成相关问题,并在引用处添加标识。同时,代码示例要正确,并且符合Python语法。 可能遇到的挑战是如何将不同的剪枝技巧分类清晰,并且用简单易懂的示例说明。例如,可行性剪枝和最优性剪枝的区别,记忆化剪枝与其他剪枝的不同。需要明确每种剪枝适用的场景,以及如何在代码中具体实现。 比如,可行性剪枝的例子可以是解决数独问题时,某个位置填入数字后是否合法,如果不合法就停止递归。最优性剪枝的例子可能是旅行商问题中,当前路径长度已经超过已知最短路径,就剪掉这个分支。记忆化剪枝的例子是斐波那契数列中使用缓存来存储已经计算过的值,避免重复计算。 然后,在代码示例部分,可能需要给出具体的函数,比如二叉树剪枝的代码,组合问题的剪枝代码,或者回溯算法的剪枝实现。每个代码块需要说明对应的剪枝技巧,并且注释关键部分。 最后,生成的相关问题需要围绕递归剪枝的扩展,比如如何选择剪枝策略,剪枝对算法复杂度的改善,实际应用案例等。</think>### 递归算法中的剪枝技术详解 剪枝是通过提前终止无效递归路径来优化算法性能的技术[^2]。以下是递归剪枝的核心技巧和实现方法: --- #### 1. **可行性剪枝** 在递归过程中提前判断当前路径是否可能满足条件,若不可行则立即返回。 - **应用场景**:迷宫路径搜索、数独求解 - **示例代码(数独合法性检查)**: ```python def solve_sudoku(board, row, col): if col == 9: # 列越界时换行 return solve_sudoku(board, row + 1, 0) if row == 9: # 数独已填完 return True if board[row][col] != 0: # 跳过已填数字 return solve_sudoku(board, row, col + 1) for num in 1..9: if is_valid(board, row, col, num): # 关键剪枝判断[^1] board[row][col] = num if solve_sudoku(board, row, col + 1): return True board[row][col] = 0 # 回溯 return False ``` --- #### 2. **最优性剪枝** 当发现当前路径不可能得到更优解时终止递归。 - **应用场景**:旅行商问题、最短路径优化 - **数学表达**: 设当前路径总长为 $L$,已知最优解为 $L_{min}$,若 $L \geq L_{min}$ 则剪枝- **代码片段**: ```python def tsp(current_path, current_cost): global best_cost if current_cost >= best_cost: # 剪枝判断[^3] return if len(current_path) == n: best_cost = min(best_cost, current_cost + distance[current_path[-1]][start]) # 递归其他路径... ``` --- #### 3. **记忆化剪枝(缓存优化)** 通过存储中间计算结果避免重复递归。 - **应用场景**:斐波那契数列、动态规划 - **数学表达**: 斐波那契递归公式 $F(n) = F(n-1) + F(n-2)$,通过缓存 $F(k)$ 的值减少计算量。 - **示例代码**: ```python memo = {0:0, 1:1} # 记忆字典初始化[^3] def fibonacci(n): if n not in memo: memo[n] = fibonacci(n-1) + fibonacci(n-2) # 存储计算结果 return memo[n] ``` --- #### 4. **结构剪枝(二叉树剪枝)** 通过后序遍历判断子树是否需要修剪。 - **应用场景**:删除二叉树中不满足条件的子树[^2] - **代码示例**: ```python def pruneTree(root): if not root: return None root.left = pruneTree(root.left) # 后序遍历处理子树 root.right = pruneTree(root.right) if root.val == 0 and not root.left and not root.right: # 剪枝条件 return None return root ``` --- #### 5. **组合问题剪枝** 通过限制取值范围避免重复组合。 - **数学表达**: 组合问题中若剩余元素数量 $k$ 不足以填满所需长度 $n$,即 $k < n$ 时剪枝- **代码示例(LeetCode 77题)**: ```python def combine(n, k): res = [] def backtrack(start, path): if len(path) == k: res.append(path.copy()) return # 剪枝:剩余元素数量必须 >= 所需元素数量 max_start = n - (k - len(path)) + 1 # 关键剪枝计算 for i in range(start, max_start + 1): path.append(i) backtrack(i + 1, path) path.pop() backtrack(1, []) return res ``` --- ### 关键总结 | 剪枝类型 | 优化目标 | 实现方法 | |----------------|------------------|----------------------------------| | 可行性剪枝 | 减少无效路径 | 在递归入口添加条件判断 | | 最优性剪枝 | 跳过次优解 | 比较当前解与历史最优解 | | 记忆化剪枝 | 避免重复计算 | 使用哈希表缓存中间结果 | | 结构剪枝 | 修剪无效子树 | 后序遍历 + 叶节点条件判断 | ---
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值