揹包問題——“01揹包”詳解及實現(包含揹包中具體物品的求解)
-----Edit by ZhuSenlin HDU
01揹包是在M件物品取出若干件放在空間為W的揹包裡,每件物品的體積為C1,C2,…,Cn,與之相對應的價值為W1,W2,…,Wn.求解將那些物品裝入揹包可使總價值最大。
動態規劃(DP):
1) 子問題定義:F[i][j]表示前i件物品中選取若干件物品放入剩餘空間為j的揹包中所能得到的最大價值。
2) 根據第i件物品放或不放進行決策
(1-1)
其中F[i-1][j]表示前i-1件物品中選取若干件物品放入剩餘空間為j的揹包中所能得到的最大價值;
而F[i-1][j-C[i]]+W[i]表示前i-1件物品中選取若干件物品放入剩餘空間為j-C[i]的揹包中所能取得的最大價值加上第i件物品的價值。
根據第i件物品放或是不放確定遍歷到第i件物品時的狀態F[i][j]。
設物品件數為N,揹包容量為V,第i件物品體積為C[i],第i件物品價值為W[i]。
由此寫出虛擬碼如下:
F[0][] ← {0} F[][0] ← {0} for i←1 to N do for k←1 to V F[i][k] ← F[i-1][k] if(k >= C[i]) then F[i][k] ← max(F[i][k],F[i-1][k-C[i]]+W[i]) return F[N][V]
以上虛擬碼陣列均為基於1索引,及第一件物品索引為1。時間及空間複雜度均為O(VN)
舉例:表1-1為一個揹包問題資料表,設揹包容量為10根據上述解決方法可得到對應的F[i][j]如表1-2所示,最大價值即為F[6][10].
表1-1揹包問題資料表
物品號i | 1 | 2 | 3 | 4 | 5 | 6 |
體積C | 2 | 3 | 1 | 4 | 6 | 5 |
價值W | 5 | 6 | 5 | 1 | 19 | 7 |
表1-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 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 5 |
2 | 0 | 5 | 6 | 6 | 11 | 11 | 11 | 11 | 11 | 11 | 11 |
3 | 0 | 5 | 5 | 10 | 11 | 11 | 16 | 16 | 16 | 16 | 16 |
4 | 0 | 5 | 5 | 10 | 11 | 11 | 16 | 16 | 16 | 16 | 17 |
5 | 0 | 5 | 5 | 10 | 11 | 11 | 19 | 24 | 24 | 29 | 30 |
6 | 0 | 5 | 5 | 10 | 11 | 11 | 19 | 24 | 24 | 29 | 30 |
很多文章講揹包問題時只是把最大價值求出來了,並沒有把所選的是哪些物品找出來。本人在學習揹包問題之前遇到過很多的類似問題,當時也是隻求得了最大價值或最大和,對具體哪些物品或路徑等細節也束手無策。再次和大家一起分享細節的求法。
根據演算法求出的最大價值表本身其實含有位置資訊,從F[N][V]逆著走向F[0][0],設i=N,j=V,如果F[i][j]==F[i-1][j-C[i]]+W[i]說明包裡面有第i件物品,同時j -= C[i],不管F[i][j]與F[i-1][j-C[i]]+W[i]相不相等i都要減1,因為01揹包的第i件物品要麼放要麼不放,不管放還是不放其已經遍歷過了,需要繼續往下遍歷。
列印揹包內物品的虛擬碼如下:
i←N
j←V
while(i>0 && j>0)
do if(F[i][j]=F[i-1][j-C[i]]+W[i])
then Print W[i]
j←j-C[i]
i←i-1
當然也可以定義一個二維陣列Path[N][V]來存放揹包內物品資訊,開始時Path[N][V]初始化為0,當 F[i][j]==F[i-1][j-C[i]]+W[i]時Path[i][j]置1。最後通過從Path[N+1][V+1]逆著走向Path[0][0]來獲取揹包內物品。其中Path[0][]與Path[][0]為邊界。
加入路徑資訊的虛擬碼如下:
F[0][] ← {0}
F[][0] ← {0}
Path[][] ← 0
for i←1 to N
do for k←1 to V
F[i][k] ← F[i-1][k]
if(k >= C[i] && F[i][k] < F[i-1][k-C[i]]+W[i])
then F[i][k] ← F[i-1][k-C[i]]+W[i]
Path[i][k] ← 1
return F[N][V] and Path[][]
列印揹包內物品的虛擬碼如下:
i←N
j←V
while(i>0 && j>0)
do if(Path[i][j] = 1)
then Print W[i]
j←j-C[i]
i←i-1
在時間及空間複雜度均為O(NV)的情況下,利用Path[][]的方法明顯比直接通過F[i][j]==F[i-1][j-C[i]]+W[i]來列印物品耗費空間,Path[][]需要額外的空間O(NV)但總空間複雜度不變仍為O(NV)。但下面要講到的O(V)的空間複雜度的方法卻不能利用關係式F [j]==F [j-C[i]]+W[i]而只能利用Path[][]進行標記.
接下來考慮如何壓縮空間,以降低空間複雜度。
時間複雜度為O(VN),空間複雜度將為O(V)
觀察虛擬碼可也發現,F[i][j]只與F[i-1][j]和F[i-1][j-C[i]]有關,即只和i-1時刻狀態有關,所以我們只需要用一維陣列F[]來儲存i-1時的狀態F[]。假設i-1時刻的F[]為{a0,a1,a2,…,av},難麼i時刻的F[]中第k個應該為max(ak,ak-C[i]+W[i])即max(F[k],F[k-C[i]]+W[i]),這就需要我們遍歷V時逆序遍歷,這樣才能保證求i時刻F[k]時F[k-C[i]]是i-1時刻的值。如果正序遍歷則當求F[k]時其前面的F[0],F[1],…,F[K-1]都已經改變過,裡面存的都不是i-1時刻的值,這樣求F[k]時利用F[K-C[i]]必定是錯的值。最後F[V]即為最大價值。
求F[j]的狀態方程如下:
(1-2)
虛擬碼如下:
F[] ← {0}
for i ← 1 to N
do for k ← V to C[i]
F[k] ← max(F[k],F[k-C[i]]+W[i])
return F[V]
同樣,怎麼求路徑?
利用前面講到的Path[][]標記,需空間消耗O(NV)。這裡不能用F [j]==F [j-C[i]]+W[i]來判斷是因為一維陣列並不能提供足夠的資訊來尋找二維路徑。
加入路徑資訊的虛擬碼如下:
F[] ← {0}
Path[][]←0
for i←1 to N
do for k←V to C[i]
if(F[k] < F[k-C[i]]+W[i])
then F[k] ← F[k-C[i]]+W[i]
Path[i][k] ← 1
return F[V] and Path[][]
列印路徑的虛擬碼和前面未壓縮空間複雜度時的虛擬碼一樣,這裡不再重寫。
下面針對前面提到的表1-1提供兩種方法的測試程式碼:
#include <iostream>
#include <cstring>
#include "CreateArray.h" //該標頭檔案用於動態建立及銷燬二維陣列,讀者自己實現
using namespace std;
//時間複雜度O(VN),空間複雜度為O(VN)
int Package01(int Weight[], int Value[], int nLen, int nCapacity)
{
int** Table = NULL;
int** Path = NULL;
CreateTwoDimArray(Table,nLen+1,nCapacity+1); //建立二維陣列
CreateTwoDimArray(Path,nLen+1,nCapacity+1); //建立二維陣列
for(int i = 1; i <= nLen; i++)
{
for(int j = 1; j <= nCapacity; j++)
{
Table[i][j] = Table[i-1][j];
Path[i][j] = 0;
if(j >= Weight[i-1] && Table[i][j] < Table[i-1][j-Weight[i-1]]+Value[i-1])
{
Table[i][j] = Table[i-1][j-Weight[i-1]]+Value[i-1];
Path[i][j] = 1;
}
}
}
int i = nLen, j = nCapacity;
while(i > 0 && j > 0)
{
if(Path[i][j] == 1)
{
cout << Weight[i-1] << " ";
j -= Weight[i-1];
}
i--;
}
cout << endl;
int nRet = Table[nLen][nCapacity];
DestroyTwoDimArray(Table,nLen+1); //銷燬二維陣列
DestroyTwoDimArray(Path,nLen+1); //銷燬二維陣列
return nRet;
}
//時間複雜度O(VN),不考慮路徑空間複雜度為O(V),考慮路徑空間複雜度為O(VN)
int Package01_Compress(int Weight[], int Value[], int nLen, int nCapacity)
{
int * Table = new int [nCapacity+1];
memset(Table,0,(nCapacity+1)*sizeof(int));
int** Path = 0;
CreateTwoDimArray(Path,nLen+1,nCapacity+1); //建立二維陣列
for(int i = 0; i < nLen; i++)
{
for(int j = nCapacity; j >= Weight[i]; j--)
{
Path[i+1][j] = 0;
if(Table[j] < Table[j-Weight[i]]+Value[i])
{
Table[j] = Table[j-Weight[i]]+Value[i];
Path[i+1][j] = 1;
}
}
}
int i = nLen, j = nCapacity;
while(i > 0 && j > 0)
{
if(Path[i][j] == 1)
{
cout << Weight[i-1] << " ";
j -= Weight[i-1];
}
i--;
}
cout << endl;
int nRet = Table[nCapacity];
DestroyTwoDimArray(Path,nLen+1); //銷燬二維陣列
delete [] Table;
return nRet;
}
測試程式碼
int main()
{
int Weight[] = {2,3,1,4,6,5};
int Value[] = {5,6,5,1,19,7};
int nCapacity = 10;
cout << Package01(Weight,Value,sizeof(Weight)/sizeof(int),nCapacity) << endl;
cout << Package01_Compress(Weight,Value,sizeof(Weight)/sizeof(int),nCapacity) << endl;
return 0;
}
本文部分內容參考“揹包九講”
相關推薦
揹包問題——“01揹包”詳解及實現(包含揹包中具體物品的求解)
-----Edit by ZhuSenlin HDU 01揹包是在M件物品取出若干件放在空間為W的揹包裡,每件物品的體積為C1,C2,…,Cn,與之相對應的價值為W1,W2,…,Wn.求解將那些物品裝入揹包可使總價值最大。 動態規劃(DP)
揹包問題——“完全揹包”詳解及實現(包含揹包具體物品的求解)
原文地址:http://blog.csdn.net/wumuzi520/article/details/7014830 完全揹包是在N種物品中選取若干件(同一種物品可多次選取)放在空間為V的揹包裡,每種物品的體積為C1,C2,…,Cn,與之相對應的價值為W1
微信和支付寶支付模式詳解及實現(.Net標準庫)
支付基本上是很多產品都必須的一個模組,大家最熟悉的應該就是微信和支付寶支付了,不過更多的可能還是停留在直接sdk的呼叫上,甚至和業務系統高度耦合,網上也存在各種解決方案,但大多形式各異,東拼西湊而成。所以這裡我介紹下OSS.PayCenter開源跨平臺支付元件 及
【程式碼】K-means聚類詳解及實現 (Matlab聚類工具箱和自己實現)
一. 聚類 先說說聚類。顧名思義,就是有一團資料,根據某種準則把相似的資料分別聚在一起,形成不同的類別(每個類別稱為一簇)。聚類是一種無監督的演算法。所謂無監督就是說,雖然聚類把物體分類到了不同的簇,只能知道哪些資料是屬於同一類的,至於這一類資料到底是什麼,並不知道。
二叉搜尋樹詳解及實現程式碼(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 多租戶 對比 繼上篇《微信和支付寶支付模式詳解及實現》到現在已經有半年時間了,這期間不少朋友在公號留言支付相關的問題,最近正好也在處理公司支付相關的對接,打算寫這篇來做一個更進一步的介紹,同時根據主要的幾個支付
機器學習中的概率模型和概率密度估計方法及VAE生成式模型詳解之七(第4章 之 梯度估算)
.com 概率 roc 生成 詳解 time 學習 style BE ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?機器學習中的概率模型和概率密度估計方法及V
數據結構 - 紅黑樹(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