用回溯法解決0-1背包問題需要解決一下問題:
1.如何動態產生子集樹
2.如何設計子集樹中的結點類型
3.如何設計兩個剪枝函數:約束函數和限界函數
4.如何儲存一個或多個最優解,同時儲存最優值
解決方案:
1.子集樹通過動態方式產生,子集樹中的結點類型共用物品類型,其中結點之間的父子關係通過遞迴調用的方式關聯,這種關係並不在類中設定變數顯示表示。
2.為了方便限界函數的計算和程式中的使用,先對物品預先處理,以物品的單位價值重量進行降序排序。
3.設定程式運行時一個構造最優解變數,該變數對應的最優值與當前最優解對於的最優值對比,如果優於當前最優解,則將覆蓋當前最優解;如果與當前最優解對應的值相等,則同時儲存兩個最優解。
以下是具體的原始碼:
#include "stdafx.h"#include <iostream>using namespace std;typedef int Typew;typedef int Typep;//物品類class Object{friend class Knap;public:int operator <= (Object a) const{return (d >= a.d);}private:int ID; //物品編號Typew w; //物品重量Typep p; //物品價值float d; //單位重量價值};//0-1背包問題的主類class Knap{public:Knap(Typew *w, Typep *p, Typew c, int n); Typep Knapsack();//回溯法解決0-1背包問題的主函數//回溯法求解01背包問題void BackTrack(int floor);//負責列印最優值和最優解,以物品編號的順序列印結果void print();private: //計算結點價值上界Typep Bound(int i);Typew c; //背包容量int n; //物品總數Object *Q; //在Q數組中存放的物品以單位重量價值降序排序Typew cw; //當前裝包重量Typep cp; //當前裝包價值int *cbestx; //當前最優解int count; //最優解的個數int *bestx[10]; //最優解,最優解的個數不超過10個。Typep bestp; //最優值Typep oldbestp; //用於回溯法邊界處理,儲存上一次最優值};Knap::Knap(Typew *w, Typep *p, Typew c, int n){//初始化Typew W = 0;Typep P = 0;count = 0;this->c = c;oldbestp = 0;this->n = n;cw = 0;cp = 0;Q = new Object[n];for(int i =0; i<n; i++){Q[i].ID = i+1;Q[i].d = 1.0*p[i]/w[i]; Q[i].w = w[i];Q[i].p = p[i];P += p[i];W += w[i];}//所有物品的總重量小於等於背包容量cif (W <= c) {bestp = P;int *newbestx = new int[n];for(int i =0; i<n; i++){newbestx[i] = 1;}bestx[count++] = newbestx;}//所有物品的總重量大於背包容量c,存在最佳裝包方案//採用簡單冒泡排序for(int i = 0; i<n-1; i++)for(int j = 0; j<n-i-1; j++){if(Q[j].d < Q[j+1].d){Object temp = Q[j];Q[j] = Q[j+1];Q[j+1] = temp;}}}Typep Knap::Knapsack(){if(count > 0) //背包容量足夠大,在初始化時已經將所有物品裝入背包{print();return bestp;}else //背包容量小於物品所有重量,存在最優裝包方案{cbestx = new int[n];BackTrack(0); //從數組Q下標0,首結點開始回溯法求解}}void Knap::BackTrack(int floor){if(floor > n-1) //已經到了子集樹中的葉子結點{if( cp == oldbestp ) //說明可能有多個最優解{int *newbe = new int[n];for (int i = 0; i < n; i++){newbe[i] = cbestx[i];}bestx[count++] = newbe;}if( cp > oldbestp) //說明最優解需要更新同時只有一個{count = 0;int *newbe = new int[n];for (int i = 0; i < n; i++){newbe[i] = cbestx[i];}bestx[count++] = newbe;oldbestp = cp;}}else{//選取數組Q下標為floor的物品,滿足背包容量約束if (c >= cw + Q[floor].w){cw += Q[floor].w;cp += Q[floor].p;if(cp >= bestp)bestp = cp;cbestx[floor] = 1;BackTrack(floor + 1);cw -= Q[floor].w;cp -= Q[floor].p;}//捨去數組Q下標為floor的物品,滿足限界函數if(cp + Bound(floor + 1) >= bestp) {cbestx[floor] = 0;BackTrack(floor + 1);}}}void Knap::print(){Typep *original = new int[n+1];cout<<"以下每行為一種解法:"<<endl;for (int i = count-1; i >= 0; i--){for (int j = 0; j < n; j++){original[Q[j].ID] = bestx[i][j];}for (int k = 1; k <= n; k++){cout<< original[k] <<" ";}cout<<endl;}cout<<"最優解的個數:"<<count<<endl;cout<<"最優值:"<<bestp<<endl;}Typep Knap::Bound(int i){Typew cleft = c - cw;Typep b = cp;while (i < n && Q[i].w <= cleft){cleft -= Q[i].w;b += Q[i].p;i++;}if(i < n) b += Q[i].p/Q[i].w * cleft;return b;}int _tmain(int argc, _TCHAR* argv[]){const int N = 4;Typew c = 7;Typew w[N] = {2,3,5,2};Typep p[N] = {6,4,8,4};cout<<"背包容量:"<<c <<" ,物品總數:"<< N<<endl;cout<<"物品重量數組:";for (int i = 0; i < N; i++){cout<<w[i]<<" ";}cout<<endl;cout<<"物品價值數組:";for (int i = 0; i < N; i++){cout<<p[i]<<" ";}cout<<endl;Knap K(w, p, c, N);K.Knapsack();K.print();system("pause");return 0;}運行結果如下圖: