1. 程式人生 > >揹包問題之回溯法

揹包問題之回溯法

問題描述:揹包的容量為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進位制列舉要高很多。