0-1背包問題:物品總數n,每個物品的體積w[i],價值v[i],給定背包的總容量W,求放入背包中物品的最大價值。
用回溯法對0-1背包問題進行求解,具體思路是:
1.使用解空間進行標記每個物品的放入情況,即要建立一個數組進行保存其是否放入,可使用 bool x[i]進行標識;
2.回溯法第一感覺上是窮舉所有情況,但事實上,有好多種情況可以進行避免,即若第t個物品放入后(即x[t]=1)已經超出背包重量,那么,在x[t]=1情況下的t+1—n個物品就不用再考慮,這樣可以節省好多時間,回溯法有區別與窮舉法;
3.對於解空間,用解空間數進行組織數據,解空間樹的深度就是問題的規模n;
4.在解空間樹中,我們用左子樹、右子樹分別標記1/0情況,即左子樹的邊代表放入,右子樹的邊代表不放入;
5.建立回溯函數是重中之重,回溯函數建立分三步:
1 void Backtrack(int t) 2 { 3 if(t > n){ //是否到達葉節點 4 for(int i = 1; i <= n; i++){ 5 best_x[i] = x[i]; //記錄回溯的最優情況 6 } 7 best_v = now_v; //記錄回溯中的最優價值 8 return; 9 } 10 if(now_w + w[t] <= W){ //約束條件,是否放入。放入考慮左子樹,否則考慮右子樹 11 x[t] = 1; 12 now_w += w[t]; 13 now_v += v[t]; 14 Backtrack(t+1); //進行下一個節點的分析 15 now_w -= w[t]; //在到達葉節點后進行回溯 16 now_v -= v[t]; 17 } 18 if(Bound(t+1) > best_v){ //限界條件,是否剪枝。若放入t后不滿足約束條件則進行到此處,然后判斷若當前價值加剩余價值都達不到最優,則沒必要進行下去 19 x[t] = 0; 20 Backtrack(t+1); 21 } 22 23 }
(1)是否已經搜索到了葉節點,若已經到了葉節點,此時該分支對應的價值情況與物品分配情況已經得知,為此進行保存后返回,然后進行回溯;
(2)若沒有搜索到葉節點,那么需要考慮對應物品是否可以放入背包(涉及到問題的約束條件),若可以(在樹的左子樹進行操作),對當前價值、當前容量進行更新,x[t]=1進行標記已經放入背包。接着進行下一個物品的分析調用該回溯函數,若是回溯函數返回,則表明已經進行到葉節點,所有情況均考慮完成,那么回溯正式開始。當前容量減去當前物品容量,當前價值減去當前物品價值,則表示當前物品沒有放入背包的情況,后續執行第(3)步。
(3)若對應的物品不能放入背包,繼續分析后面剩余物品的價值加上當前的價值是否會大於我們前面求得的最優價值,若后面剩余物品的價值加上當前的價值小於,則我們就沒必要在進行往下考慮。否則,我們需要繼續往下考慮,即將當前的物品x[t]=0進行標記,接着對下一個物品調用回溯函數。
上述遞歸函數有些繞,其實就是在沒有搜索到葉節點的情況下,判斷是否可以放入背包,若可以,放入,繼續往下進行,若不可以,則判斷剩余的全部放入背包是否比當前得到的最優值更好,若沒有當前最優值好,則pass。回溯也用到了遞歸,首先是距離根節點最遠的開始回溯,最遠的情況執行完畢返回到次遠分析,最后分析到根節點。

1 #include <iostream> 2 #define N 100 3 using namespace std; 4 int n; 5 double W; 6 double w[N]; 7 double v[N]; 8 bool x[N]; //用於記錄某次回溯情況 9 bool best_x[N]; //存儲最優回溯情況 10 double now_v; //當前價值 11 double remain_v; //剩余價值 12 double now_w; //當前容量 13 double best_v; //最優價值 14 15 double Bound(int k) 16 { 17 remain_v = 0; 18 while(k <= n){ 19 remain_v += v[k]; 20 k++; 21 } 22 return remain_v + now_v; 23 } 24 25 void Backtrack(int t) 26 { 27 if(t > n){ //是否到達葉節點 28 for(int i = 1; i <= n; i++){ 29 best_x[i] = x[i]; //記錄回溯的最優情況 30 } 31 best_v = now_v; //記錄回溯中的最優價值 32 return; 33 } 34 if(now_w + w[t] <= W){ //約束條件,是否放入。放入考慮左子樹,否則考慮右子樹 35 x[t] = 1; 36 now_w += w[t]; 37 now_v += v[t]; 38 Backtrack(t+1); //進行下一個節點的分析 39 now_w -= w[t]; //在到達葉節點后進行回溯 40 now_v -= v[t]; 41 } 42 if(Bound(t+1) > best_v){ //限界條件,是否剪枝。若放入t后不滿足約束條件則進行到此處,然后判斷若當前價值加剩余價值都達不到最優,則沒必要進行下去 43 x[t] = 0; 44 Backtrack(t+1); 45 } 46 47 } 48 49 void Knapsack(double W, int n) 50 { 51 double sum_w = 0; 52 double sum_v = 0; 53 best_v = 0; 54 for(int i = 0; i < n; i++){ 55 sum_w += w[i]; 56 sum_v += v[i]; 57 } 58 if(sum_w <= W){ 59 best_v = sum_v; 60 cout << "These goods could be put in the shopping car"; 61 cout << "The best value is:" << best_v <<endl; 62 return; 63 } 64 Backtrack(1); 65 cout << "The best value is:" << best_v << endl; 66 cout << "The condiction of these goods are:" << endl; 67 for(int i = 1; i <= n; i ++){ 68 cout << i << " "; 69 cout << best_x[i] << endl; //打印所有物品的放入情況,若為1表示放入,若為0則表示不放入 70 } 71 } 72 73 int main() 74 { 75 cout << "Please input the num of goods:"; 76 cin >> n; 77 cout << "Please input the capacity of the shopping car:"; 78 cin >> W; 79 for(int i = 1; i <= n; i++){ 80 cin >> w[i] >> v[i]; 81 } 82 Knapsack(W,n); 83 return 0; 84 }
注: 代碼來自陳小玉老師《趣學算法》
