4回溯法、空间状态树

回溯 <---->递归
1.递归的下面就是回溯的过程

2.回溯法是一个 纯暴力的 搜索

3.回溯法解决的问题:

3.1组合 如:1234  两两组合

3.2切割问题 如:一个字符串有多少个切割方式 ,或者切割出来是回文

3.3子集 : 1 2 3 4  的子集

3.4排列问题(顺序)

3.5棋盘问题:n皇后  解数独

4.回溯可抽象成树形结构
树的宽度是集合的数目 for循环
树的深度是递归的层数

void  backtracking(){

进入终止条件后开始
	if(终止条件)	{

		收集结果 

		return	//本层函数调用结束

	}

for(集合的元素集,类似子节点的个数)

	{

		处理结点

		递归函数; 

		回溯操作

	(撤销处理结点122撤销 ,13 撤销314}
	return 

}

组合问题

1 2 3 4
比如求两个数的组合 12 13 14 23 24 34 可以两层for循环
如果10个数求三个数的组合 三层for循环
n个数求50个数的组合 50层for循环 不合理
所以 用回溯(递归)来控制for循环的次数

树形结构
在这里插入图片描述
可以看出这个棵树,一开始集合是 1,2,3,4, 从左向右取数,取过的数,不在重复取。

第一次取1,集合变为2,3,4 ,因为k为2,我们只需要再取一个数就可以了,分别取2,3,4,得到集合[1,2] [1,3] [1,4],以此类推。

回溯三部曲

  • 递归函数参数和返回值

  • 确定终止条件

  • 单层递归逻辑

  • 递归函数参数和返回值
    参数 二维数组result 存放最后结果
    一维数组path 存放路径上的值
    n个数 k个数的组合 startindex 告诉递归每层从哪个数开始取 比如在第二层中从234开始取

  • 确定终止条件
    叶子节点 结果的大小是2 path.size==k 收集path结果

  • 单层递归逻辑
    先将节点i加入路径path
    回溯(因为要进入下一层 开始的结点是从i+1开始)
    撤回结点 path.pop()

private:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path; // 用来存放符合条件结果
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            result.push_back(path);//取一条路径上的结果
            return;
        }
        for (int i = startIndex; i <= n; i++) {
            path.push_back(i); // 处理节点 
            backtracking(n, k, i + 1); // 递归
            path.pop_back(); // 回溯,撤销处理的节点
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        result.clear(); // 可以不写
        path.clear();   // 可以不写
        backtracking(n, k, 1);
        return result;
    }
};

组合问题剪枝

来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了(因为只剩下3,4达不到4个数字)。在第二层for循环,从元素3开始的遍历都没有意义了。

每一层的for循环从第二个数开始遍历的话,都没有意义。
在这里插入图片描述
「所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置」。
「如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了」。

注意代码中i,就是for循环里选择的起始位置。

for (int i = startIndex; i <= n; i++) { 

接下来看一下优化过程如下:

  • 已经选择的元素个数:path.size();
  • 还需要的元素个数为: k - path.size();
  • 在集合n中至少要从该起始位置 : n - (k - path.size()) + 1,开始遍历

就是说从开始位置到最终位置还需要 k - path.size()个元素
所以设开始位置为index 所以n-index+1= k - path.size() 所以index= n - (k - path.size()) + 1

举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。从2开始搜索都是合理的,可以是组合[2, 3, 4]。

所以优化之后的for循环是:

for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置
class Solution {
private:
    vector<vector<int>> result; 
    vector<int> path;
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方 就是结点孩子的个数
            path.push_back(i); // 处理节点 
            backtracking(n, k, i + 1);//递归就是沿着树枝往下搜索的过程
            path.pop_back(); // 回溯,撤销处理的节点
        }
    }
public:

    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1: 输入:candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ]

示例 2: 输入:candidates = [2,3,5], target = 8, 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]
在这里插入图片描述

#思路
回溯三部曲

  • 函数参数和返回值

路径上的值的组合如[1,2]为path 一维数组
result 二维数组,将符合的path放入result中
s为path内所有元素的和
target 目标和
startindex 开始的搜索的结点
candidate 集合

  • 终止条件

if( s>target) //return
if( s==target)
result.push_back(path)//收集结果
return

  • 单层搜索逻辑

for (int i = startIndex; i < candidates.size(); i++) {集合的长度
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i); // 关键点:不用i+1了,表示可以重复读取当前的数
sum -= candidates[i]; // 回溯
path.pop_back(); // 回溯
}

// 版本一
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum > target) {
            return;
        }
        if (sum == target) {
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i < candidates.size(); i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i); // 不用i+1了,表示可以重复读取当前的数
            sum -= candidates[i];
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

状态空间树

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

蒙特卡罗 找到一定范围的解 一定能在这个范围内找到解
拉斯维加斯 找到精确的解 但是解不一定正确
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为每个背包都有选还是不选两种选择

n皇后 不同行不同列 不在同一对角线
xi表示的是列号
在这里插入图片描述
判断k行皇后是否可以放在i列上面在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

子集合数问题

在这里插入图片描述
在这里插入图片描述

哈密顿环节 图着色问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
相邻的图连起来就可以

回溯法和分支界限法的异同

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 10
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
回溯法是一种常用于求解组合优化问题的算法,其中解空间是问题的解空间的一种表示形式,而解向量则是解空间中的一个具体解。 解空间是通过对问题的所有可能解进行系统性的枚举来构建的一棵,其中每个节点代表一个可能的解。的根节点表示问题的初始状态,而叶子节点表示问题的最终解。每个节点都有一个或多个子节点,表示在当前状态下可能的下一步选择。根据问题的特点,可以使用不同的策略来构建解空间,例如有序选择、无序选择、可重复选择等。 解向量是解空间中的一个具体解,它是一个向量或数组,包含了问题中所有自变量的取值。回溯法通过深度优先搜索解空间,并在搜索过程中不断更新当前的解向量,直到找到满足问题约束条件的解或遍历完整个解空间。 在回溯法中,每次进行决策时,都会选择一个当前节点的子节点作为下一步的选择,并更新解向量。如果在某个节点发现当前解向量不满足问题约束条件,则回溯到上一个节点,选择另一个子节点继续搜索,直到找到满足约束条件的解或遍历完整个解空间。 因此,回溯法通过搜索解空间并不断更新解向量,最终找到满足约束条件的解。解向量是回溯法中的一个重要概念,它与解空间密切相关,代表了问题的一个具体解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值