**回溯搜索法-**纯暴力搜索,不是一种高效的算法。但因为有些问题能够通过回溯法解决已经很棒了,回溯法比那种通过多层循环的效率还是要高很多的。
回溯法均可抽象为一个N叉树形结构,这个树的宽度是这个集合所有的元素,深度为递归的深度。如下图:
回溯三步曲:
1.递归函数参数和返回值
2.确定终止条件
3.单层递归逻辑
回溯法模板:
void backtracking(参数值)//回溯法参数不容易确定,一般先写逻辑,需要什么参数,就写什么参数。
{
if(终止条件)://有递归就一定有终止条件
收集结果;
return;
for(集合元素)
{
处理节点;
递归过程;//树形结构往下分叉
回溯过程;//即撤销操作,递归后面往往是回溯
}
}
回溯法解决问题类型
(1)组合类问题
力扣77题:
1.参数与返回值的确定
二维数组 result
一维数组 path
void backtracking(n,k,start_index)
{
}
2.终止条件的确定
if(path.size()==k):
{
result.push(path);
return;
}
3.单层逻辑问题
for (i=start_index;i<=n;i++)
{
path.push(i)
backtracking(n,k,i+1);//递归
path.pop()//回溯
}
回溯常常伴随着剪枝操作,剪枝操作指的是对于一些不可能满足要求的子孩子,则不需要对其进行递归操作,直接在单层逻辑问题上就可以对其进行剪枝操作。
这一题的剪枝操作可以如下:
假设有n个数,需要寻找k个数的集合,而此时的path中数目为path.size,因此i至多从(n-(k-path.size)+1)处选取。这个值的得到是根据假设这个至多值为m,这个m到n后面的所有值的大小是等于(n-m+1)=(k-path.size)
因此可以推得m=n-(k-path.size)+1。即代码修改为
for (i=start_index;i<=n-(k-path.size)+1;i++)
{
path.push(i)
backtracking(n,k,i+1);//递归
path.pop()//回溯
}
(2)切割问题
例:字符串切割问题:寻找回文子串
(3)子集问题
(4)排列问题
(5)棋盘问题