小故事
某一天,一位牧师在森林里散步,他一不小心脚滑了,嗯,是脚滑了=.=,掉到了一个洞穴里,当他醒来时,发现自己居然落进了一个地下迷宫(真够背的),而牧师所处的这个密室周围有三道门,还好,不是三百道,还有希望,于是,牧师就开始想,该怎么出去呢?又没什么线索,难道要靠运气吗?唉~,想起运气,牧师慢慢闭上了自己的双眼,回忆起自己昨天的经历——跟一群三岁小孩子玩五分钱一局的掷色子游戏,硬是输了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;
}