三種揹包問題+滾動數組優化

三種揹包問題+滾動數組優化

滾動數組

滾動數組簡單的理解就是讓數組滾動起來,起到節省存儲空間的作用。主要應用在遞推或動態規劃中。

舉個適合使用滾動數組的例子:
斐波那契數列爲1、1、2、3、5、8、13、21、34……此數列從第3項開始,每一項都等於前兩項之和。
遞推公式爲F(n)=F(n-1)+F(n-2),n≥3,F(1)=1,F(2)=1。

假如我們要求斐波拉切數列的第80項,我們常規的方法是定義一個80大小的數組,然後進行如下的循環。

在這裏插入圖片描述

那麼我們要求第2e7項的斐波拉契,我們不可能去定義一個2e7的數組。通過觀察,我們可以發現其實每一次循環都只用到前兩位的數組,使用過後就不會使用了,很浪費空間。我們可以使用滾動數組來解決。

使用滾動數組的話,我們就只需要定義一個3大小的數組(仔細想想我們滾動話只需要3個數就夠了)。

在這裏插入圖片描述

滾動數組的使用重在對於取餘(%)的理解下面來演示一下。

無論多大的數取餘某個數時,得到的數都在0到這個數之間。當下標對循環長度取餘,則對於n長度的環,第n-1個的下一個將會是第0個,從而實現了循環。

在這裏插入圖片描述
在這裏插入圖片描述
滾動數組只能節約空間複雜度,時間複雜度還是和原來一樣。

01揹包

描述:
有n件物品和一個容量爲m的揹包。第i件物品的費用是v[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

這是最基礎的揹包問題,特點是:每種物品僅有一件,可以選擇放或不放。

狀態轉移方程:

二維數組

用 dp[i][j]表示前i種物品放入一個容量爲j的揹包的最大價值
dp[i][j]=j<w[i]? dp[i-1][j]:max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); (1<=i<=n,0<=j<=m)

在這裏插入圖片描述
下面通過圖表樣例理解下
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
此時我們可以發現我們得到了這麼一個數組,dp[i][j]即表示在前i個物品中選取任意物品裝填承重j揹包時可得到的最大價值和。

通過對上面圖表的分析理解我們發現,我們只需要前兩行的值就能得出新的一行,和斐波拉契很相似,我們可以使用滾動數組。

滾動數組

代碼解析:
在這裏插入圖片描述
但是僅僅滾動數組的優化通常來說是不夠的。畢竟不管怎麼樣還是一個二維數組,優化始終是有限的。

優化版

用dp[j]表示當前總重量爲j的所有方案中的最大價值
dp[j]=max(dp[j],dp[j-w[i]]+v[i]); (1<=i<=n ,w[i]<=j<=W)

代碼解析:
在這裏插入圖片描述

這裏的一維數組的優化是最優的。

核心是基於一維數組做反向迭代,這樣的話j<w[i]的部分直接不用複製了,且由於是反向的線性更新也不會發生衝突。

舉個例子

在這裏插入圖片描述

在這裏插入圖片描述
結合動圖理解這裏的反向迭代的巧妙。

完全揹包

描述:
N種物品,第i種的物品重量是w[i],價值是v[i],每種物品有無限件,求總重量在揹包承重m以內的最大總價值。

狀態轉移方程:

二維數組

用dp[i][j]表示前i種物品放入一個容量爲j的揹包的最大價值類似於簡化爲01揹包。
dp[i][j] = max(dp[i][j], dp[i-1][j-kw[i]] + kv[i]]) (1<=i<=n,0<=j<=m) (0 <= k*w[i] <= j)

代碼解析:

在這裏插入圖片描述

這裏可以理解爲變相的01揹包,重量爲kw[i],價值爲kv[i]。

小優化版:用dp[i][j]表示前i種物品放入一個容量爲j的揹包的最大價值,
dp[i][j] = max(dp[i-1][j],dp[i][j-w[i]+v[i]) (1<=i<=n,0<=j<=W)

代碼解析:

在這裏插入圖片描述

核心在於將已裝載的dp[i][j]用於後續dp,因爲物品無限件,同一物品可以選取多次。這是其與01揹包的主要區別。

01揹包 dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
完全揹包 dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);

