【算法】回溯算法01

本文介绍了回溯算法的概念,将其与深度优先搜索(DFS)进行了对比,并详细阐述了回溯算法与动态规划的区别。通过LeetCode的全排列问题,解释了回溯算法的解题思路,包括设置状态变量、剪枝操作以及递归过程。此外,还展示了完整的解题代码,帮助读者深入理解回溯算法的实现细节。
摘要由CSDN通过智能技术生成

初识回溯算法01

参考:leetcode46【全排列】题解    ~~    回溯算法入门级详解 + 练习

1.1 回溯算法与深度优先搜索(DFS)

回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
       ~~~~~~        · 找到一个可能存在的正确的答案;
       ~~~~~~        · 在尝试了所有可能的分步方法后宣告该问题没有答案。

深度优先搜索 算法(Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深的搜索树的分支。当结点 v 的所在边都己被探寻过,搜索将回溯到发现结点 v 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止。如果还存在未被发现的结点,则选择其中一个作为源结点并重复以上过程,整个进程反复进行直到所有结点都被访问为止。

换句话说,回溯算法与递归、DFS有着千丝万缕的联系。

1.2 与动态规划的区别

共同点
用于求解多阶段决策问题。多阶段决策问题即:
① 求解一个问题分为很多步骤(阶段);
② 每一个步骤(阶段)可以有多种选择。

不同点
动态规划 只需要求我们评估最优解是多少,最优解对应的具体解是什么并不要求。因此很适合应用于评估一个方案的效果;
回溯算法 可以搜索得到所有的方案(当然包括最优解),但是本质上它是一种遍历算法,时间复杂度很高。

1.3 理解回溯算法之“全排列问题”

Leetcode 46 全排列 题目link
         ~~~~~~~~         给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

以 [1,2,3] 数组的全排列为例,讲讲回溯算法中的递归思想:
       ~~~~~~       以1开头的全排列:1+ [2,3] ;以2开头的全排列:2+ [1,3] ;以3开头的全排列:3+ [1,2] 。

蓝色箭头代表深度搜索,红色箭头代表回退过程
重点:
1、使用深度优先遍历有「回头」的过程,在「回头」以后, 状态变量需要设置成为和先前一样 ,因此在回到上一层结点的过程中,需要撤销上一次的选择,这个操作称之为「状态重置」
2、深度优先遍历,借助系统栈空间,保存所需要的「状态变量」,在编码中只需要注意遍历到相应的结点的时候,状态变量的值是正确的,具体的做法是:往下走一层的时候,path 变量在尾部追加,而往回走的时候,需要撤销上一次的选择,也是在尾部操作,因此 path 变量是一个栈。

1.3.1 设置2个状态变量

首先这棵树除了根结点叶子结点以外,每一个结点做的事情其实是一样的,即:在已经选择了一些数的前提下,在剩下的还没有选择的数中,依次选择一个数,这显然是一个 递归 结构;
1、递归的终止条件是: 一个排列中的数字已经选够了 ,因此我们需要一个变量来表示当前程序递归到第几层,我们把这个变量叫做 「depth」;
2、布尔数组「used」,初始化的时候都为true表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 false ,这样在考虑下一个位置的时候,就能够以 O(1)的时间复杂度判断这个数是否被选择过,这是一种以空间换时间的思想。

1.3.2 剪枝

由于回溯算法的时间复杂度很高,因此在遍历的时候,如果能够提前知道这一条分支不能搜索到满意的结果,就可以提前结束,这一步操作称为剪枝。有些时候,需要做一些预处理工作(例如排序)才能达到剪枝的目的。预处理工作虽然也消耗时间,但能够剪枝节约的时间更多。

1.3.3 解题过程

dfs参数: ① nums 原数组. ② bool型used[]数组. ③ depth深度. ④ 栈空间path[]数组. ⑤ 存放结果的的res[[]]二维数组 (参数必须引用传递)

递归终止: 一般是depth到达最大深度,叶子节点处。

if(depth==len){
      res.push_back(path);
      return;
}

递归过程: 入栈→调用递归→退栈。

for(int i=0;i<len;i++){
    if(used[i]==true)
    {
        path.push_back(nums[i]);
        used[i]=false;
        dfs(nums,used,depth+1,path,res);
        used[i]=true;
        path.pop_back();
    }
}
1.3.4 回溯过程path状态
  • 递 [1] → [1,2] → [1,2,3]
    归 [1,2] → [1]
    递 [1,3] → [1,3,2]
    归 [1,3] → [1] → [ ]
  • 递 [2] → [2,1] → [2,1,3]
    归 [2,1] → [2]
    递 [2,3] → [2,3,1]
    归 [2,3] → [2] → [ ]
  • 递 [3] → [3,1] → [3,1,2]
    归 [3,1] → [3]
    递 [3,2] → [3,2,1]
    归 [3,2] → [3] → [ ]
1.3.5 完整代码
class Solution {
public:
    void dfs(vector<int> nums,vector<bool> used,int depth,vector<int> path,vector<vector<int>>& res)
    {
        int len=nums.size();
        if(depth==len)
        {
            res.push_back(path);
            return;
        }
        for(int i=0;i<len;i++)
        {
            if(used[i]==true)
            {
                path.push_back(nums[i]);
                used[i]=false;
                dfs(nums,used,depth+1,path,res);
                used[i]=true;
                path.pop_back();
            }
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        int len=nums.size();
        if(len==1)
        {
            res.push_back(nums);
            return res;
        }
        vector<bool> used(len,true);
        //dfs(candidates,begin,target,path,res);
        vector<int> path;
        dfs(nums,used,0,path,res);
        return res;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值