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

揹包問題——“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技術詳解》的精華放上網。公諸同好。 第一章    引言 “第一公里”是指全球資訊網流量向用戶傳送的第一個出口,是網站伺服器接入網際網路的鏈路所能提供

pythonlist實現

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