1. 程式人生 > >揹包九講-附錄(二) 揹包問題的搜尋解法

揹包九講-附錄(二) 揹包問題的搜尋解法

        《揹包問題九講》的本意是將揹包問題作為動態規劃問題中的一類進行講解。但鑑於的確有一些揹包問題只能用搜索來解,所以這裡也對用搜索解揹包問題做簡單介紹。大部分以01揹包為例,其它的應該可以觸類旁通。

簡單的深搜

        對於01揹包問題,簡單的深搜的複雜度是O(2^N)。就是枚舉出所有2^N種將物品放入揹包的方案,然後找最優解。基本框架如下:

procedure SearchPack(i,cur_v,cur_w)
    if(i>N)
        if(cur_w>best)
            best=cur_w
        return
    if(cur_v+v[i]<=V)
        SearchPack(i+1,cur_v+v[i],cur_w+w[i])
    SearchPack(i+1,cur_v,cur_w)

其中cur_v和cur_w表示當前解的費用和權值。主程式中呼叫SearchPack(1,0,0)即可。

搜尋的剪枝

        基本的剪枝方法不外乎可行性剪枝或最優性剪枝。

        可行性剪枝即判斷按照當前的搜尋路徑搜下去能否找到一個可行解,例如:若將剩下所有物品都放入揹包仍然無法將揹包充滿(設題目要求必須將揹包充滿),則剪枝。

        最優性剪枝即判斷按照當前的搜尋路徑搜下去能否找到一個最優解,例如:若加上剩下所有物品的權值也無法得到比當前得到的最優解更優的解,則剪枝。

搜尋的順序

        在搜尋中,可以認為順序靠前的物品會被優先考慮。所以利用貪心的思想,將更有可能出現在結果中的物品的順序提前,可以較快地得出貪心地較優解,更有利於最優性剪枝。所以,可以考慮將按照“價效比”(權值/費用)來排列搜尋順序。

        另一方面,若將費用較大的物品排列在前面,可以較快地填滿揹包,有利於可行性剪枝。

        最後一種可以考慮的方案是:在開始搜尋前將輸入檔案中給定的物品的順序隨機打亂。這樣可以避免命題人故意設定的陷阱。

        以上三種決定搜尋順序的方法很難說哪種更好,事實上每種方法都有適用的題目和資料,也有可能將它們在某種程度上混合使用。

子集和問題

        子集和問題是一個NP-Complete問題,與前述的(加權的)01揹包問題並不相同。給定一個整數的集合S和一個整數X,問是否存在S的一個子集滿足其中所有元素的和為X。

        這個問題有一個時間複雜度為O(2^(N/2))的較高效的搜尋演算法,其中N是集合S的大小。

        第一步思想是二分。將集合S劃分成兩個子集S1和S2,它們的大小都是N/2。對於S1和S2,分別枚舉出它們所有的2^(N/2)個子集和,儲存到某種支援查詢的資料結構中,例如hash set。

        然後就要將兩部分結果合併,尋找是否有和為X的S的子集。事實上,對於S1的某個和為X1的子集,只需尋找S2是否有和為X-X1的子集。

        假設採用的hash set是理想的,每次查詢和插入都僅花費O(1)的時間。兩步的時間複雜度顯然都是O(2^(N/2))。

        實踐中,往往可以先將第一步得到的兩組子集和分別排序,然後再用兩個指標掃描的方法查詢是否有滿足要求的子集和。這樣的實現,在可接受的時間內可以解決的最大規模約為N=42。

搜尋還是DP?

        在看到一道揹包問題時,應該用搜索還是動態規劃呢?

        首先,可以從資料範圍中得到命題人意圖的線索。如果一個揹包問題可以用DP解,V一定不能很大,否則O(VN)的演算法無法承受,而一般的搜尋解法都是僅與N有關,與V無關的。所以,V很大時(例如上百萬),命題人的意圖就應該是考察搜尋。另一方面,N較大時(例如上百),命題人的意圖就很有可能是考察動態規劃了。

        另外,當想不出合適的動態規劃演算法時,就只能用搜索了。例如看到一個從未見過的揹包中物品的限制條件,無法想出DP的方程,只好寫搜尋以謀求一定的分數了。