*对回溯法不是很理解的请移步博客http://blog.csdn.net/sm9sun/article/details/53244484
掌握了回溯法以后,我们给出两种定义:
当所给问题是从n个元素的集合S中找出满足某种性质的子集时,解空间为子集树。例如:0-1背包问题
当所给问题是从n个元素的集合S中找出满足某种性质的排列时,解空间为排列树。例如:旅行售货员问题
代码:
子集树
void backtrack (int t){if (t>n) output(x);elsefor (int i=0;i<=1;i++) {x[t]=i;if (legal(t)) backtrack(t+1);}}
排列树
void backtrack (int t){if (t>n) output(x);elsefor (int i=t;i<=n;i++) {swap(x[t], x[i]);if (legal(t)) backtrack(t+1);swap(x[t], x[i]);}}
题目描述:
有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且,装载问题要求确定是否有一个合理的装载方案可将这些集装箱装上这2艘轮船。如果有,找出一种装载方案。
例如:当n=3,c1=c2=50,且w=[10,40,40]时,则可以将集装箱1和2装到第一艘轮船上,而将集装箱3装到第二艘轮船上;如果w=[20,40,40],则无法将这3个集装箱都装上轮船。
解题思路: 容易证明,如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
(1)首先将第一艘轮船尽可能装满;
(2)将剩余的集装箱装上第二艘轮船。
将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近C1。由此可知,装载问题等价于以下特殊的0-1背包问题。
/**************************************************************** *问 题:装载问题 *算 法:回溯法 *描 述:解空间为 子集树 ****************************************************************/#include<stdio.h>int x[1001],w[1001];int cw,n,c,r,bestw;void backtrack(int i){if (i>n){bestw=cw;return;}r-=w[i];if (cw+w[i]<=c){x[i]=1;cw+=w[i];backtrack(i+1);cw-=w[i]; }if (cw+r>bestw) {x[i]=0;backtrack(i+1); }r+=w[i];}int main(){ int i;while(~scanf("%d",&n)){for(i=1;i<=n;i++){scanf("%d",&w[i]);x[i]=0;r+=w[i];}scanf("%d",&c);cw=bestw=0;backtrack(1);printf("%d\n",bestw);}}
题目描述:
某售货员要到若干城市去推销商品,已知各城市之间的路程(旅费),他要选定一条从驻地出发,经过每个城市一遍,最后回到驻地的路线,使总的路程(总旅费)最小。
解题思路:
旅行售货员问题的解空间是一颗排序树,对于排序树的回溯法搜索与生成1,2,3,4,...,n的所有排列的递归算法Perm类似,开始时,x = [1,2,...,n],则相应的排序树由x[1:n]的所有排序构成。以下解释排序算法Perm:
假设Perm(1)的含义是对x = [1,2,...,n]进行排序,则Perm(i)的含义是对[i,i+1,i+2,...,n]进行排序。为了方便描述,规定一个操作P(j),表示在数组x中,第j个与第一个交换,于是得到递归关系式: Perm(i) = P(i)Perm(i+1) + P(i+1)Perm(i+1) + ...+P(n)Perm(i+1),旅行售货员的回溯法Backtrack在Perm的基础上使用了剪枝函数,将无效的全排列和有效的全排列非最优的次序统统都舍去。
/**************************************************************** *问 题:旅行售货员 *算 法:回溯法 *描 述:解空间为 排列树 ****************************************************************/#include <stdio.h>int map[11][11]; //保存图信息int x[11]; //x[i]保存第i步遍历的城市int isIn[11]; //保存 城市i是否已经加入路径int bestw; //最优路径总权值int cw; //当前路径总权值int bestx[11]; //最优路径int n;void Travel_Backtrack(int t){ //递归法 int i,j; if(t>n){ //走完了,输出结果 for(i=1;i<=n;i++) //输出当前的路径 printf("%d ",x[i]); printf("/n"); if(cw < bestw){ //判断当前路径是否是更优解 for (i=1;i<=n;i++){ bestx[i] = x[i]; } bestw = cw; } return; } else{ for(j=1;j<=n;j++){ //找到第t步能走的城市 if(map[x[t-1]][j] != -1 && !isIn[j]){ //能到而且没有加入到路径中 isIn[j] = 1; x[t] = j; cw += map[x[t-1]][j]; Travel_Backtrack(t+1); isIn[j] = 0; x[t] = 0; cw -= map[x[t-1]][j]; } } }}int main(){ int i,j; while(~scanf("%d%d",&n)) { for(i=1;i<=n;i++) for(j=1;j<=n;j++) map[i][j]=-1; for(i=1;i<=n;i++) for(j=1;j<=n;j++) scanf("%d",map[i][j]); for (i=1;i<=n;i++){ x[i] = 0; //表示第i步还没有解 bestx[i] = 0; //还没有最优解 isIn[i] = 0; //表示第i个城市还没有加入到路径中 } x[1] = 1; //第一步 走城市1 isIn[1] = 1; //第一个城市 加入路径 bestw = 4000; cw = 0; Travel_Backtrack(2); //从第二步开始选择城市 printf("最优值为%d/n",bestw); printf("最优解为:/n"); for(i=1;i<=n;i++){ printf("%d ",bestx[i]); } printf("/n");}}