紅色代表錯誤或者特別注意
藍色代表修復後的正確代碼
黃色表示變量
一.問題分析
1.問題的性質
回溯法是對樹的深度遍歷,需要用到遞歸.
分支限界法是對樹的廣度遍歷,需要用到<queue>數據結構.而且每個狀態都是一個數據結構實體
狀態應該表示如下幾個屬性:
int cp //已放入物品總價值
int rp //剩餘物品的總價值
int rw //剩餘容量
int id //物品序號,比如某結點id=0,拓展當前結點時就要檢查物品0 放入/不放入.
int[] x //當前解向量
運算過程可以描述爲:
將當前狀態結點的符合條件的子結點push到隊尾,並讓當前節點出隊.
2.限界條件&約束條件
01揹包問題中,當前結點和左右子結點的限界條件和約束條件各不一樣
當前結點:
①約束條件:什麼情況下,當前結點無法生成左右子結點?
只有遍歷完最後一個物品元素,或者容量=0時.看看此時能不能更新最優解. 最後當前結點出隊並檢查下一隊列元素
②限界條件:什麼情況下,當前結點沒必要生成左右子節點?
當livenode.cp+livebode.rp<bestp時. 最後當前結點出隊並檢查下一隊列元素
左子結點進行約束條件:當前剩餘容量能夠放入第t個物品
if(livenode.cp > W[ t ]) 擴展lchild,lchild按條件賦值,id++
右子節點進行限界條件:不放入第t個物品,剩餘價值--,計算已有價值和剩餘價值是否大於最優解
if( livenode.cp + (livenode.rp - V[ t ]) ) > bestp
二.代碼實現
分爲三部分: 全局變量區 / 廣度遍歷函數 / 初始化&調用&顯示函數
1.全局變量區
①一開始W[]和V[]在一個struct裏,發現寫出來有點繁瑣:Good[i].value.
②每個結點都有cp來表示當前價值,不需要再像回溯法寫在全局變量裏
vector<int>W = { 2,5,4,2 };
vector<int>V = { 6,3,5,4 };
int N1 = (int)V.size();
int bestp1; //記錄最優值
vector<bool>bestx1; //記錄最優解
int Volume; //記錄購物車總容量
int sumv, sumw; //全部物品總重量和總價值
//狀態結構,記錄下每個狀態
struct State
{
int cp; //當前狀態的放入物品總價
int rp; //當前狀態的剩餘物品總價
int rw; //當前狀態的剩餘容量
int id; //物品號
vector<bool> x; //當前策略
State()
{
cp = rp = rw = id = 0;
x.resize(N1);
}
State(int _cp,int _rp,int _rw, int _id) //構造函數,方便賦值,不必再寫好幾行賦值語句
{
cp = _cp;
rp = _rp;
rw = _rw;
id = _id;
x.resize(N1);
}
};
2.廣度遍歷函數
int BFS_01Backpack()
{
int t;//當前物品序號
queue<State>q1;
q1.push(State(0, sumv, Volume, 0));//已有物品總值,剩餘物品總值,剩餘容量,物品序號
while (!q1.empty()) //開始循環
{
State livenode, lchild, rchild; //每次循環創建當前結點,左子樹,右子樹
livenode = q1.front(); //上一次while循環入隊的左右孩子成爲了新的livenode
q1.pop();
t = livenode.id;
//終止條件
if (t >= N1 || livenode.rw == 0)//物品序號 t=[0,N-1] || 當前狀態的剩餘空間==0
{
//多個可行解中得最優值
if (livenode.cp >= bestp1)
{
bestx1 = livenode.x;
bestp1 = livenode.cp;
}
continue;
}
//對活結點的限界條件
if (livenode.cp + livenode.rp < bestp1)
continue;
//左子樹
if (livenode.rw >= W[t]) //約束條件:剩餘容量裝得下當前物品
{
//已有價值+, 剩餘價值-, 剩餘容量-, 序號比父結點+1
lchild = State(livenode.cp + V[t], livenode.rp-V[t], livenode.rw - W[t], livenode.id + 1);
lchild.x = livenode.x;
lchild.x[t] =true; //表示放入
if (lchild.cp > bestp1) //當前解>最優解才更新
bestp1 = lchild.cp;
q1.push(lchild);
}
//右子樹
if (livenode.cp + (livenode.rp - V[t]) >= bestp1)
{
//已有價值不變, 剩餘價值-, 剩餘容量不變, 序號比父結點+1
rchild = State(livenode.cp, livenode.rp-V[t], livenode.rw, t + 1);
rchild.x = livenode.x;
rchild.x[t] = false;
q1.push(rchild);
}
}
return bestp1;
}
3.初始化&調用&打印函數
void backpack()
{
//初始化
bestp1 = 0;
Volume = 10;
bestx1.resize(N1);
sumw = accumulate(W.begin(), W.end(), 0); //記錄總重量
sumv = accumulate(V.begin(), V.end(), 0); //記錄總價值
//調用BFS()
cout<<"最優值爲: "<<BFS_01Backpack();
//輸出
cout << "放入的物品爲: ";
for (int i = 0; i < N1; i++)
{
if (bestx1[i] == true)
cout << i << " ";
}
cout << endl;
}
三.Bug分析
1.vector越界問題
忘記聲明vector<bool> x時預設長度.
2.輸出結果錯誤
右子樹的約束條件出了問題,原錯誤代碼:
if (livenode.cp + livenode.rp >= bestp1)
右子樹表示不放入物品t,剩餘價值也一樣要減少:
if (livenode.cp + (livenode.rp - V[t]) >= bestp1)