這個已裝載怎麼理解?
舉個例子:
遍歷到第5個物品,其重量是3:
那麼dp[5][3]處在此次迭代中會存: 以3容量的揹包選取前4種物品 和 選取一個桃子後以0容量的揹包選取前四種物品中的最優情況。dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);

如果此時選擇第5個的情況是最優情況,被存儲了,那就表示已裝載。因爲能在後續的遍歷中多次拿到第5個物品。

如果已經dp[5][3]拿到了第5個物品那麼在後續的dp[5][6]只需要存: 以6容量的揹包選取前4種物品 和 再拿一個第5個物品加上dp[5][3]中的最優情況。


看一下和01揹包的區別
在01揹包中:
假如遍歷到第5個物品,其重量是3:
那麼dp[5][3]在此處迭代會存:以3容量的揹包選取前4種物品 和 選取一個桃子後以0容量的揹包選取前四種物品中的最優情況。dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
而dp[5][6]則是存以6容量的揹包選取前4種物品 和 (以3容量的揹包選取前4種物品+一個第5中物品的價值)的最優情況。
dp[5][6]與dp[5][3]沒有直接的迭代關係。

滾動數組

完全揹包的滾動數組和01揹包類似。

代碼解析:
在這裏插入圖片描述
但是僅僅滾動數組的優化通常來說是不夠的。

優化版

用dp[j]表示當前總重量爲j的所有方案中的最大價值
dp[j]=max(dp[j],dp[j-w[i]]+v[i]); (1<=i<=n,0<=j<=W)

代碼解析:
在這裏插入圖片描述

核心在於類似01揹包的反向迭代

舉個例子

在這裏插入圖片描述

在這裏插入圖片描述

結合01揹包的動圖,理解其區別。

多重揹包

描述
有n種物品,第i種物品的重量是w[i],價值是w[i],每種物品的數量有限k[i],如何在m重量內,讓總價值儘可能最大。

普通的二維解法和完全揹包很類似。

二維數組

代碼解析:

在這裏插入圖片描述

這裏與完全揹包唯一不同的就是不但要滿足揹包大小,還要滿足物品個數。

滾動數組

滾動數組多重揹包

代碼解析:

在這裏插入圖片描述

二進制優化

這裏三個for循環複雜度太高,不適合很多題目,我們要進行優化。

首先我們要知道我們的目的是什麼,目的是將單種物品分解爲概念上的多個不同物品然後做01揹包。而且分解不是亂分解的,我們需要做到首先不能重複,其次需要將所有的分解的情況都要表示出來,比如有10個x物品,我們需要把0-10的組合情況全部都能表示出來。

優化解法 (二進制優化)
1.如果限制的數量*物品容量>=當前最大容量
直接考慮成完全揹包問題來處理,把物品數量當成無限數量來考慮。

2.如果限制的數量物品容量<當前最大容量
將單種物品二進制分解爲概念上的多個不同物品然後做01揹包
*

普通情況直接完全揹包很好理解,特殊的二進制分解我們可以根據例子來理解:

假如給了我們 價值爲 2,但是數量卻是10 的物品,我們應該把10給拆開,要知道二進制能夠表示任何數的,所以10 就是可以有1,2,4,8之內的數把它組成,一開始我們選上 1了,然後讓10-1=9,再選上2,9-2=7,在選上 4,7-4=3,而這時的3<8了,所以我們就是可以得出 10由 1,2,4,3來組成。

我們能發現1,2,3,4能組成1-10以內的任何數。

那麼他們的價值是什麼呢,是2,4,6,8,也就說給我們的價值爲2,數量是10的這批貨物,已經轉化成了價值分別是2,4,6,8元的貨物了,那麼最後在進行dp選擇的時候,就能通過他們的最優解選擇,組成出我們需要的組成物品。形成概念上的01揹包。

在這裏插入圖片描述
在這裏插入圖片描述