回溯算法(基础)

目录

一、基本概念

二、以简单全排列认识回溯 

(一)决策树 

(二)回溯示意图  

(三)核心代码 

(四)完整代码 

三、组合问题

(一)问题   

(二)示意图 

(三)核心代码 

(四)完整代码 

(五)剪枝 

四、总结


一、基本概念

  • 回溯法是枚举法的一种,对于某些问题而言,回溯法是一种可以找出所有(或一部分)解的一般性算法,同时避免枚举的不正确的数值。一旦发现不正确的数值,就不再递归到下一层,而是回溯到上一层,以节省时间,是一种走不通就退回再走的方式

二、以简单全排列认识回溯 

(一)决策树 

  • 比如求三个数[1,2,3]的全排列,如果第一位先排1,那么第二位只能是2或3,如果第二位是2,那么第三位只能是3......

 回溯算法(基础)_第1张图片

  • 只要从根遍历这棵树,记录路径上的数字,其实就是所有的全排列。不妨把这棵树称为回溯算法的「决策树」,为啥说这是决策树呢?因为在每个节点上其实都在做决策

回溯算法(基础)_第2张图片

  • 比如现在在蓝色节点,可以选择2,也可以选择3 
  • 同时每做一次选择,都展开一棵空间树 
  • 选择完后,如果是重复选是路径,就做剪枝 

回溯算法(基础)_第3张图片

  • 可做的选择已走的路径作为树节点的两个属性

回溯算法(基础)_第4张图片

(二)回溯示意图  

回溯算法(基础)_第5张图片

(三)核心代码 

void BackTrack(vector nu, vector& p, vector>& 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
#include
#include
using namespace std;
bool judge(int x, vectorp)//判断p中有没有元素x
{
	int i = 0;
	while (i < p.size())
	{
		if (x == p[i])
			return true;
		else
			i++;
	}
	return false;
}
void BackTrack(vector nu, vector& p, vector>& 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> 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>result;//存放符合条件结果的集合
	vectorpath;//已走的路径
	vectornum(n);//存放元素
	cout << "依次输入各元素:";
	for (int i = 0; i < n; i++)
	{
		cin >> num[i];
	}
	BackTrack(num, path, result);
	cout << "全排列结果:" << endl;
	print(result);//遍历
}

三、组合问题

(一)问题   

  • 有任意n个数,返回所有可能的 k 个数的组合 

(二)示意图 

回溯算法(基础)_第6张图片

(三)核心代码 

void BackTrack(int start, int k, vector nu, vector& p, vector>& 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
#include
#include
using namespace std;
void BackTrack(int start, int k, vector nu, vector& p, vector>& 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> 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>result;//存放符合条件结果的集合
	vectorpath;//已走的路径
	vectornum(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

(五)剪枝 

  • 优化过程:
  1. 已经选择的元素个数:path.size()
  2. 还需要的元素个数为: k - path.size()
  3. 在集合中至多要从num.size() - (k-p.size())位置,开始遍历

回溯算法(基础)_第7张图片

#include
#include
#include
using namespace std;
void BackTrack(int start, int k, vector nu, vector& p, vector>& 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> 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>result;//存放符合条件结果的集合
	vectorpath;//已走的路径
	vectornum(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(路径,选择列表);//递归
		回溯,撤销处理结果
	}
}

你可能感兴趣的:(算法,算法)