背包必备理论基础2
上一篇是用二維dp數(shù)組來講解01背包。
這一篇說一說滾動數(shù)組,就是把二維dp降為一維dp。
接下來還是用如下這個例子來進行講解
背包最大重量為4。
問:背包能背的物品最大價值是多少?
一維dp數(shù)組(滾動數(shù)組)
對于背包問題其實狀態(tài)都是可以壓縮的。
在使用二維數(shù)組的時候,遞推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);i是物品,j是背包容量。
dp[i][j] 表示從下標為[0-i]的物品里任意取,放進容量為j的背包,價值總和最大是多少。
其實可以發(fā)現(xiàn)如果把dp[i - 1]那一層拷貝到dp[i]上,表達式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
于其把dp[i - 1]這一層拷貝到dp[i]上,不如只用一個一維數(shù)組了,只用dp[j](一維數(shù)組,也可以理解是一個滾動數(shù)組)。
這就是滾動數(shù)組的由來,需要滿足的條件是上一層可以重復(fù)利用,直接拷貝到當前層。
動規(guī)五部曲分析如下:
確定dp數(shù)組的定義
在一維dp數(shù)組中,dp[j]表示:容量為j的背包,所背的物品價值可以最大為dp[j]。
一維dp數(shù)組的遞推公式
dp[j]可以通過dp[j - weight[i]]推導(dǎo)出來,dp[j - weight[i]]表示容量為j -weight[i]的背包所背的最大價值。
dp[j - weight[i]] + value[i] 表示 容量為 j - 物品i 的重量的背包 加上物品i的價值。(也就是容量為j的背包,放入物品i了之后的價值即:dp[j])
此時dp[j]有兩個選擇,一個是取自己dp[j],一個是取dp[j - weight[i]] +value[i],指定是取最大的,畢竟是求最大價值,
所以遞歸公式為:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
一維dp數(shù)組如何初始化
dp[j]表示:容量為j的背包,所背的物品價值可以最大為dp[j],那么dp[0]就應(yīng)該是0。
那么dp數(shù)組除了下標0的位置,初始為0,其他下標應(yīng)該初始化多少呢?
看一下遞歸公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
dp數(shù)組在推導(dǎo)的時候一定是取價值最大的數(shù),如果題目給的價值都是正整數(shù)那么非0下標都初始化為0,如果題目給的價值有負數(shù),那么非0下標就要初始化為負無窮。
這樣才能讓dp數(shù)組在遞歸公式的過程中取的最大的價值,而不是被初始值覆蓋了。
那么假設(shè)物品價值都是大于0的,所以dp數(shù)組初始化的時候,都初始為0就可以了。
一維dp數(shù)組遍歷順序
for(int i = 0; i < weight.size(); i++) { // 遍歷物品// 遍歷背包容量,要保證j-weight[i]>=0,所以要求j>=wewight[i]for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);//dp數(shù)組存在覆蓋,所以要max} }二維dp遍歷的時候,背包容量是從小到大,而一維dp遍歷的時候,背包是從大到小。
因為倒敘遍歷是為了保證物品i只被放入一次!。但如果一旦正序遍歷了,那么物品0就會被重復(fù)加入多次!
舉一個例子:物品0的重量weight[0] = 1,價值value[0] = 15
如果正序遍歷
dp[1] = dp[1 - weight[0]] + value[0] = 15
dp[2] = dp[2 - weight[0]] + value[0] = 30
此時dp[2]就已經(jīng)是30了,意味著物品0,被放入了兩次,所以不能正序遍歷。
為什么倒敘遍歷,就可以保證物品只放入一次呢?
倒敘就是先算dp[2]
dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp數(shù)組已經(jīng)都初始化為0)
dp[1] = dp[1 - weight[0]] + value[0] = 15
所以從后往前循環(huán),每次取得狀態(tài)不會和之前取得狀態(tài)重合,這樣每種物品就只取一次了。
那么問題又來了,為什么二維dp數(shù)組歷的時候不用倒敘呢?
因為對于二維dp,dp[i][j]都是通過上一層即dp[i - 1][j]計算而來,本層的dp[i][j]并不會被覆蓋!
再來看看兩個嵌套for循環(huán)的順序,代碼中是先遍歷物品嵌套遍歷背包容量,那可不可以先遍歷背包容量嵌套遍歷物品呢?
不可以!
因為一維dp的寫法,背包容量一定是要倒序遍歷,如果遍歷背包容量放在第一層,那么每個dp[j]就只會放入一個物品,即:背包里只放入了一個物品。
舉例推導(dǎo)dp數(shù)組
一維dp,分別用物品0,物品1,物品2 來遍歷背包,最終得到結(jié)果如下:
一維dp01背包完整C++測試代碼
總結(jié)