回溯算法的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中。
幂集
即求一个集合的所有子集。比如对于集合A={1,2,3},则A的幂集为p(A)={{1,2,3},{1,2},{1,3},{1},{2,3},{2},{3},Φ}
求幂集P(A)的元素的过程可看成是依次对集合A中元素进行“取”或“舍”的过程,并且可以用一棵状态树来表示。求幂集元素的过程即为先序遍历这棵状态树的过程。
每个节点都是一个一维数组。
这个问题中不存在剪枝,所有状态都是合法的。
#include<vector> #include<iostream> #include<queue> using namespace std; template<typename Printable> void PowerSet(vector<Printable> &vec,queue<Printable> que){ if(que.empty()){ for(int i=0;i<vec.size();i++) cout<<vec[i]<<" "; cout<<endl; return; } Printable ele=que.front(); que.pop(); vector<Printable> newvec(vec); vec.push_back(ele); PowerSet(newvec,que); PowerSet(vec,que); } int main(){ string str="abcd"; queue<char> que; vector<char> vec; for(int i=0;i<str.size();i++) que.push(str[i]); PowerSet(vec,que); return 0; }
0-1背包问题
给定n种物品和一背包。物品i的重量是wi>0,其价值为vi>0,背包的容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
跟上面的问题类似,对于每件物品都有选和不选两可能。
利用回溯算法写还可以加入一些优化,进行剪枝,因为很多情况是没有意义的,例如当重量大于背包容量的时候,没有必要对剩下的物品再来决策了。或者将剩下的所有物品都选取其总价值也没有目前已经求得的方案的价值还大的话,也是可以剪枝的。
下面编程求一个具体的背包问题。
物品编号 | 大小 | 价值 |
1 | 2 | 1 |
2 | 3 | 4 |
3 | 4 | 3 |
4 | 5 | 6 |
5 | 6 | 8 |
#include<iostream> #include<vector> #include<utility> #include<queue> using namespace std; const int BAG_NUM=5; //物体的个数 const double CAPACITY=12; //背包的容量 double volume[BAG_NUM]={2,3,4,5,6}; //每个物体的体积 double price[BAG_NUM]={1,4,3,6,8}; //每个物体的价值 typedef pair<double,vector<int> > PACK; struct cmp{ bool operator() (const PACK &p1,const PACK &p2)const{ return p1.first<p2.first; } }; priority_queue<PACK,vector<PACK>,less<PACK> > maxQ; void grow(const PACK &pack){ vector<int> combin=pack.second; double value=pack.first; int len=combin.size(); if(len>=BAG_NUM) //物体已经遍历完了,返回 return; //如果加上剩下物体全部的价值,也超不过当前已经找到的最大价值,则剪枝 if(maxQ.size()>0){ PACK maxpack=maxQ.top(); double maxValue=maxpack.first; double currValue=value; for(int i=len;i<BAG_NUM;++i) currValue+=price[i]; if(currValue<maxValue) return; } vector<int> discard(combin); discard.push_back(0); PACK newpack=make_pair(value,discard); maxQ.push(newpack); grow(newpack); //如果放入当前物体重量超过总容量则剪枝 double totalweight=volume[len]; for(int i=0;i<len;++i){ if(combin[i]==1) totalweight+=volume[i]; } if(totalweight>CAPACITY) return; vector<int> take(combin); take.push_back(1); double newvalue=value+price[len]; PACK np=make_pair(newvalue,take); maxQ.push(np); grow(np); } int main(){ double v=0.0; vector<int> ve; PACK pack=make_pair(v,ve); grow(pack); PACK rect=maxQ.top(); cout<<"最大价值:"<<rect.first<<endl; cout<<"放入包中的物体:"; vector<int> vec=rect.second; for(int i=0;i<vec.size();++i){ if(vec[i]==1) cout<<i+1<<"\t"; } cout<<endl; return 0; }
最大价值:14
放入包中的物体:4 5
走迷宫
走迷宫可以用一个栈来解决。如果路径走得通就将其压入栈中,如果走不通就回退--对应出栈操作。最后栈里面存放的就是可靠路径。
每个节点都是一个数。
这里面仅仅使用一个“栈”很轻松地实现了“回溯”。
N皇后
在一个N×N的格子中放N个皇后,任意两个皇后都不能在同一行、同一列、同一斜线方向上。求所有可行的摆法。
每个节点都是一个二维数组。实际上可以压缩一个一维数组以节省空间。
#include<iostream> #include<vector> using namespace std; int numAnswer=0; void printTable(const vector<int> &); void queen(vector<int> vec,int rest){ if(rest==0){ cout<<"answer "<<++numAnswer<<endl; printTable(vec); } int num=vec.size()-rest+1; int i; for(i=0;i<vec.size();i++){ if(vec[i]<0){ bool b1=true,b2=true; for(int j=i-1;j>=0 && num-i+j>0;--j){ if(vec[j]==(num-i+j)){ b1=false; break; } } for(int j=i+1;j<vec.size() && num-j+i>0;++j){ if(vec[j]==(num-j+i)){ b2=false; break; } } if(b1 && b2){ vector<int> newvec(vec); newvec[i]=num; queen(newvec,rest-1); } } } } void printTable(const vector<int> &vec){ int len=vec.size(); for(int i=0;i<len;++i){ for(int j=0;j<len;++j){ if(vec[i]==j+1) cout<<1<<" "; else cout<<0<<" "; } cout<<endl; } } int main(){ int len; cout<<"Input the number of queens"<<endl; cin>>len; vector<int> vec(len,-1); queen(vec,vec.size()); return 0; }
代码中利用递归实现树的向下增长,“剪树”是通过让函数return结束递归来完成的。
本文来自博客园,作者:高性能golang,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/2676958.html