详细讲解回溯算法(一)

本篇博文先不根据样例讲解算法,我会在接下的博文中一一讲解回溯法的具体运用。

这里先详细讲解回溯算法的原理和思路。

在了解回溯算法之前,先对回溯算法中涉及的知识点的概念先讲解下,方便理解博文,,哈哈大家不要嫌啰嗦,可能都想直接了解什么是回溯法,但基础不好,后面的运用又怎能彻底掌握呢,不要嫌麻烦,多点耐心,这个其实很容易就理解的,嗷嗷嗷!!!

1.1问题的解空间

一个复杂问题的解决方案是由若干个小的决策步骤组成的决策序列,所以一个问题的解可以表示成解向量X=(x1,x2,.....xn),其中分量xi对应第i步的选择,X中个分量xi所有取值的组合构成问题的解向量空间,简称解空间或者解空间树(因为解空间一般用树形式来组织),由于一个解向量往往对应问题的某个状态,所以解空间又称为问题的状态空间树。

可行解:解空间中满足约束条件的解空间;

最优解:解空间中使目标函数取最大或者最小值的可行解

这里拿球数组a的幂集,来对解空间树进行讲解;

  解:解向量X=(x1,x2,x3),xi=1(1<=i<=3)表示选择ai.xi=0表示不选择ai。求解过程分为3步,分别对a的3个元素做决策(选择或不选择),对应的解空间图如上图所示,其中每个叶子结点都构成一个解,例如I结点的解向量为(1,1,0),对应解是(1,-2)。

一个问题的求解过程就是在对应的解空间中搜索以寻找满足目标函数的解,所以算法的设计的关键点;

(1):结点是如何拓展的,例如求幂集问题中,第i层结点的扩展方式就是选择ai和不选择ai两种;

(2):在解空间树种按什么方式搜索,一种是DFS,回溯法就是这种;另一种是BFS,分枝限界法是这种;

(3):解空间树通常是庞大的,如何高效地找打问题的解;

解空间树有两种类型。

子集树:所给的问题是从n个元素的集合S中找到满足某种性质的子集时,相应的解空间树。如上图

排列树:所给的问题是确定n个元素满足某种性质的排列时,相应的解空间树。

需要注意的是,问题的解空间树是虚拟的,不需要在算法执行时构造一棵真正的树结构。

好了,啰嗦完了,下面讲解算法。

1.2什么是回溯法:

按照DFS算法的策略,从跟结点出发搜索解空间树。首先跟结点成为活结点(指自身已生成但其孩子结点没有全部生成的结点),同时也成为当前的扩展结点(指正在产生孩子结点的结点,也称为E结点)

在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成了死结点(指其所有子结点均已产生的结点)。此时应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法以这种方式递归地在解空间中搜索,直到找到所要求的解或解空间中已无活结点为止。

所以回溯法体现出走不通就退回再走的思路。

这里注意:

1.由于采用回溯法求解时存在退回到祖先结点的过程,所以需要保存搜索过的结点。通常有两种:一:用子定义栈来保存;二:采用递归方法。

2.用回溯法通常采用两种策略避免无效搜索。一:用约束函数在扩展结点处剪除不满足约束条件的路径;二:用限界函数剪去得不到问题的解或最优解的路径。这两类函数统称为剪枝函数

这里归纳下,用回溯法求解的一般步骤:

(1)针对给定的问题确定问题的解空间树

(2)确定结点的扩展搜索规则。

(3)以深度优先方式搜索解空间树,并在搜索过程中可以采用剪枝函数来避免无效搜索。其中,深度优先方式可以采用递归回溯或者非递归(迭代)回溯。

好了,回溯法到这里就讲完了,有看不懂的可以问我,嗯嗯嗯。。。另外,我在讲点东西。

1,3回溯法与深度优先遍历的异同。

两者的不同点如下:

(1)访问的次序不同:深度优先遍历的目的是“遍历”,本质是无序的,重要的是是否被访问过,因此在实现上只需要对于每个位置是否被访问就足够了。回溯法的目的是“求解过程”,本质是有序的,也就是说必须每一步都是要求的次序。

