1. 程式人生 > >揹包問題——“完全揹包”詳解及實現(包含揹包具體物品的求解)

揹包問題——“完全揹包”詳解及實現(包含揹包具體物品的求解)

原文地址:http://blog.csdn.net/wumuzi520/article/details/7014830

  完全揹包是在N物品中選取若干件(同一種物品可多次選取)放在空間為V的揹包裡,每物品的體積為C1,C2,…,Cn,與之相對應的價值為W1,W2,…,Wn.求解怎麼裝物品可使揹包裡物品總價值最大。

動態規劃(DP):

        1) 子問題定義:F[i][j]表示前i物品中選取若干件物品放入剩餘空間為j的揹包中所能得到的最大價值。

        2) 根據第i物品放多少件進行決策

                                     (2-1)

        其中F[i-1][j-K*C[i]]+K*W[i]表示前i-1物品中選取若干件物品放入剩餘空間為j-K*C[i]的揹包中所能得到的最大價值加上k件第i物品;

       設物品種數為N,揹包容量為V,第i物品體積為C[i],第i物品價值為W[i]。

       與01揹包相同,完全揹包也需要求出NV個狀態F[i][j]。但是完全揹包求F[i][j]時需要對k分別取0,…,j/C[i]求最大F[i][j]值,耗時為j/C[i]。那麼總的時間複雜度為O(NV∑(j/C[i]))

由此寫出虛擬碼如下:

  1. F[0][] ← {0}  
  2. F[][0] ← {0}  
  3. for i←1 to N  
  4.     dofor j←1 to V  
  5.         dofor k←0 to j/C[i]  
  6.            if(j >= k*C[i])  
  7.                 then F[i][k] ← max(F[i][k],F[i-1][j-k*C[i]]+k*W[i])  
  8. return F[N][V]  

以上虛擬碼陣列均為基於1索引,即第一件物品索引為1。空間複雜度O(VN)、時間複雜度為O(NV∑(j/C[i]))

        簡單優化:

        若兩件物品滿足C[i] ≤C[j]&&W[i] ≥W[j]時將第j種物品直接篩選掉。因為第i種物品比第j種物品物美價廉,用i替換j得到至少不會更差的方案。

       這個篩選過程如下:先找出體積大於揹包的物品直接篩掉一部分(也可能一種都篩不掉)複雜度O(N)。利用計數排序思想對剩下的物品體積進行排序,同時篩選出同體積且價值最大的物品留下,其餘的都篩掉(這也可能一件都篩不掉)複雜度O(V)。整個過程時間複雜度為O(N+V)

       轉化為01揹包:

       因為同種物品可以多次選取,那麼第i種物品最多可以選取V/C[i]件價值不變的物品,然後就轉化為01揹包問題。整個過程的時間複雜度並未減少。如果把第i種物品拆成體積為C[i]×2k價值W[i]×2k的物品,其中滿足C[i]×2k≤V。那麼在求狀態F[i][j]時複雜度就變為O(log2(V/C[i]))。整個時間複雜度就變為O(NVlog2(V/C[i]))

時間複雜度優化為O(NV)

將原始演算法的DP思想轉變一下。

設F[i][j]表示出在前i種物品中選取若干件物品放入容量為j的揹包所得的最大價值。那麼對於第i種物品的出現,我們對第i種物品放不放入揹包進行決策。如果不放那麼F[i][j]=F[i-1][j];如果確定放,揹包中應該出現至少一件第i種物品,所以F[i][j]種至少應該出現一件第i種物品,即F[i][j]=F[i][j-C[i]]+W[i]。為什麼會是F[i][j-C[i]]+W[i]?因為F[i][j-C[i]]裡面可能有第i種物品,也可能沒有第i種物品。我們要確保F[i][j]至少有一件第i件物品,所以要預留C[i]的空間來存放一件第i種物品。

狀態方程為:

                           (2-2)

