回溯算法的求解過程實質上是一個先序遍歷一棵"狀態樹"的過程,只是這棵樹不是遍歷前預先建立的,而是隱含在遍歷過程中。
冪集
即求一個集合的所有子集。比如對於集合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結束遞歸來完成的。