目录
一、基本概念
二、以简单全排列认识回溯
(一)决策树
(二)回溯示意图
(三)核心代码
(四)完整代码
三、组合问题
(一)问题
(二)示意图
(三)核心代码
(四)完整代码
(五)剪枝
四、总结
一、基本概念
- 回溯法是枚举法的一种,对于某些问题而言,回溯法是一种可以找出所有(或一部分)解的一般性算法,同时避免枚举的不正确的数值。一旦发现不正确的数值,就不再递归到下一层,而是回溯到上一层,以节省时间,是一种走不通就退回再走的方式
二、以简单全排列认识回溯
(一)决策树
- 比如求三个数[1,2,3]的全排列,如果第一位先排1,那么第二位只能是2或3,如果第二位是2,那么第三位只能是3......
- 只要从根遍历这棵树,记录路径上的数字,其实就是所有的全排列。不妨把这棵树称为回溯算法的「决策树」,为啥说这是决策树呢?因为在每个节点上其实都在做决策
- 比如现在在蓝色节点,可以选择2,也可以选择3
- 同时每做一次选择,都展开一棵空间树
- 选择完后,如果是重复选是路径,就做剪枝
- 将可做的选择和已走的路径作为树节点的两个属性
(二)回溯示意图
(三)核心代码
void BackTrack(vector<int> nu, vector<int>& p, vector<vector<int>>& r)
{
if (p.size() == nu.size())//结束条件
{
r.push_back(p);
return;
}
for (int i = 0; i <= nu.size() - 1; i++)
{
if (judge(nu[i], p))//如果为假,本次循环后面的代码不会执行
{
continue;
}
p.push_back(nu[i]);//处理节点
BackTrack(nu, p, r);
p.pop_back();//撤销节点
}
}
(四)完整代码
#include<iostream>
#include<vector>
#include<stdbool.h>
using namespace std;
bool judge(int x, vector<int>p)//判断p中有没有元素x
{
int i = 0;
while (i < p.size())
{
if (x == p[i])
return true;
else
i++;
}
return false;
}
void BackTrack(vector<int> nu, vector<int>& p, vector<vector<int>>& r)
{
if (p.size() == nu.size())//结束条件
{
r.push_back(p);
return;
}
for (int i = 0; i <= nu.size() - 1; i++)
{
if (judge(nu[i], p))//如果为假,本次循环后面的代码不会执行
{
continue;
}
p.push_back(nu[i]);//处理节点
BackTrack(nu, p, r);
p.pop_back();//撤销节点
}
}
void print(vector<vector<int>> r)//遍历
{
for (int i = 0; i < r.size(); i++)
{
for (int j = 0; j < r[i].size(); j++)
{
cout << r[i][j] << " ";
}
cout << endl;
}
}
int main()
{
int n;//元素个数
cout << "输入元素个数:";
cin >> n;
vector<vector<int>>result;//存放符合条件结果的集合
vector<int>path;//已走的路径
vector<int>num(n);//存放元素
cout << "依次输入各元素:";
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
BackTrack(num, path, result);
cout << "全排列结果:" << endl;
print(result);//遍历
}
三、组合问题
(一)问题
- 有任意n个数,返回所有可能的 k 个数的组合
(二)示意图
(三)核心代码
void BackTrack(int start, int k, vector<int> nu, vector<int>& p, vector<vector<int>>& r)
{
if (p.size()==k)//结束条件
{
r.push_back(p);
return;
}
for (int i = start; i <= nu.size() - 1; i++)
{
p.push_back(nu[i]);//处理节点
BackTrack(i + 1, k, nu, p, r);
p.pop_back();//回溯
}
}
(四)完整代码
#include<iostream>
#include<vector>
#include<stdbool.h>
using namespace std;
void BackTrack(int start, int k, vector<int> nu, vector<int>& p, vector<vector<int>>& r)
{
if (p.size()==k)//结束条件
{
r.push_back(p);
return;
}
for (int i = start; i <= nu.size() - 1; i++)
{
p.push_back(nu[i]);//处理节点
BackTrack(i + 1, k, nu, p, r);
p.pop_back();//回溯
}
}
void print(vector<vector<int>> r)//打印
{
for (int i = 0; i < r.size(); i++)
{
for (int j = 0; j < r[i].size(); j++)
{
cout << r[i][j] << " ";
}
cout << endl;
}
}
int main()
{
int n,k;//元素个数和每个组合元素个数
cout << "输入元素个数:";
cin >> n;
vector<vector<int>>result;//存放符合条件结果的集合
vector<int>path;//已走的路径
vector<int>num(n);//存放元素
cout << "依次输入各元素:";
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
cout << "输入组合元素个数:";
cin >> k;
BackTrack(0, k, num, path, result);
cout << "结果:" << endl;
print(result);//打印
}
//输入元素个数:5
//依次输入各元素:1 2 3 4 5
//输入组合元素个数:2
//结果:
//1 2
//1 3
//1 4
//1 5
//2 3
//2 4
//2 5
//3 4
//3 5
//4 5
(五)剪枝
- 优化过程:
- 已经选择的元素个数:path.size()
- 还需要的元素个数为: k - path.size()
- 在集合中至多要从num.size() - (k-p.size())位置,开始遍历
#include<iostream>
#include<vector>
#include<stdbool.h>
using namespace std;
void BackTrack(int start, int k, vector<int> nu, vector<int>& p, vector<vector<int>>& r)
{
if (p.size()==k)//结束条件
{
r.push_back(p);
return;
}
for (int i = start; i <= nu.size() - (k-p.size()); i++)
{
p.push_back(nu[i]);//处理节点
BackTrack(i + 1, k, nu, p, r);
p.pop_back();//回溯
}
}
void print(vector<vector<int>> r)//打印
{
for (int i = 0; i < r.size(); i++)
{
for (int j = 0; j < r[i].size(); j++)
{
cout << r[i][j] << " ";
}
cout << endl;
}
}
int main()
{
int n,k;//元素个数和每个组合元素个数
cout << "输入元素个数:";
cin >> n;
vector<vector<int>>result;//存放符合条件结果的集合
vector<int>path;//已走的路径
vector<int>num(n);//存放元素
cout << "依次输入各元素:";
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
cout << "输入组合元素个数:";
cin >> k;
BackTrack(0, k, num, path, result);
cout << "结果:" << endl;
print(result);//打印
}
//输入元素个数:5
//依次输入各元素:1 2 3 4 5
//输入组合元素个数:2
//结果:
//1 2
//1 3
//1 4
//1 5
//2 3
//2 4
//2 5
//3 4
//3 5
//4 5
四、总结
- 回溯算法模板
void backtracking(参数)
{
if (终止条件)
{
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小))
{
处理节点;
backtracking(路径,选择列表);//递归
回溯,撤销处理结果
}
}