回溯算法入门

回溯算法三要素

抽象地说,解决一个回溯问题,实际上就是遍历一棵决策树的过程,树的每个叶子节点存放着一个合法答案。你把整棵树遍历一遍,把叶子节点上的答案都收集起来,就能得到所有的合法答案

站在回溯树的一个节点上,你只需要思考 3 个问题:

1、路径:也就是已经做出的选择。 #记录下已经走过的路

2、选择列表:也就是你当前可以做的选择。

3、结束条件:也就是到达决策树底层,无法再做选择的条件

例如**[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层叶子节点,这里也就是选择列表为空的时候**。

我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。 前序位置做选择,后序位置撤销选择

for 选择 in 选择列表:
    # 做选择
    将该选择从选择列表移除
    路径.add(选择)
    backtrack(路径, 选择列表)
    # 撤销选择
    路径.remove(选择)
    将该选择再加入选择列表

回溯算法就是纯暴力穷举,复杂度一般都很高

46. 全排列

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. 二叉树算法

定义: 二叉树算法主要处理涉及二叉树结构的问题,包括遍历、插入、删除、查找等操作。二叉树问题通常通过递归或迭代的方式来解决。

关键点:

  • 递归: 二叉树的许多操作可以通过递归实现,因为树的结构本身具有递归性质。
  • 遍历: 二叉树的遍历方式(前序、中序、后序)可以用来处理或分析树中的节点。
  • 分治思想: 二叉树的每个节点可以看作一个子问题的根节点,这种问题分解方式类似于动态规划的子问题分解。

例子: 二叉搜索树的操作、树的遍历、路径和问题、平衡树等。

关系与联系

  1. 递归:
    • 回溯动态规划二叉树算法 都大量使用递归。回溯算法的递归深度决定了搜索的深度,动态规划中的递归常用来计算子问题,而二叉树的许多操作如遍历、查找等也可以用递归实现。
  2. 决策树与问题分解:
    • 回溯算法 可以视为在决策树中探索所有可能的路径,常常会遇到指数级别的时间复杂度。
    • 动态规划 在一定程度上可以看作是对回溯算法的优化,主要通过记忆化或表格法减少不必要的重复计算。动态规划中的递归往往表现为后序遍历,因为要先解决子问题再合并结果。
    • 二叉树算法 也是通过分解问题,特别是将大问题分解为左子树和右子树的小问题,类似于动态规划中的子问题分解。
  3. 剪枝与优化:
    • 回溯算法 通过剪枝来优化搜索过程,动态规划则是通过消除重复计算(重叠子问题)来优化。

总结

  • 回溯算法 是一种暴力搜索,适用于探索所有可能的解,它的效率往往不高,但可以保证找到所有解。
  • 动态规划 是一种优化方法,适用于具有重叠子问题和最优子结构的问题,能够通过存储中间结果来提高效率。
  • 二叉树算法 处理具有递归结构的二叉树问题,常使用分治法解决复杂问题。

三者在解决问题时常常有交集,但侧重点不同。回溯偏重探索和搜索,动态规划强调优化和效率,而二叉树算法主要处理树结构的递归问题。

回溯法代码框架套路 非常好用!

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:  触发结束条件
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

局部变量:

  • 用于在函数或方法中保存临时信息,适合短期使用。
  • 例如,一个方法内部的循环计数器 i,或临时计算结果,都可以是局部变量。

当函数内部的局部变量通过参数传递给另外一个函数时,另一个函数也可以使用这个局部变量。 短期使用的变量,采用局部变量的方式合适

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值