回溯算法


回溯算法 是一种穷举所有可能性的算法,也就是列举一棵决策树的所有路径

再说题目前,先说说选择层数和选择列表
如上所说,列举一棵树的所有路径,这里用[ a, b, c]的全排列来举个例子

在这里插入图片描述
蓝色的就是当前选择列表,红色的就是选择层数,一开始,选择列表为[a, b, c]所以我们有三种选择

而我们用递归实现回溯时,通常通过递归的调用进入下一层,而在回溯的函数里用for来列举当前选择列表

一般来说,使用回溯算法的题目可以分三种

三种在写法还是略有不同的,主要在结束条件上和for的初值等

顺便说一句,三种题目的剪枝条件都需要先对原序列进行排序才能正常进行

一:排列

排列的思路比较好理解

结束条件
一般都是路径到底部了,长度够了,就可以填入结果了

if(buf.size() == nums.size()) 
{
	.......//填入结果
	return ;
}

for的初值
都是从0开始,因为不能抛弃前面的点,比如我们从2开始[1,2,3]的排列,我们不能抛弃前面的1是吧?,不然就构不成一个完整的排列了,所以每一次选择列表都从0开始,但是在一个排列里我们也不希望出现aaa这种情况吧,所以在一条路径里,我们用一个数组来标记当前值是否被选中过,然后再for里判断,如果是选过的,就跳过
然后在走完一条路径后,撤销这些标记,不然下一条路径就不正常了

for(int i = 0;i<nums.size();++i)
{
	if(flag[i]==true)continue;//如果被选过了就跳过
	buf.push_back(nums[i]);//做出选择
	flag[i] = true;//标记
	................//递归调用
	buf.pop_back();//撤销选择
	flag[i] = fasle;//取消标记
}

剪枝
需要剪枝是因为在带排列的元素中可能出现重复元素,一旦出现了重复元素,在选择列表里就会有重复的待选项,而这些同一个选择列表里重复的项最后走完的路径是一样的,所以我们要排除掉重复的路径,选择列表是靠for枚举的,所以要在for里面,递归调用前判断是否是重复的
很简单

if(nums[i] == nums[i-1])continue;

但是别忘了i是从0开始的,所以i-1可能会越界,所以得加上一个i>0的条件

if(i > 0 && nums[i] == nums[i-1])continue;

但是这两个条件还是不够,还得加上一个flag[i-1] == fasle;

if(i > 0 && nums[i] == nums[i-1] && flag[i-1]==false)continue;

再结合之前的标记条件:

if(flag[i] == true ||(i > 0 && nums[i] == nums[i-1] && flag[i-1]==false))continue;


二:组合

结束条件
组合无顺序,但是数量确定了,比如在[1,2,3]中找出所有长度为2的组合,所以结束条件也是长度

if(buf.size() == n)
{
	......//填入结果
	return;
}

for的初值
在这里插入图片描述

因为组合无顺序要求,所以1,2,3和3,1,2没区别,所以我们不需要前面的数字,比如找[1,2,3]的长度为2的组合我们一开始肯定是找出[1,2]、[1,3]但是从2开始时,我们可能会得到[2,1]和[2,3],明显,[2,1]和[1,2]重复了,不是我们想要的 结果,那怎么办呢,很简单,在后续的选择列表里抛弃‘1’就行了呗,于是我们就从for的初值上下手,不要让1出现就好了,for就不能从0开始了,必须从上一层之后开始

排列里我们只跳过当前数字,因为已经选过了,而在组合里我们要跳过前面的所有数字,因为会重复
于是我们在回溯函数的参数里加上一个数来控制for的初值

void backtrack(.....其他参数,int idx)
{
	//.....结束条件,填入结果
	
	for(int i = idx;i<nums.size();++i)//控制初值
	{
		//.....做出选择
		backtrack(...其他参数,i+1);  //记住别传错了,是i+1,不是idx+1,不然就起不到去重的作用了
		//.....撤销选择
	}
}

剪枝:

如果待组合元素中有重复值,那我们还是需要剪枝,这里的剪枝和排列的剪枝写法很像,就是不需要判断最后的flag数组

	if(i > idx && nums[i] == nums[i-1]) continue;

这里注意,不再是i > 0,而是i > idx,意义也一样,排列里的i>0是为了防止越界(数组的界和选择列表的界),因为选择列表是从0开始的,这里的i>idx也是防止越界,虽然我们抛弃了前面的数字,但他们实际上还是存在的,而在进行比较时,很可能会把当前列表里的数字与前面已经抛弃的数字进行比较,这样就可能会出错,导致去重不完整


三:子集

子集总体来说和组合差不多,for的初值剪枝都一样

不一样的就是结束条件,因为子集,不需要考虑长度,所以直接填入就好了。


最后

以上的内容都是个模板而已,仅供参考,有些时候还得根据题意做出一些改变,但是大体思路没变,比如在二维数组得相关回溯题里,就可以用4个if来代替for进行选择列表的选择,对于边界值的限定也更方便,而在有些题目里,传for初值时,既不是0,也不是i+1,而是直接传i,因为当前值可以复用,等等,都是根据具体情况来的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timathy33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值