以深度优先的方式系统地搜索问题的解的方法称为回溯法。 可以系统地搜索一个问题的所有解或任意解。 有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。 回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。
if (pd(a[20],a[1])) print();} else search(t+1); b[i]=0; } } int main(){ search(1); cout<<total<<endl; //输出总方案数 } int print(){ total++; cout<<"<"<<total<<">"; for (int j=1;j<=20;j++) cout<<a[j]<<" “; cout<<endl; } bool pd(int x,int y){ int k=2,i=x+y; while (k<=sqrt(i)&&i%k!=0) k++; if (k>sqrt(i)) return 1; else return 0; } 例题二 【例2】设有n个整数的集合{1,2,…,n},从中取出任意r个数进行排列(r<n),试列出所有的排列。 #include #include #include using namespace std; int num=0,a[10001]={0},n,r; bool b[10001]={0}; int search(int); //回溯过程 int print(); //输出方案 int main(){ cout<<“input n,r:”; cin>>n>>r; search(1); cout<<“number=”<<num<<endl; //输出方案总数 } int search(int k){ int i; for (i=1;i<=n;i++) if (!b[i]) { //判断i是否可用 a[k]=i; //保存结果 b[i]=1; if (kr) print(); else search(k+1); b[i]=0; } } int print(){ num++; for (int i=1;i<=r;i++) cout<<setw(3)<<a[i]; cout<<endl; } 例题三 【例3】任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和 #include #include #include using namespace std; int a[10001]={1},n,total; int search(int s ,int t); // s 代表待拆分的数; 序列中t 代表拆分序列中的数的编号 int print(int); int main(){ cin>>n; search(n,1); //将要拆分的数n传递给s cout<<“total=”<<total<<endl; //输出拆分的方案数 } int search(int s,int t){ int i; for (i=a[t-1];i<=s;i++)//后面的加数不小于前面的加数 if (i<n) { //当前数i要大于等于前1位数,且不过n a[t]=i; //保存当前拆分的数i s-=i; //s减去数i, s的值将继续拆分 if (s==0) print(t); //当s=0时,拆分结束输出结果 else search(s,t+1); //当s>0时,继续递归 s+=i; //回溯:加上拆分的数,以便产生所有可能的拆分 } } int print(int t){ cout<<n<<”="; for (int i=1;i<=t-1;i++) //输出一种拆分方案 cout<<a[i]<<"+"; cout<<a[t]<<endl; total++; //方案数累加1 } 例题四 装载问题 #include using namespace std; class goods{ int weight; public: goods(int w=0):weight(w) {} int get_w(){ return weight; } void set(int w){ weight=w; }
}; //goods *g,集装箱列表 //int *best,待求解的最优装载方案 //int t,子集树数的层号。根节点在第0层,叶节点在第n层 //int n,集装箱的总数 //int &cw, 当前的轮船的荷载 //int bestcw ,当前的最大荷载 //int *x,满足当前最大荷载的装载方案 //int r剩余的集装箱重量和 void load(goods *g, int *x, int t, int n,int cw, int &bestcw ,int *best,int r,int c){ if(t>n) { //已经遍历的到叶子结点,得到了一个解决方案 if(cw>bestcw) { for(int i=0;i<n;i++) best[i]=x[i]; bestcw=cw; } } else{ //每个结点可以有两个分支,分别利用约束规则和限界规则进行剪枝 r=r-g[t].get_w();//剩余未处理的物品的重量和,与是否选取当前物品无关 if(cw+g[t].get_w()<=c){ // 根据题意中的约束条件进行剪枝 x[t]=1; cw=cw+g[t].get_w(); //当前装入的物品的重量和 load(g,x,t+1,n,cw,bestcw,best,r,c); cw=cw-g[t].get_w(); //回溯的需要 } if(cw+r>bestcw) { //限界规则 x[t]=0; load(g,x,t+1,n,cw,bestcw,best,r,c); } r=r+g[t].get_w(); //回溯的需要 } } int main(){ int n,c,bestcw=0; int x,best, r=0; cout<<“请输入物品的件数和轮船的装载重量:"; cin>>n>>c; goods g; g=new goods[n]; x=new int [n]; best=new int[n]; cout<<“请输入每件物品的重量:”; for(int i=0;i<n;i++) { int w; cin>>w; g[i].set(w);r=r+w; } load(g,x,0,n,0,bestcw,best,r,c); cout<<bestcw<<endl; for(i=0;i<n;i++) cout<<best[i]<<" “; cout<<endl; return 0; } 例题五 0-1背包问题 #define NUM 100 int c; //背包的容量 int n; //物品的数量 int cw; //当前重量 int cv; //当前价值 int bestv; //当前最优价值 //描述每个物品的数据结构 struct Object{ int w; //物品的重量 int v; //物品的价值 double d; //物品的单位重量价值比 }Q[NUM]; //物品的数组 对物品以单位重量价值比递减排序的因子是: bool cmp(Object a, Object b) { if(a.d>=b.d) return true; else return false; } 物品的单位重量价值比是在输入数据时计算的: for(int i=0; i<n; i++) { scanf(”%d%d",&Q[i].w,&Q[i].v); Q[i].d = 1.0Q[i].v/Q[i].w; } 使用C++标准模板库的排序函数sort()排序: sort(Q, Q+n, cmp); //形参i是回溯的深度,从0开始.商品编号从0开始编号 void backtrack(int i){ if (i+1>n) {bestv = cv; return;} //进入左子树搜索, 表示选择第i件物品 if (cw+Q[i].w<=c){ //约束条件 cw += Q[i].w; //选择第i件物品 ,导致相关的数据发生变化 cv += Q[i].v; //选择第i件物品 ,导致相关的数据发生变化 backtrack(i+1); cw -= Q[i].w; //回溯的需要,恢复数据 cv -= Q[i].v; //回溯的需要,恢复数据 } //进入右子树搜索,表示不选择第i件物品,相关的cw, cv不改变 if (Bound(i+1)>bestv) backtrack(i+1); } //形参i是回溯的深度 int Bound(int i){ int cleft = c-cw; //背包剩余的容量 int b = cv; //上界 //尽量装满背包 while (i<n && Q[i].w<=cleft){ cleft -= Q[i].w; b += Q[i].v; i++; } //剩余的部分空间也装满。0-1是可能装不满的。但此处主要计算最大值,所以,需要从装满的角度考虑该问题 if (i<n) b += 1.0cleftQ[i].v/Q[i].w; return b; } 例题六 着色问题 #define NUM 100 int n; //图的顶点数量 int m; //可用颜色数量 int a[NUM][NUM]; //图的邻接矩阵 int x[NUM]; //当前的解向量 int sum ; void BackTrack(int t ){ int i; //到达叶子结点,获得一个着色方案 if( t > n ) { sum ++ ; for(i=1; i<=n ;i++) printf("%d “,x[i]); printf(”\n"); } else //搜索当前扩展结点的m个孩子 for(i=1; i<=m; i++ ){//尝试每种颜色 x[t] = i; if( Same(t) ) //判断t 和其邻接点的着色方法是否相同 BackTrack(t+1); x[t] = 0;//回溯的需要,恢复到未着色的状态 } } //形参t是回溯的深度 bool Same(int t) { int i; for(i=1; i<=n; i++ ) //检查t与所有顶点的邻接关系,包括染色和未染色的。 if( (a[t][i] == 1) && (x[i] == x[t])) return false; return true; } 例题八 n皇后问题 void Backtrack(int t) {//形参t是回溯的深度,从1开始 int i; //到达叶子结点,获得一个可行方案。累计总数,并输出该方案 if (t>n) { sum++; //是全局变量 for (i=1; i<=n; i++) printf(" %d", x[i]); printf("\n"); } else for (i=1; i<=n; i++) { x[t] = i; if (Place(t)) Backtrack(t+1); } } //形参t是回溯的深度 inline bool Place(int t) { int i; for (i=1; i<t; i++) if ((abs(t-i) == abs(x[i]-x[t])) || (x[i] == x[t])) //同一条对角线;同一行 return false; return true; } 例题九 旅行商问题 #define NUM 100 int n; //图G的顶点数量 int m; //图G的边数 int a[NUM][NUM]; //图G的邻接矩阵 int x[NUM]; //当前解 int bestx[NUM]; //当前最优解向量 int cc; //当前费用 int bestc; //当前最优值 int NoEdge = -1; //形参t是回溯的深度,从2开始。根结点为第1层。城市编号从1开始.从第1个城市出发 void Backtrack(int t){ //到达叶子结点的父结点。旅行路径上的倒数第二个城市。最后一个城市是出发点 if(tn) { if(a[x[n-1]][x[n]]!= NoEdge && a[x[n]][1]!= NoEdge && (cc + a[x[n-1]][x[n]]+a[x[n]][1]<bestc||bestc NoEdge)) { for(int i=1; i<=n; i++) bestx[i] = x[i]; bestc = cc + a[x[n-1]][x[n]] + a[x[n]][1]; } return; } else { for(int i=t; i<=n; i++) { if(a[x[t-1]][x[i]]!= NoEdge && (cc + a[x[t-1]][x[i]]< bestc||bestc == NoEdge)) { swap(x[t],x[i]); // 先交换。此时,X[t]的值发变化。 cc += a[x[t-1]][x[t]]; //再计算。不能交换顺序。 Backtrack(t+1); // 如果要改变顺序,应该为cc += a[x[t-1]][x[i]]。 cc -= a[x[t-1]][x[t]]; swap(x[t],x[i]); } } } }