如何理解回溯的思想

小故事

        某一天,一位牧师在森林里散步,他一不小心脚滑了,嗯,是脚滑了=.=,掉到了一个洞穴里,当他醒来时,发现自己居然落进了一个地下迷宫(真够背的),而牧师所处的这个密室周围有三道门,还好,不是三百道,还有希望,于是,牧师就开始想,该怎么出去呢?又没什么线索,难道要靠运气吗?唉~,想起运气,牧师慢慢闭上了自己的双眼,回忆起自己昨天的经历——跟一群三岁小孩子玩五分钱一局的掷色子游戏,硬是输了400多块,而自己正是因为这件事,才到森林散步,可没想到...。唉,往事历历在目,牧师又深深叹了一口气,他深知自己的脸有多黑,知道好运从不眷顾他,但当下,得赶紧想办法,家人还等着他回家吃饭,必须得赶快出去,既然自己走不了捷径,那么就做一次莽夫,把所有的路给走一遍,就不信出不去,说干就干,他抓起一把石子,每个门口放一粒石子,走进了第一道门,捡起石子,进入到另一个密室,发现周围还是三道门,再放石子,走第一道门(捡石子),发现是个死胡同,然后回头走第二道(捡石子),还是个死胡同,第三道也是,于是他退回到前一个密室,走进了第二道门...就这样,牧师按顺序,一条路一条路的走,走过就把石子给捡起来,遇到死胡同就掉头,就这样,不到两个小时,牧师终是找到了出口。而这,就是回溯的思想。

下面是牧师自制的地图,GIF如果不动,请刷新一下

回溯算法该怎么写呢?注意以下几点。

        1、当前节点有那些可选“路径”: 就比如求一串数“1234”的全排列,先选第一个数,有四个数可选,对吧。

        2、回溯:也就是后退一步,防止你的这次搜索对旁系的节点产生影响,比如上面的全排列,如果你选了1,后面只能选234,也就是标记1已经被用过了,但如果你后来选了2开头,后面可选134,但1已经被你标记不能用了,所以,在“后退”的过程中,需要恢复之前的数据。

        3、结束判断:就是我们如果得到想要的结果,就不需要再进一步搜索了,比如上面的全排列,我们想要的结果是搜索到4个数字就行了(这四个数字满足条件1),就不要搜索第5个数字。

下面给出几道例题:

全排列的回溯算法

PS :看代码要养成一个习惯,从主函数看起,跟着主函数“运行”

#include<iostream>
using namespace std;
int result[1000];		//结果存放到result数组中。
int visited[1000];      //用于表示某个数是否被用了,如visited[1]==1,说明1被用了
void print(int k) {		//输出函数,输出结果
	for (int i = 1; i <= k; i++) {
		cout << result[i] << " ";
	}
	cout << endl;
}  
void search(int k) {		//k表示搜索深度,也就是全排列中的第几个数
	for (int i = 1; i <= 4; i++) {
		if (visited[i] == 0) {		//for嵌套if:用于判断每个位置可以用哪些数,比如第一个位置可用1234,若选了1,就把visited[1]置为1,下一层搜索。就只剩下234了
			visited[i] = 1;
			result[k] = i;
			if (k == 4)    //找到四个数,就调用输出函数
				print(k);
			
			else {       //否则就搜索下一个数
				search(k + 1);
			}
			visited[i] = 0;		//把i恢复为0,从整体来看,之前把i置1,不管递归调用了多少次,这里的i还是没变对吧,现在把i置0,就实现了一个后退的效果
		}
	}
}
int main() {
	int array[4]={1,2,3,4};   //求这四个数字的全排列。
	search(1);				//从第一个数开始找
	return 0;
}

 

整数的分划问题: 

整数的分划问题。 
如,对于正整数n=6,可以分划为: 

6
5+1 
4+2
4+1+1 
3+3
3+2+1
3+1+1+1 
2+2+2
2+2+1+1
2+1+1+1+1 
1+1+1+1+1+1+1 

对于给定的正整数n,编写算法打印所有划分

#include<iostream>
using namespace std;
int n, result[1000];
void print(int k) {
	for (int i = 1; i <= k; i++)
		cout << result[i] << " ";
	cout << endl;
}
void search(int k) {
	for (int i = result[k - 1]; i > 0; i--) {  //for确定可选数,每次的数都不会比前一个数大
		if (n >= 0) {     //保证n被划分后不是负数
			result[k] = i;
			n -= i;       //除去被分划的数
			if (n == 0)   //当这个数被划分为0
				print(k);
			else
				search(k + 1);
			n += i;		//回溯一步,恢复		
		}
	}
}
int main() {
	cin >> n;
	result[0] = n;  //初始化第一个位置,方便迭代
	search(1);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值