基本思想:
動態規划算法通常用於求解具有某種最優性質的問題。在這類問題中,可能會有許多可行解。每一個解都對應於一個值,我們希望找到具有最優值的解。動態規划算法與分治法類似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,然后從這些子問題的解得到原問題的解。與分治法不同的是,適合於用動態規划求解的問題,經分解得到子問題往往不是互相獨立的(即下一個子階段的求解是建立在上一個子階段的解的基礎上,進行進一步的求解)。若用分治法來解這類問題,則分解得到的子問題數目太多,有些子問題被重復計算了很多次。如果我們能夠保存已解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重復計算,節省時間。我們可以用一個表來記錄所有已解的子問題的答案。不管該子問題以后是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規划法的基本思路。具體的動態規划算法多種多樣,但它們具有相同的填表格式。
應用場景:
適用動態規划的問題必須滿足最優化原理、無后效性和重疊性。
1、最優化原理(最優子結構性質) 最優化原理可這樣闡述:一個最優化策略具有這樣的性質,不論過去狀態和決策如何,對前面的決策所形成的狀態而言,余下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略總是最優的。一個問題滿足最優化原理又稱其具有最優子結構性質。
2、無后效性 將各階段按照一定的次序排列好之后,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。換句話說,每個狀態都是過去歷史的一個完整總結。這就是無后向性,又稱為無后效性。
3、子問題的重疊性 動態規划將原來具有指數級時間復雜度的搜索算法改進成了具有多項式時間復雜度的算法。其中的關鍵在於解決冗余,這是動態規划算法的根本目的。動態規划實質上是一種以空間換時間的技術,它在實現的過程中,不得不存儲產生過程中的各種狀態,所以它的空間復雜度要大於其它的算法。
下面是一個關於 0-1背包問題 的動態規划思想PPT截圖:
問題描述:
給定n種物品和一背包。物品i的重量是wi,其價值為vi,背包的容量為C。問應如何選擇裝入背包的物品,使得裝入背包中物品的總價值最大?
對於一種物品,要么裝入背包,要么不裝。所以對於一種物品的裝入狀態可以取0和1.我們設物品i的裝入狀態為xi,xi∈ (0,1),此問題稱為0-1背包問題。
數據:物品個數n=5,物品重量w[n]={0,2,2,6,5,4},物品價值V[n]={0,6,3,5,4,6},
(第0位,置為0,不參與計算,只是便於與后面的下標進行統一,無特別用處,也可不這么處理。)總重量c=10。背包的最大容量為10,那么在設置數組m大小時,可以設行列值為6和11,那么,對於m(i,j)就表示可選物品為i…n,背包容量為j(總重量)時背包中所放物品的最大價值。
最優值分析過程如下:
當背包為空時,首先分析將物品n放入背包,即在總重量分別為0到10時,如何放置物品n使總價值最大。
對於m[5][j],當j<w[5]時,物品5不能放入背包中,此時背包的價值為0。當j>=w[5]時,物品5可以放入背包,此時背包的價值為v[5]。得到結果如下表:
在物品5的基礎上分析物品4,
當j<w[4]時,物品4不能放入,此時背包的最大價值為m[4+1][j];即m[4][0..4]=m[5][0..4]
當j>=w[4]時,物品4要么放入要么不放入。當物品4放入背包后,對於物品4+1到n,能達到的最大價值為m[4+1][j-w[4]]+v[4],故此時能達到的最大價值為m[4+1][j-w[4]]+v[4]
當物品4不放入背包時,能達到的最大價值為m[4+1][j]。最后比較放入與不放入情況下,兩者的最大值取其大者,分析結果如下:
由前面分析過程得m[i][j]的遞歸過程如下:
最終得到如下結果:
構造最優解
最優解的構造可根據C列的數據來構造最優解,構造時從第一個物品開始。從i=1,j=c即m[1][c]開始。
1、對於m[i][j],如果m[i][j]==m[i+1][j],則物品i沒有裝入背包,否則物品i裝入背包;
2、為了確定后繼即物品i+1,應該尋找新的j值作為參照。如果物品i已放入背包,則j=j-w[i];如果物品i未放入背包,則j=j。
3、重復上述兩步判斷后續物品i到物品n-1是否放入背包。
4、對於物品n,直接通過m[n][j]是否為0來判斷物品n是否放入背包。
#include<iostream> #include<stack> #include<vector> using namespace std; stack<int> KnapSack(int c,vector<int> w,vector<int> v,int &max_m) { vector<vector <int> > m(w.size(),vector<int>(c+1)); stack<int> res; int i,j; max_m=0; for(j=0;j<c+1;j++) //對於m[n][j], if(j<w[w.size()-1]) m[w.size()-1][j]=0; //當j<w[n]時,物品n不能放入背包中,此時背包的價值為0。 else m[w.size()-1][j]=v[v.size()-1]; //當j>=w[n]時,物品n可以放入背包,此時背包的價值為v[n] for(i=w.size()-2;i>=0;i--) //對於m[i][j], { for(j=0;j<c+1;j++) if(j<w[i]) //當j<w[i]時,物品i不能放入背包中,此時背包的價值為m[i+1][j]。 m[i][j]=m[i+1][j]; else //當j>=w[i]時,物品n可以放入背包 { int m1=m[i+1][j]; //當物品i不放入背包時,能達到的最大價值為m[i+1][j] int m2=m[i+1][j-w[i]]+v[i]; //當物品i放入背包后,對於物品i+1到n,能達到的最大價值為m[i+1][j-w[i]]+v[i] m[i][j]=m1>m2?m1:m2; //兩者取其大者 } } /* cout << "最優值矩陣:"<<endl; for(i=0;i<w.size();i++) { for(j=0;j<c+1;j++) cout<<m[i][j]<<" "; cout <<endl; } cout <<endl; */ j=c; for(i=0;i<w.size()-1;i++) { if(m[i][j]!=m[i+1][j]) { res.push(i+1); max_m+=v[i]; j=j-w[i]; } } if(m[w.size()-1][j]!=0) { res.push(w.size()); max_m+=v[w.size()-1]; } return res; } int main() { vector<int> weight; vector<int> value; stack<int> result; int max_weight; int tmp; int result_m=0; cout<< "輸入背包最大容量"<<endl; cin >> max_weight; cout <<"輸入物品重量,以0結束"<<endl; while(1) { cin>>tmp; if(tmp!=0) weight.push_back(tmp); else break; } cout <<"物品重量: "<<endl; for(int i=0;i<weight.size();i++) cout <<weight[i]<<" "; cout << endl; cout <<"輸入物品權重,以0結束"<<endl; while(1) { cin>>tmp; if(tmp!=0) value.push_back(tmp); else break; } cout <<"物品權重: "<<endl; for(int i=0;i<value.size();i++) cout <<value[i]<<" "; cout << endl; result=KnapSack(max_weight,weight,value,result_m); cout <<"放入背包的物品為:"<<endl; while(!result.empty()) { cout <<result.top()<<" "; result.pop(); } cout <<endl; cout<<"背包最大價值為:"<<result_m<<endl; return 0; }