0.回溯法的算法框架
A.简介
回溯法,又称试探法。一般需要遍历解空间,时间复杂度概况:子集树Ω(2^n),排序树Ω(n!),暴力法
B.回溯法解题三步骤
1)定义问题的解空间
如0-1背包问题,当n=3时,解空间是(0,0,0)、(0,0,1)、(0,1,0)、(0,1,1)、(1,0,0)、(1,0,1)、(1,1,0)、(1,1,1)。1代表选择该物品,0代表不选择该物品
C.子集树、排列树及其他2)确定易搜索的解空间结构
3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
1)子集树
a.概念
b.回溯法搜索子集树的一般算法当所给问题是从n个元素的集合S中找出S满足的某种性质的子集时,相应的解空间树称为子集树。例如,0-1背包问题,要求在n个物品的集合S中,选出几个物品,使物品在背包容积C的限制下,总价值最大(即集合S的满足条件<容积C下价值最大>的某个子集)。
另:子集树是从集合S中选出符合限定条件的子集,故每个集合元素只需判断是否(0,1)入选,因此解空间应是一颗满二叉树
void backtrack(int t)//t是当前层数 { if(t>n)//需要判断每一个元素是否加入子集,所以必须达到叶节点,才可以输出 { output(x); } else { for(int i=0;i<=1;i++)//子集树是从集合S中,选出符合限定条件的子集,故每个元素判断是(1)否(0)选入即可(二叉树),因此i定义域为{0,1} { x[t]=i;//x[]表示是否加入点集,1表示是,0表示否 if(constraint(t)&&bound(t))//constraint(t)和bound(t)分别是约束条件和限定函数 { backtrack(t+1); } } } }
2)排列树
a.概念
b.回溯法搜素排列树的一般算法当问题是确定n个元素满足某种性质的排列时,相应的解空间称为排列树。排列树与子集树最大的区别在于,排列树的解包括整个集合S的元素,而子集树的解则只包括符合条件的集合S的子集。
void backtrack(int t)//t是当前层数 { if(t>n)//n是限定最大层数 { output(x); } else { for(int i=t;i<=n;i++)//排列树的节点所含的孩子个数是递减的,第0层节点含num-0个孩子,第1层节点含num-1个孩子,第二层节点含num-2个孩子···第num层节点为叶节点,不含孩子。即第x层的节点含num-x个孩子,因此第t层的i,它的起点为t层数,终点为num,第t层(根节点为空节点,除外),有num-t+1个亲兄弟,需要轮num-t+1回 { swap(x[t],x[i]);//与第i个兄弟交换位置,排列树一条路径上是没有重复节点的,是集合S全员元素的一个排列,故与兄弟交换位置后就是一个新的排列 if(constraint(t)&&bound(t))//constraint(t)和bound(t)分别是约束条件和限定函数 { backtrack(t+1); } swap(x[i],x[t]); } } }
3)解空间非子集树,非排列树
递归:
void backtrack(int t) { if(t>n) { output(x); } else { for(int i=f(n,t);i<=g(n,t);i++) { x[t]=h(i); if(constraint(t)&&bound(t)) { backtrack(t+1); } } } }
迭代:void backtrack(int t) { int t=1; while(t>0) { if(f(n,t)<=g(n,t)) { for(int i=f(n,t);i<=g(n,t);i++)//f和g是当前扩展节点的起止点 { x[t]=h(i); if(constraint(t)&&bound(t)) { if(solution(t))//原先的递归出口 { output(x); } else { t++;//未解决问题,但在限定条件之内,走下一层 } } } } else { t--;//回到上一层 } } }
D.方法
先判断问题是子集树、排列树、还是非子集树非排列树,然后在做
1.装载问题
A.递归
根据节1给出的回溯模板,模仿所得,子集树
在编写时未考虑最优解问题(C1上装载越多,解越优),故本程序无法剪枝。若要考虑最优解,当总载重量小于最优载重量时,可以把右子树(0,不选择)全部剪去
#include
#include #include using namespace std; int c1,c2,wsum; vector w; vector s;//是否加入点集C1 int random(int start,int end) { return start+rand()%(end-start); } int s1=0; void maxLoading(int c1w,int t) { if(t>=w.size()) { if((wsum-c1w)<=c2) { cout<<"choice "<<++s1<<":\n\tc1 :"; for(int i=0;i >num>>c1>>c2; wsum=0; for(int i=0;i 课本中和上述其实是一样的,一个用for,i=0,i=1来访问左右子树,弃用for,直接上
B.迭代void maxLoading(int c1w,int t) { if(t>=w.size()) { if((wsum-c1w)<=c2) { cout<<"choice "<<++s1<<":\n\tc1 :"; for(int i=0;i
其实就是二叉树遍历的非递归版本(直接爬的代码)
template
Type MaxLoading(Type w[],Type c,int n,int bestx[]) { //迭代回溯法,返回最优装载量及其相应解,初始化根节点 int i =1; int *x = new int[n+1]; Type bestw = 0, cw = 0, r = 0; for(int j=1;j<=n;j++) r+=w[j]; while(true) { while(i<=n && cw+w[i]<=c) { r -= w[i]; cw +=w[i]; x[i] =1; i++; } if(i>n) { for(int j=1;j<=n;j++) bestx[j] = x[j]; bestw = cw; } else { r -= w[i]; x[i] = 0; i++; } while(cw+w[i] <= bestw) { i--; while(i>0 && !x[i]) { r+=w[i]; i--; } if(i == 0) { delete[] x; return bestw; } x[i] =0; cw -= w[i]; i++; } } }
2.批处理作业调度
排列树
#include
#include #include #include #include using namespace std; struct Node { int tm1; int tm2; }; vector x;//当前路径 vector bestx;//最优路径 vector f2;//机器2完成处理时间 vector v;//每个作业需要的处理时间 int bestT; int f;//完成时间和 int f1;//机器1完成处理时间 int num;//作业数 int random(int start,int end) { return start+rand()%(end-start); } void flowShop(int t)//t>=1 { if(t>num) { for(int i=1;i<=num;i++) { bestx[i]=x[i]; } bestT=f; } else { for(int i=t;i<=num;i++) { f1+=v[x[i]].tm1; f2[t]=((f2[t-1]>f1)?f2[t-1]:f1)+v[x[i]].tm2; f+=f2[t]; swap(x[t],x[i]); if(f =bestT的分支 { flowShop(t+1); } swap(x[i],x[t]); f1-=v[x[i]].tm1; f-=f2[t]; } } } int main() { cin>>num; for(int i=0;i<=num;i++) { Node t; t.tm1=random(1,10); t.tm2=random(1,10); v.push_back(t); x.push_back(i);//初始化路径为作业输入顺序 f2.push_back(0); bestx.push_back(-1); } f=0; f1=0; bestT=INT_MAX; flowShop(1); for(int i=1;i<=num;i++) { cout<
3.符号三角形问题
行一的每个元素都具有两种选择(0,+,1,-),行n的符号由行n-1来决定。
子集树,未剪枝
#include
#include using namespace std; vector v; int num; int countSame=0; bool isEqual() { int count0=0; int k=num; for(int j=num;j>0;j--)//确定符号 { for(int i=k;i =num) { if(isEqual())//相等加1 { countSame++; } } else { for(int i=0;i<=1;i++)//行一num个元素,均有两种选择 { v[t]=i; Triangels(t+1); } } } int main() { cin>>num; for(int i=0;i<(num+1)*num/2;i++) { v.push_back(-1); } Triangels(0); cout<
4.n后问题
n后问题,解空间非子集树,非排列树(一直往这方向上靠,做了上面3个题,有些先入为主了)
每行皇后只有一位,用v[ i ] = j来表示皇后在( i , j ),确实没有想到(一直考虑用二维数组,或者一维数组模拟二维数组)
用斜率来判断是否在一条斜线上,这个也没想到
A.递归
#include
#include #include using namespace std; vector v;//v[i]=j,表示第i行,第j列是皇后 int num;//棋盘行长,列长,也是皇后个数 int sum;//可行方案个数 bool isOk(int t) { for(int j=1;j num) { sum++; } else { for(int i=1;i<=num;i++) { v[t]=i;//第t行的皇后放在第i列 if(isOk(t)) { nQueen(t+1); } } } } int main() { sum=0; cin>>num; for(int i=0;i<=num;i++) { v.push_back(0); } nQueen(1); cout< B.迭代
#include
#include #include using namespace std; vector v;//v[i]=j,表示第i行,第j列是皇后 int num;//棋盘行长,列长,也是皇后个数 int sum;//可行方案个数 bool isOk(int t) { for(int j=1;j 0) { v[t]+=1;//第t行,第v[t]+1列 while(!isOk(t)&&v[t]<=num)//列范围内,寻找符合条件的v[t]=j { v[t]+=1; } if(v[t]<=num)//找到 { if(t==num) { sum++;//找到后,下个循环v[t]会大于num,有t--处理 } else { t++; v[t]=0; } } else//未找到 { t--; } } } int main() { sum=0; cin>>num; for(int i=0;i<=num;i++) { v.push_back(0); } nQueen(1); cout<
5.0-1背包问题
子集树
#include
#include #include using namespace std; struct Node { int w; int p; }; vector v;//物品 vector x;//当前方案 vector bestx;//存储最佳方案 int num;//物品数 int c;//背包容量 int maxP;//最大价值 int random(int start,int end) { return start+rand()%(end-start); } void storage(int cp) { if(cp>maxP) { maxP=cp; for(int i=0;i =num) { return; } else { if(cw<=c) { for(int i=0;i<=1;i++) { x[t]=i; if(i==1) { cw+=v[t].w; cp+=v[t].p; } if(cw<=c) { storage(cp); knapsack(cw,t+1,cp); } } } } } int main() { maxP=-1; cin>>num>>c; for(int i=0;i
6.最大团问题
子集树
#include
#include using namespace std; vector< vector > v; vector x; vector bestx; int maxv;//最优解顶点个数 int num; void storage(int nowv) { if(nowv>maxv) { maxv=nowv; for(int i=0;i =num) { storage(nowv); } else { for(int i=0;i<=1;i++) { x[t]=i; if(i==0) { MaxClique(t+1,nowv); } else if(i==1&&judge(t)) { MaxClique(t+1,nowv+1); } } } } int main() { maxv=-1; cin>>num; //初始化全无边 for(int i=0;i temp; for(int j=0;j >x1>>y1) { v[x1][y1]=1; v[y1][x1]=1; } else { break; } } MaxClique(0,0); cout<
7.图的m着色问题
子集树,解空间子集树,不是非要往0-1二叉树上靠,也可以是m叉树
#include
#include using namespace std; vector< vector > v; vector x; int num; int m; int sum; bool judge(int t,int color) { for(int i=0;i =num) { cout< >num>>m; for(int i=0;i temp; for(int j=0;j >x1>>y1) { v[x1][y1]=1; v[y1][x1]=1; } else { break; } } coloring(0); cout<
8.旅行售货员问题
排列树
#include
#include #include #include #include using namespace std; vector< vector > v; vector x; int num; int costbest; int random(int s,int e) { return s+rand()%(e-s); } int countDis() { int cost=0; for(int i=1;i =num) { int costx=countDis(); if(costx >num; for(int i=0;i temp; for(int j=0;j
9.圆排列问题
排列树
#include
#include #include #include #include using namespace std; vector x; vector r; int num; double disbest; double countDis(int a,int b) { return sqrt(pow(a+b+0.0,2)-pow(a-b+0.0,2)); } void circle(int t,double discost) { if(t>=num) { if(discost >num; for(int i=0;i >temp; r.push_back(temp); x.push_back(i); } for(int i=0;i