動態規划——背包問題python實現(01背包、完全背包、多重背包)


參考:

背包九講——嗶哩嗶哩

背包九講



01背包問題

描述:

有N件物品和一個容量為V的背包。

第i件物品的體積是vi,價值是wi。

求解將哪些物品裝入背包,可使這些物品的總體積不超過背包流量,且總價值最大。


二維動態規划

f[i][j] 表示只看前i個物品,總體積是j的情況下,總價值最大是多少。 result = max(f[n][0~V]) f[i][j]:

  • 不選第i個物品:f[i][j] = f[i-1][j];

  • 選第i個物品:f[i][j] = f[i-1][j-v[i]] + w[i](v[i]是第i個物品的體積) 兩者之間取最大。 初始化:f[0][0] = 0 (啥都不選的情況,不管容量是多少,都是0?)

代碼如下:

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

# 初始化,先全部賦值為0,這樣至少體積為0或者不選任何物品的時候是滿足要求  
dp = [[0 for i in range(v+1)] for j in range(n+1)]

for i in range(1, n+1):
    for j in range(1,v+1):
        dp[i][j] = dp[i-1][j]  # 第i個物品不選
        if j>=goods[i-1][0]:# 判斷背包容量是不是大於第i件物品的體積
            # 在選和不選的情況中選出最大值
            dp[i][j] = max(dp[i][j], dp[i-1][j-goods[i-1][0]]+goods[i-1][1])


print(dp[-1][-1])

一維動態優化

從上面二維的情況來看,f[i] 只與f[i-1]相關,因此只用使用一個一維數組[0~v]來存儲前一個狀態。那么如何來實現呢?

第一個問題:狀態轉移

假設dp數組存儲了上一個狀態,那么應該有:

dp[i] = max(dp[i] , dp[i-v[i]]+w[i])

max函數里面的dp[i]代表的是上一個狀態的值。

第二個問題:初始化

這里開始初始化一個長度為V+1一維數組,選取0個物品時,體積為0~V時的最大價值(值全部為0)。

第三個問題:遞推關系

試想一下,我要保證求取第i個狀態時,用到一維數組中的值是第i-1個狀態的。如果,我從前往后推,那么當我遍歷到后面時,我用到的狀態就是第i個狀態而不是第i-1個狀態。比如:

dp[i] = max(dp[i] , dp[i-v[i]]+w[i])

這里的dp[i-v[i]]是已經重新賦值的,而不是上一個狀態的值,所以這樣是錯誤的。因此,我們要從后往前推

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

dp = [0 for i in range(v+1)]

for i in range(n):
    for j in range(v,-1,-1): # 從后往前
        if j >= goods[i][0]:
            dp[j] = max(dp[j], dp[j-goods[i][0]] + goods[i][1])

print(dp[-1])

確定體積的情況

如果我要求的不是盡可能最大的價值,而是剛好等於背包容量的最大價值,那么該如何去做呢?


完全背包問題

完全背包問題

描述:

有N件物品和一個容量為V的背包,每件物品都有無限個!

第i件物品的體積是vi,價值是wi。

求解將哪些物品裝入背包,可使這些物品的總體積不超過背包流量,且總價值最大。


一維動態規划

完全背包問題跟01背包問題最大的區別就是每一個物品可以選無數次,因此當我們考慮到第i個物品時,我們應該考慮的情況是:不選這個物品、選一次這個物品、選兩次這個物品......,直到不能再選(選的次數k,k*v[i] > j,j為當前背包容量),然后再從這些情況中選最大的。代碼如下:

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])
dp = [0 for i in range(v+1)]
for i in range(n):
    for j in range(v,-1,-1): # 從后往前
        k = j//goods[i][0]  # 能選多少次
        # 從這些次里面取最大
        dp[j] = max([dp[j- x* goods[i][0]] + x * goods[i][1] for x in range(k+1)])

print(dp[-1])

一維動態規划(優化)

剛剛那個問題,我們是延續01背包的問題,從后往前遞推。但是對於這個問題,其實可以通過從前往后遞推。如何理解呢?

假設在考慮第i個物品時的兩個狀態:

A:dp[k*v[i] + x]

B:dp[(k-1)*v[i] + x]

根據前面的歸納,從前一個狀態遞推過來:

  • 要求A的值,應該要從k+1個狀態中選出最大的:

    dp[x] + k*w[i]
    dp[v[i] + x] + (k-1)*w[i]
    dp[2*v[i] + x] + (k-2)*w[i]
    ...
    ...
    dp[(k-1)*v[i] + x] + w[i]
    dp[k*v[i] + x
    
  • 要求B的值,應該要從k個狀態中選出最大的:

    dp[x] + (k-1)*w[i]
    dp[v[i] + x] + (k-2)*w[i]
    dp[2*v[i] + x] + (k-3)*w[i]
    ...
    ...
    dp[(k-2)*v[i] + x] + w[i]
    dp[(k-1)*v[i] + x
    

我們可以看到,一一對應過來的話,這兩個狀態實際上只差一個w[i]的值。因此:

一方面我們可以根據前一個狀態(i-1)推出此時的狀態,另一方面由於當前狀態前面的值也是當前問題的子問題,因此我們也可以從前面的值推到后面的值。

從前往后遞推的代碼如下:

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])
dp = [0 for i in range(v+1)]
for i in range(n):
    for j in range(v+1):
        if j >= goods[i][0]:
            dp[j] = max(dp[j], dp[j-goods[i][0]] + goods[i][1])

print(dp[-1])

多重背包問題

多重背包問題

描述:

有N件物品和一個容量為V的背包。

第i件物品的體積是vi,價值是wi,數量是si。

求解將哪些物品裝入背包,可使這些物品的總體積不超過背包流量,且總價值最大。


一維動態規划

其實跟上面的完全背包問題類似,只不過我們從后往前遞推的時候,物體i選取的次數應該要重新考慮下:

k = min(s[i], j//v[i]), j為當前的背包容量

代碼如下:

n,v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

dp = [0 for i in range(v+1)]

for i in range(n):
    for j in range(v, -1, -1):
        # 考慮兩種情況的最小值
        k = min(j//goods[i][0], goods[i][2])
        dp[j] = max([dp[j-x*goods[i][0]] + x*goods[i][1] for x in range(k+1)])

print(dp[-1])

一維動態規划(轉換01背包)

想法很簡單,直接把背包中的物品展開,展成很多數量為1的物品,這樣就轉換為01背包問題。代碼如下:

n,v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

new_goods = []

# 展開
for i in range(n):
    for j in range(goods[i][2]):
        new_goods.append(goods[i][0:2])

goods = new_goods
n = len(goods)

# 01背包問題
dp = [0 for i in range(v+1)]

for i in range(n):
    for j in range(v,-1,-1):
        if j>= goods[i][0]:
            dp[j] = max(dp[j], dp[j - goods[i][0]] + goods[i][1])

print(dp[-1])


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM