揹包問題之回溯法
問題描述:揹包的容量為C,現有N件物品,價格分別為p[0],p[1]......p[n-1].重量分別為:w[0],w[1]......w[n-1].從N件物品中選擇任意個放入揹包中,使得物體的價值最大並且總重量不超過揹包的容量C。
採用數學語言描述如下:
在 w[0]*x[0] + w[1] *x[1]+....... +w[n-1]*x[n-1] < C, x[i] = 0 或1 的條件下
求 p[0]*x[0] + p[1] *x[1]+....... +p[n-1]*x[n-1] 的最大值。
回溯法類其實也算列舉法的一種,但在搜尋過程中,一般使用遞迴來完成。
回溯法的基本思想
對於用回溯法求解的問題,首先要將問題進行適當的轉化,得出狀態空間樹。 這棵樹的每條完整路徑都代表了一種解的可能。通過深度優先搜尋這棵樹,列舉每種可能的解的情況;從而得出結果。但是,回溯法中通過構造約束函式,可以大大 提升程式效率,因為在深度優先搜尋的過程中,不斷的將每個解(並不一定是完整的,事實上這也就是構造約束函式的意義所在)與約束函式進行對照從而刪除一些 不可能的解,這樣就不必繼續把解的剩餘部分列出從而節省部分時間。
回溯法中,首先需要明確下面三個概念:
(一)約束函式:約束函式是根據題意定出的。通過描述合法解的一般特徵用於去除不合法的解,從而避免繼續搜尋出這個不合法解的剩餘部分。因此,約束函式是對於任何狀態空間樹上的節點都有效、等價的。
(二)狀態空間樹:剛剛已經提到,狀態空間樹是一個對所有解的圖形描述。樹上的每個子節點的解都只有一個部分與父節點不同。
(三)擴充套件節點、活結點、死結點:所謂擴充套件節點,就是當前正在求出它的子節點的節點,在DFS中,只允許有一個擴充套件節點。活結點就是通過與約束函式的對照,節點本身和其父節點均滿足約束函式要求的節點;死結點反之。由此很容易知道死結點是不必求出其子節點的(沒有意義)。
利用回溯法解題的具體步驟
首先,要通過讀題完成下面三個步驟:
(1)描述解的形式,定義一個解空間,它包含問題的所有解。
(2)構造狀態空間樹。
(3)構造約束函式(用於殺死節點)。
然後就要通過DFS思想完成回溯,虛擬碼如下:
void BackTrack(int depth)
{
if(depth > maxDepth) //已經到最大深度
{
if(solution is target)
save solution;
return;
}
for (int i =0;i<TotalExpendNode;++i)
{
if(currentNode is searchable) //當前結點滿足約束條件
{
do something;
BackTrack(depth+1);
undo something;
}
}
}
對於揹包問題其演算法程式碼如下:
小結:回溯法作為一種窮舉方法,可以使用約束函式來排除一些不可能的結點。雖然不理論上此演算法在理論上最壞的情況下,複雜度仍為2^n,但在實際實驗中,其搜尋效率比上一次使用的2進位制列舉要高很多。