(2)访问次数不同:深度优先遍历对已经访问过的顶点不再访问。回溯法中已经访问过的顶点可能再次访问。

(3)剪枝不同:深度优先遍历不含剪枝。

实际上,除了剪枝是回溯法的一个明显特征外(并非任何回溯法都包含剪枝部分),很难严格区分回溯法 与深度优先遍历。因为这些算法很多是递归算法,在递归调用中隐含着状态的自动回退和恢复。

 

 

 

 

 

 

 

 

### C++ 中回溯算法的实现与讲解 #### 回溯算法的概念 回溯法种经典的算法设计方法,广泛应用于解决组合问题、排列问题以及划分问题等。其核心思想是通过试探性的逐步构建解决方案,在发现当前路径无法满足条件时立即撤销该选择并回到上步重新尝试其他可能性[^4]。 --- #### 二、C++ 回溯算法的具体实现案例分析 以下是几个典型的基于 C++ 的回溯算法实例及其解析: ##### 1. 子集生成问题 此问题是求解给定数组 `nums` 所有可能的子集(幂集),允许元素重复出现的情况。具体代码如下所示: ```cpp #include <iostream> #include <vector> using namespace std; class Solution { public: vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int>> result; vector<int> current; backtrack(nums, 0, current, result); return result; } private: void backtrack(vector<int>& nums, int start, vector<int>& current, vector<vector<int>>& result) { result.push_back(current); // 将当前状态存入结果集中 for (int i = start; i < nums.size(); ++i) { current.push_back(nums[i]); // 做出选择 backtrack(nums, i + 1, current, result); // 进行下步决策 current.pop_back(); // 撤销选择 } } }; int main() { vector<int> nums = {1, 2, 3}; Solution solver; vector<vector<int>> result = solver.subsets(nums); for (const auto& subset : result) { for (int num : subset) { cout << num << " "; } cout << endl; } return 0; } ``` 上述代码展示了如何利用递归函数 `backtrack()` 来枚举所有可能的子集,并将其存储至最终的结果列表中[^1]。 --- ##### 2. 数字拆分问题 另个典型例子是对正整数 n 的不同方式拆分为若干个较小自然数之和的问题。下面提供了个简单的实现版本: ```cpp #include <bits/stdc++.h> using namespace std; int a[10001] = {1}, n, total; // 定义递归回溯函数 void search(int s, int t){ int i; for(i = a[t-1]; i <= s; i++){ if(i < n){ a[t] = i; s -= i; if(s == 0){ print(t); } else{ search(s, t+1); } s += i; } } } // 输出每种拆分方案 void print(int t){ cout << n << "="; for(int i=1;i<=t-1;i++) cout << a[i] << "+"; cout << a[t] << endl; total++; } int main(){ cin >> n; search(n, 1); cout << "total=" << total << endl; return 0; } ``` 这段程序实现了对输入数值的不同分解形式进行穷尽列举的功能[^2]。 --- ##### 3. 字符串分割成回文序列 最后个示例涉及字符串操作领域内的个问题:将个字符串划分为多个部分使得每部分均为回文结构。下面是完整的源码展示: ```cpp #include <string> #include <vector> using namespace std; class Solution { public: bool isPalindrome(const string &s, int start, int end){ while(start < end && s[start]==s[end]){ start++;end--; } return start >= end; } void dfs(const string &s, vector<string> path, vector<vector<string>> &res, int pos){ if(pos>=s.length()){ res.emplace_back(path); return ; } for(auto i=pos;i<s.length();++i){ if(!isPalindrome(s,pos,i))continue; path.emplace_back(s.substr(pos,i-pos+1)); dfs(s,path,res,i+1); path.pop_back(); } } vector<vector<string>> partition(string s) { vector<vector<string>> res; vector<string> path; dfs(s,path,res,0); return res; } }; ``` 这里采用了深度优先搜索加剪枝策略来优化性能表现的同时也保证了解空间覆盖全面性[^3]。 --- #### 三、总结说明 综上所述,回溯算法作为种通用性强且灵活度高的计算模型,在实际应用中有非常广泛的用途价值所在。无论是处理离散型数据还是连续区间上的复杂约束条件下寻找最优解的任务场景下都能发挥重要作用。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值