虛擬碼為:

  1. F[0][] ← {0}  
  2. F[][0] ← {0}  
  3. for i←1 to N  
  4.     dofor j←1 to V  
  5.         F[i][j] ← F[i-1][j]  
  6.         if(j >= C[i])  
  7.             then F[i][j] ← max(F[i][j],F[i][j-C[i]]+ W[i])  
  8. return F[N][V]  

        具體揹包中放入那些物品的求法和01揹包情況差不多,從F[N][V]逆著走向F[0][0],設i=N,j=V,如果F[i][j]==F[i][j-C[i]]+W[i]說明包裡面有第i件物品,同時j -= C[i]。完全揹包問題在處理i自減和01揹包不同,01揹包是不管F[i][j]與F[i-1][j-C[i]]+W[i]相不相等i都要減1,因為01揹包的第i件物品要麼放要麼不放,不管放還是不放其已經遍歷過了,需要繼續往下遍歷而完全揹包只有當F[i][j]與F[i-1][j]相等時i才自減1。因為F[i][j]=F[i-1][j]說明揹包裡面不會含有i,也就是說對於前i種物品容量為j的揹包全部都放入前i-1種物品才能實現價值最大化,或者直白的理解為前i種物品中第i種物品物不美價不廉,直接被篩選掉。

        列印揹包內物品的虛擬碼如下:

  1. i←N  
  2. j←V  
  3. while(i>0 && j>0)  
  4.      doif(F[i][j]=F[i][j-C[i]]+W[i])  
  5.           then Print W[i]  
  6.                j←j-C[i]  
  7.         else
  8.           i←i-1  

        和01揹包一樣,也可以利用一個二維陣列Path[][]來標記揹包中的物品。開始時Path[N][V]初始化為0,當 F[i][j]==F[i][j-C[i]]+W[i]時Path[i][j]置1。最後通過從Path[N+1][V+1]逆著走向Path[0][0]來獲取揹包內物品。其中Path[0][]與Path[][0]為邊界。同樣,在列印路徑的時候當Path[][]=1時,列印W[i];Path[][]=0時i自減1.

       加入路徑資訊的虛擬碼如下:

  1. F[0][] ← {0}  
  2. F[][0] ← {0}  
  3. Path[][] ← 0  
  4. for i←1 to N  
  5.     dofor k←1 to V  
  6.         F[i][k] ← F[i-1][k]  
  7.         if(k >= C[i] && F[i][k] < F[i][k-C[i]]+W[i])  
  8.             then F[i][k] ← F[i][k-C[i]]+W[i]  
  9.                  Path[i][k] ← 1  
  10. return F[N][V] and Path[][]  

列印揹包內物品的虛擬碼如下:

  1. i←N  
  2. j←V  
  3. while(i>0 && j>0)  
  4.      doif(Path[i][j]=1)  
  5.           then Print W[i]  
  6.                j←j-C[i]  
  7.         else
  8.           i←i-1  

優化空間複雜度為O(V)

        和01揹包問題一樣,完全揹包也可以用一維陣列來儲存資料。演算法樣式和01揹包的很相似,唯一不同的是對V遍歷時變為正序,而01揹包為逆序。01揹包中逆序是因為F[i][]只和F[i-1][]有關,且第i的物品加入不會對F[i-1][]狀態造成影響。而完全揹包則考慮的是第i物品的出現的問題,第i種物品一旦出現它勢必應該對第i種物品還沒出現的各狀態造成影響。也就是說,原來沒有第i種物品的情況下可能有一個最優解,現在第i種物品出現了,而它的加入有可能得到更優解,所以之前的狀態需要進行改變,故需要正序。

狀態方程為:

                          (2-3)

虛擬碼如下:

  1. F[] = {0}  
  2. for i←1 to N  
  3.     dofor k←C[i] to V  
  4.         F[k] ← max(F[k],F[k-C[i]]+W[i])  
  5. return F[V]  

        具體揹包中放入那些物品的求法和上面空間複雜度為O(NV)演算法一樣,用一個Path[][]記錄揹包資訊。但這裡面是當F[i]=F[i-C[i]]+W[i]時將Path置1.

        虛擬碼如下:

  1. F[0][] = {0}  
  2. F[][0] = {0}  
  3. Path[][] ← 0  
  4. for i←1 to N  
  5.     dofor k←C[i] to V  
  6.         if(F[i] < F[k-C[i]]+W[i])  
  7.             then F[i] ← F[k-C[i]]+W[i]  
  8.                  Path[i][k] ← 1  
  9. return F[N][V] and Path[][]  

        列印路徑的虛擬碼和前面未壓縮空間複雜度時的虛擬碼一樣,這裡不再重寫。

         舉例:表2-1為一個揹包問題資料表,設揹包容量為10根據上述解決方法可得到對應的F[i][j]如表2-2所示,最大價值即為F[6][10].

表2-1揹包問題資料表

物品號i 1 2 3 4 5 6
體積C 3 2 5 1 6 4
價值W 6 5 10 2 16 8

表2-2前i件物品選若干件放入空間為j的揹包中得到的最大價值表

