0 1揹包問題

2021-09-27 04:36:41 字數 4010 閱讀 8347

簡述

0 1 揹包可以說是講述動態規劃的最適合入門的問題了,所謂動態規劃,其實可以簡單理解為一種特殊的分而治之的策略,和我們常見的如二分查詢等分而治之策略的不同在於,可以採用分而治之策略的問題,它劃分出來的若干子問題是沒有交集的,即子問題的求解完全不受其他因素的干擾,就如下圖所示:

我們只需要將目光聚集解決如何解決每乙個小問題,再將所有待解決的小問題合併,複雜的大問題也就迎刃而解。

但對於一般的動態規劃問題,它分出來的子問題卻往往互相交錯,你中有我我中有你,使得問題變得十分複雜,在求解乙個子問題的時候還需要考慮其他子問題對該問題的影響,這使得我們陷入了一種複雜的遞迴呼叫中。

問題01揹包問題的描述大概就是我有乙個負重固定的揹包,還有一地物品,物品的重量和價值隨機,但每件物品都只有乙個,其實有相同的也無所謂,現在需要盡量裝走這些物品,使得在揹包負重足夠的情況下,被揹包裝入的物品總價值最大

上圖中的w代表每個物品的重量,v代表每個物品對應的價值,我們可以很快地想到乙個方法,每次拿上未拿的物品中 v/w 最大的那個物品,直到揹包裝不下為止,這種想法是非常自然的,但往往卻不是正確的,以上述圖為例, v/m 最大的是第五個物品,其次是第四個,然後是第六個, 此時這三個物品重量為 3+4+9 =16,已經放不下任何乙個其他物品了,這三個物品的總價值為 4+7+8 =19;然而經過觀察,我們發現明明取 1 , 2, 4 , 5 這四個物品,總重量為19, 總價值為20,比貪心策略求出的值還要大!!

解我們當然可以遍歷所有的情況,然後找出那個 v 最大的情況,但這種遍歷是非常耗時的,我們有更好的解法,只需將思路稍微轉變一下即可。

我們將目光聚焦在每次做抉擇的時刻,假設我已經挑選過前i-1個物品,這些物品我有的拿了有的沒拿,在揹包負重不足的條件下(前i-1個物品並未全部放入或者全部放入後放不下第i個物品),這是我這個狀態下揹包存放物品的最優解,那麼在遇見第i個物品後,我便要思考拿不拿這個物品了, 無非有兩種情況,將揹包裡的一些物品丟棄,且並不需要考慮丟了誰,只關注揹包負重,直到可以裝下第i個物品 ,然後將這個物品放入揹包; 或者乾脆不用管這個第i個物品, 因為就算我為它騰出了空間,帶上它以後價值居然還沒有我原來揹包大,我們當然選擇使得我揹包價值盡量大的那種選擇,選擇完畢後,目前這個揹包的狀態,就是探尋了前i個物品後我得到的最優的揹包!

這很好解釋,簡單來說,我之前的狀態是最優的,那麼我僅需要考慮這次的選擇最優,那麼我總體的狀態也就是最優的。

有了上述一層關係,就可以很簡單地得到如下推導:

假設i為 前 i 個物品,j為當前揹包負重,那麼f(i,j)表示:只考慮前i個物品的情況下,負重 j情況下 所能得到的最優解,遞推式如下:

f(i,j) = max
一般的,我們稱上述推導式為狀態轉移方程

假設總共有 m 件物品 , 揹包的總負重為 g , 不妨再通過上面的描述再解釋一遍 ,考慮前 m 件物品 , 負重g情況下所能得到的最優解,道法自然 , 這不正就是我們所要求的最後的問題的解嗎?

關於上述的狀態轉換方程的解釋,我提到了乙個條件,在揹包負重不足的情況下,那麼如果揹包的負重大到可以將所有物品都存放進去,這個方程還成立嗎?

換一種角度就能夠想到,我們實際的操作其實就是填表,揹包負重越大,表越長,填到最後如果負重足夠,所有的物品也都是可以順利被包含在內的。

