回溯算法三要素
抽象地说,解决一个回溯问题,实际上就是遍历一棵决策树的过程,树的每个叶子节点存放着一个合法答案。你把整棵树遍历一遍,把叶子节点上的答案都收集起来,就能得到所有的合法答案。
站在回溯树的一个节点上,你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。 #记录下已经走过的路
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件
例如**[2]
就是「路径」,记录你已经做过的选择;[1,3]
就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层叶子节点,这里也就是选择列表为空的时候**。
我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。 前序位置做选择,后序位置撤销选择
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表
回溯算法就是纯暴力穷举,复杂度一般都很高。
class Solution:
def __init__(self):
self.res = []
# 主函数,输入一组不重复的数字,返回它们的全排列
def permute(self, nums):
# 记录「路径」
track = []
# 「路径」中的元素会被标记为 true,避免重复使用
used = [False] * len(nums)
self.backtrack(nums, track, used)
return self.res
# 路径:记录在 track 中
# 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false)
# 结束条件:nums 中的元素全都在 track 中出现
def backtrack(self, nums, track, used):
# 触发结束条件
if len(track) == len(nums):
self.res.append(track.copy())
return
for i in range(len(nums)):
# 排除不合法的选择
if used[i]:
# nums[i] 已经在 track 中,跳过
continue
# 做选择 前序位置做选择
track.append(nums[i])
used[i] = True
# 进入下一层决策树
self.backtrack(nums, track, used)
# 取消选择 后序位置撤销选择
track.pop()
used[i] = False
回溯算法、动态规划和二叉树算法是解决不同类型问题的三种重要算法思想,但它们之间有一些联系,特别是在递归和问题分解方面。理解它们的关系可以帮助你更好地选择和应用这些算法。
1. 回溯算法
定义: 回溯算法是一种暴力搜索算法,用于在决策树中搜索所有可能的解,适用于求解组合、排列、子集等问题。回溯算法通过递归方式尝试所有可能的路径,当发现某条路径不满足条件时,回溯到上一层并尝试其他路径。
关键点:
- 决策树: 问题的解空间通常可以表示为一棵树,树的每个节点代表一个决策或选择。
- 回溯: 在探索完一条路径后,退回到上一个决策点,继续探索其他可能的路径。
- 剪枝: 提前终止不符合条件的路径,减少搜索空间。
例子: 求解N皇后问题、全排列问题、子集和问题等。
2. 动态规划
定义: 动态规划是一种优化算法,通常用于求解具有重叠子问题和最优子结构性质的问题。通过将复杂问题分解为更小的子问题,逐步构建解的过程。
关键点:
- 重叠子问题: 问题可以被分解成相同的子问题,重复计算会浪费资源。
- 最优子结构: 问题的最优解可以通过其子问题的最优解来构建。
- 记忆化或表格法: 通过记忆化(自顶向下)或表格法(自底向上)存储子问题的结果,避免重复计算。
例子: 斐波那契数列、背包问题、最长公共子序列、硬币找零问题等。
3. 二叉树算法
定义: 二叉树算法主要处理涉及二叉树结构的问题,包括遍历、插入、删除、查找等操作。二叉树问题通常通过递归或迭代的方式来解决。
关键点:
- 递归: 二叉树的许多操作可以通过递归实现,因为树的结构本身具有递归性质。
- 遍历: 二叉树的遍历方式(前序、中序、后序)可以用来处理或分析树中的节点。
- 分治思想: 二叉树的每个节点可以看作一个子问题的根节点,这种问题分解方式类似于动态规划的子问题分解。
例子: 二叉搜索树的操作、树的遍历、路径和问题、平衡树等。
关系与联系
- 递归:
- 回溯、动态规划 和 二叉树算法 都大量使用递归。回溯算法的递归深度决定了搜索的深度,动态规划中的递归常用来计算子问题,而二叉树的许多操作如遍历、查找等也可以用递归实现。
- 决策树与问题分解:
- 回溯算法 可以视为在决策树中探索所有可能的路径,常常会遇到指数级别的时间复杂度。
- 动态规划 在一定程度上可以看作是对回溯算法的优化,主要通过记忆化或表格法减少不必要的重复计算。动态规划中的递归往往表现为后序遍历,因为要先解决子问题再合并结果。
- 二叉树算法 也是通过分解问题,特别是将大问题分解为左子树和右子树的小问题,类似于动态规划中的子问题分解。
- 剪枝与优化:
- 回溯算法 通过剪枝来优化搜索过程,动态规划则是通过消除重复计算(重叠子问题)来优化。
总结
- 回溯算法 是一种暴力搜索,适用于探索所有可能的解,它的效率往往不高,但可以保证找到所有解。
- 动态规划 是一种优化方法,适用于具有重叠子问题和最优子结构的问题,能够通过存储中间结果来提高效率。
- 二叉树算法 处理具有递归结构的二叉树问题,常使用分治法解决复杂问题。
三者在解决问题时常常有交集,但侧重点不同。回溯偏重探索和搜索,动态规划强调优化和效率,而二叉树算法主要处理树结构的递归问题。
回溯法代码框架套路 非常好用!
result = [] def backtrack(路径, 选择列表): if 满足结束条件: 触发结束条件 result.add(路径) return for 选择 in 选择列表: 做选择 backtrack(路径, 选择列表) 撤销选择
局部变量:
- 用于在函数或方法中保存临时信息,适合短期使用。
- 例如,一个方法内部的循环计数器
i
,或临时计算结果,都可以是局部变量。
当函数内部的局部变量通过参数传递给另外一个函数时,另一个函数也可以使用这个局部变量。 短期使用的变量,采用局部变量的方式合适
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » 回溯算法入门
发表评论 取消回复