0 1 2 3 4 5 6 7 8 9 10
0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 6 6 6 12 12 12 18 18
2 0 0 5 6 10 11 15 16 20 21 25
3 0 0 5 6 10 11 15 16 20 21 25
4 0 2 5 7 10 12 15 17 20 22 25
5 0 2 5 7 10 12 16 18 21 23 26
6 0 2 5 7 10 12 16 18 21 23 26

 下面針對前面提到的表2-1提供兩種方法的測試程式碼:

  1. #include <iostream>
  2. #include <cstring>
  3. #include "CreateArray.h"        //該標頭檔案用於二維陣列的建立及銷燬,讀者自己實現
  4. usingnamespace std;  

//時間複雜度O(VN),空間複雜度為O(VN)

  1. int Package02(int Weight[], int Value[], int nLen, int nCapacity)  
  2. {  
  3.     int** Table = NULL;  
  4.     int** Path = NULL;  
  5.     CreateTwoDimArray(Table,nLen+1,nCapacity+1);    //建立二維陣列
  6.     CreateTwoDimArray(Path,nLen+1,nCapacity+1); //建立二維陣列
  7.     for(int i = 1; i <= nLen; i++)  
  8.     {  
  9.         for(int j = 1; j <= nCapacity; j++)  
  10.         {  
  11.             Table[i][j] = Table[i-1][j];  
  12.             if(j >= Weight[i-1] && Table[i][j] < Table[i][j-Weight[i-1]]+Value[i-1])  
  13.             {  
  14.                 Table[i][j] = Table[i][j-Weight[i-1]]+Value[i-1];  
  15.                 Path[i][j]=1;  
  16.             }  
  17.         }  
  18.     }  
  19.     int i = nLen, j = nCapacity;  
  20.     while(i > 0 && j > 0)  
  21.     {  
  22.         if(Path[i][j] == 1)  
  23.         {  
  24.             cout << Weight[i-1] << " ";  
  25. 相關推薦

    揹包問題——“完全揹包實現包含揹包具體物品求解

    原文地址:http://blog.csdn.net/wumuzi520/article/details/7014830   完全揹包是在N種物品中選取若干件(同一種物品可多次選取)放在空間為V的揹包裡,每種物品的體積為C1,C2,…,Cn,與之相對應的價值為W1

    揹包問題——“01揹包實現包含揹包具體物品求解

    -----Edit by ZhuSenlin HDU          01揹包是在M件物品取出若干件放在空間為W的揹包裡,每件物品的體積為C1,C2,…,Cn,與之相對應的價值為W1,W2,…,Wn.求解將那些物品裝入揹包可使總價值最大。         動態規劃(DP)

    微信和支付寶支付模式實現.Net標準庫

         支付基本上是很多產品都必須的一個模組,大家最熟悉的應該就是微信和支付寶支付了,不過更多的可能還是停留在直接sdk的呼叫上,甚至和業務系統高度耦合,網上也存在各種解決方案,但大多形式各異,東拼西湊而成。所以這裡我介紹下OSS.PayCenter開源跨平臺支付元件 及

    【程式碼】K-means聚類實現 Matlab聚類工具箱和自己實現

    一. 聚類 先說說聚類。顧名思義,就是有一團資料,根據某種準則把相似的資料分別聚在一起,形成不同的類別(每個類別稱為一簇)。聚類是一種無監督的演算法。所謂無監督就是說,雖然聚類把物體分類到了不同的簇,只能知道哪些資料是屬於同一類的,至於這一類資料到底是什麼,並不知道。

    getClass()和getClassLoader()區別 以及ClassLoader用途檔案載入,類載入

     1.1 幾個相關概念ClassLoader負責載入系統的所有Resources(Class,檔案,來自網路的位元組流等),通過ClassLoader從而將資源載入JVM   每個class都有一個reference,指向自己的ClassLoader。Class.getClassLoader()   arra

    二叉搜尋樹實現程式碼BST

    概念 二叉搜尋樹(Binary Search Tree),又稱二叉排序樹,它或者是一顆空樹,或者具有如下性質的樹: 若它的左子樹不為空,則左子樹上所有節點的值都小於根節點的值 若它的右子樹不為空,則右子樹上所有節點的值都大於根節點的值 它的左右子樹也分別

    資料結構之折半插入排序圖文程式碼C++實現

    問題:對待排序的陣列r[1..n]中的元素進行直接插入排序,得到一個有序的(從小到大)的陣列r[1..n]。演算法思想:1、設待排序的記錄存放在陣列r[1..n]中,r[1]是一個有序序列。2、迴圈n-1次,每次使用折半查詢法,查詢r[i](i=2,..,n)在已排好的序列r

    redis配置文件實現主從同步切換

    redis redis主從 redis配置文件詳解及實現主從同步切換redis復制Redis復制很簡單易用,它通過配置允許slave Redis Servers或者Master Servers的復制品。接下來有幾個關於redis復制的非常重要特性:一個Master可以有多個Slaves。Slaves能

    微信和支付寶支付模式實現

    配置 其余 logs https 朋友 一個 target 多租戶 對比   繼上篇《微信和支付寶支付模式詳解及實現》到現在已經有半年時間了,這期間不少朋友在公號留言支付相關的問題,最近正好也在處理公司支付相關的對接,打算寫這篇來做一個更進一步的介紹,同時根據主要的幾個支付

    數據結構 - 紅黑樹Red Black Tree插入實現Java

    啟示 dpa con 技術分享 節點數 src 通知 一點 this   最終還是決定把紅黑樹的篇章一分為二,插入操作一篇,刪除操作一篇,因為合在一起寫篇幅實在太長了,寫起來都覺得累,何況是閱讀並理解的讀者。       紅黑樹刪除操作請參考 數據結構 - 紅黑樹(Red

    數據結構 - 紅黑樹Red Black Tree刪除實現Java

    replace ati 轉載 之前 9.png one com 四種 簡單   本篇要講的就是紅黑樹的刪除操作       紅黑樹插入操作請參考 數據結構 - 紅黑樹(Red Black Tree)插入詳解與實現(Java)   紅黑樹的刪除是紅黑樹操作中比較麻煩且比較有意

    Ansible自動化運維之Playbook體驗

    tasks ansible sha shadow yml ESS remote name 自動化運維 Handlers介紹: Handlers也是一些task的列表,和一般的task並沒有什麽區別。是由通知者進行的notify,如果沒有被notify,則Handlers

    滾動條製作

    檢視滾動條的滾動距離 js中有兩套方法可以檢視當前滾動條的滾動距離。 第一套是這樣的: window.pageXOffset/window.pageYOffset 這個方法可以檢視滾動條的橫軸和縱軸的滾動距離,但是很遺憾的是IE8以及以下的版本不相容。 因此針對於IE,我們就需要有第二套方法: d

    nfs實現全網備份

    1.統一hosts cat /etc/hosts 172.16.1.5 lb01 172.16.1.6 lb02 172.16.1.7 web02 172.16.1.8 web01 172.16.1.51 db01 172.16.1.31 nfs01

    紅黑樹Red Black Tree刪除實現Java

      本篇要講的就是紅黑樹的刪除操作   紅黑樹的刪除是紅黑樹操作中比較麻煩且比較有意思的一部分。   在此之前,重申一遍紅黑樹的五個定義: 1. 紅黑樹的節點要不是黑色的要不是紅色的     2. 紅黑樹的根節點一定是黑色的     3. 紅黑樹的所有葉子節點都是黑色的(注意:紅黑樹的葉子節點指Nil節點

    堆排序演算法實現-----------c語言

    堆排序原理:   堆排序指的是將大堆(小堆)堆頂(即下標為0)元素與堆的最後一個(即下標為hp->size - 1)元素交換,hp->size–,將其餘的元素再次調整成大堆(小堆),再次將堆頂(即下標為0)元素與堆的最後一個(即下標為hp->s

    B-tree實現(C語言)

    // // MBTree.c // MBTree // // Created by Wuyixin on 2017/8/4. // Copyright © 2017年 Coding365. All rights reserved. // #include "MBTree.h" static K

    B+tree實現(C語言)

    // // BPlusTree.c // BPlusTree // // Created by Wuyixin on 2017/8/4. // Copyright © 2017年 Coding365. All rights reserved. // #include "BPlusTree.h"

    CDN技術實現原理

    一本好的入門書是帶你進入陌生領域的明燈,《CDN技術詳解》絕對是帶你進入CDN行業的那盞最亮的明燈。因此,雖然只是純粹的重點抄錄,我也要把《CDN技術詳解》的精華放上網。公諸同好。 第一章    引言 “第一公里”是指全球資訊網流量向用戶傳送的第一個出口,是網站伺服器接入網際網路的鏈路所能提供

    python中list實現

    list為python中的常用資料型別,其為python中內建類,繼承自object。接下來全面介紹list的常見方法及自己實現類list功能的類建立list建立空list​ list1 = [] ​ list2 = list()建立並初始化list​ l