小問題還有乙個小問題值得我們思考,假設我們通過遞迴的思想,不斷地遞迴遞迴,直到原始狀態,先不考慮原始狀態的情況,將重點放在 狀態轉移方程上,這兩個比對的上一層狀態,會不會是之前已經出現過的呢?如果已經出現過了,我們自然沒有必要耗費資源再計算一遍,這也是文章開頭我提到的交叉子問題的情況,很顯然,確實會發生這種問題,而且還十分普遍。

如何解決這種不斷重複的問題,最簡單明瞭的方法就是記下來,將我們之前計算過的所有 f() 都記錄在對應位置,這樣當我們要求解乙個子問題,我們先查詢之前是否有計算過,如果有,直接返回,如果沒有,則計算該值,並記錄下來留作下次查詢。當然,還有一種方法是迭代的自底而上全都求解一遍,下面的**就採取的是迭代方式而不是遞迴。

**

const

int n =

2002

;//根據題目要求頂下陣列大小

int f[n]

[n];

intgetanswer

(int n ,

int bag ,

int* w ,

int* v)

}return f[n]

[bag]

;}

上述的**實際上就是展示一種填表的操作,將所有存放所有最優狀態的表填好,答案自然就在這張表的最末尾,由此可以得到該演算法的複雜度 o( n * bag) ,所以揹包問題通常它的物品數或者揹包負重都能太大。

優化:滾動陣列

我們再來看一下它的狀態轉移方程:

f(i,j) = max
結合上面的**就不難發現,第 i 行的狀態的求解 , 僅與第 i-1行有關 , 我們需要的答案在那張表的最末端 , 也就是說,這張表的其他地方對於我來說是無所謂的,那麼是否可以僅開乙個一維的陣列,存放當前行的最優狀態,等到下一行時,再通過原有的值重新整理一遍,得到新的一行的狀態值呢?

答案是當然的,方式也很簡單,僅需要逆向遍歷

為何要逆向遍歷呢? 再仔細觀察一下狀態轉移方程中第二個子狀態:f(i -1 , j-i.w) + i.v,如果做順序遍歷,那麼它找到的揹包實際上是已經更新過以後的揹包了, 原來的資料被抹去無法找回,但如果是逆序,則這個子狀態需要的上一層的資料,都是未曾動過的。

此時的狀態轉換方程為:f(j) = max

優化後的**如下:

const

int n =

2002

;//根據題目要求頂下陣列大小

//int f[n][n] ;

int f[n]

;int

getanswer

(int n ,

int bag ,

int* w ,

int* v)

}return f[bag]

;}

完整**如下

#include

using namespace std;

const

int n =

2002

;//根據題目要求頂下陣列大小

//int f[n][n] ;

int f[n]

;int

getanswer

(int n ,

int bag ,

int* w ,

int* v)

}return f[bag];}

// int getanswer(int n , int bag , int* w , int* v)

// }

// return f[n][bag];

// }

intmain

(int argc,

char

const

*ar**)

cout<<

getanswer

(n,bag,w,v)

;return0;

}

揹包問題 01揹包問題

n個物品,總體積是v,每個物品的體積的vi,每個物品的最大價值是wi,在不超過v的體積下求最大價值 eg揹包容積為 5 物品數量為 4 物品的體積分別為 物品的價值分別為 思路定義乙個二位陣列int f new int n 1 v 1 f i j 就表示在1 i個物品中選取體積小於v的情況的最大價值...

揹包問題 01揹包

有n件物品和乙個容量為v的揹包。第i件物品的重量是c i 價值是w i 求解將哪些物品裝入揹包可使價值總和最大。01揹包中的 01 就是一種物品只有1件,你可以選擇放進去揹包即1,也可以選擇不放入揹包中即0。include include using namespace std const int ...

揹包問題(01揹包)

1085 揹包問題 在n件物品取出若干件放在容量為w的揹包裡,每件物品的體積為w1,w2 wn wi為整數 與之相對應的價值為p1,p2 pn pi為整數 求揹包能夠容納的最大價值。input 第1行,2個整數,n和w中間用空格隔開。n為物品的數量,w為揹包的容量。1 n 100,1 w 10000...