Python 刷題筆記:揹包問題

刷動態規劃的第二天,有些自閉,剛靠着大魔王的歌緩過來了。關於動態規劃,我還處於看題解時哦哦哦、看題目時???的階段,所以整理的點不深。除了昨天推給大家的鏈接,今天也是發現了一位刷題大牛的寶藏,不僅動態規劃,各類算法都做了整理、引導,屬實 respect !

動態規劃專題
https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie

關於動態規劃,我現階段的理解是在窮舉的過程中找到可以複用的求值過程,或者說找到當前要求的值與已經求好值之間的關係,建立所謂的“狀態轉移方程”。具體的講解我等之後理解加深有機會再展開,刷題階段效率爲主,今天記錄兩道揹包題目。

題目

0-1揹包問題描述

現在有一個可裝載重量爲 W 的揹包和 N 個物品,每個物品有重量和價值兩個屬性。其中第 i 個物品的重量爲 wt[i],價值爲 val[i],現在讓你用這個揹包裝物品,最多能裝的價值是多少?

例如:
W = 10, N = 5
wt = [2, 2, 6, 5, 4]
val = [6, 3, 5, 4, 6]

返回 15,因爲可以裝 [2,2,4] 重量的物品,價值分別 [6,3,6]

題目中 0-1 指的是揹包裝物品時,要麼裝整個物品、要麼不裝,不能只裝取部分。

題目分析

因爲揹包有重量限制,所以當我們遍歷物品時,無法確定物品是否被裝入揹包,就更難以找到裝某一物品時與之前狀態的關聯。動態規劃會強調“狀態”,通過自定義的一維或二維數組爲我們將物品裝入揹包這個行爲定義成狀態的變化,從而找到與上一次裝物品之間的關聯。

動態規劃英文 dynamic programming,所以定義相關的狀態數組多用 dp, 本題目中就是通過定義二維數組、在 Python 中即嵌套列表來實現。

揹包問題中,用 dp [ i ] [ j ] 表示在物品列表中的前 i 件物品操作完、此時揹包容量爲 j 的狀態下,揹包所能裝的最大價值,恰好對應了題目所求,求容量爲 W 的揹包、 N 個物品所實現最大價值即 dp [ N ] [ W ] 對應的值。

爲何要定義這麼一個奇怪的狀態呢?就是爲了找尋裝物品時不同狀態間的關係,從而建立狀態轉移方程。在裝第 i 件物品時,對應題目中的重量和價值列表,該物品重量爲 wt[i-1]、價值爲 val[i-1]。

在操作這第 i 件物品之前,揹包狀態處於 dp [ i-1 ] [ j ] ,物品 -1、容量不變。進行到第 i 件了,要麼裝、要麼不裝就這兩種選擇:裝的話,新的狀態要在之前的價值上添加新物品的價值;不裝的話,那麼新狀態與之前狀態的價值是相等的。這便是揹包問題狀態轉移關鍵所在。有種建立遞歸關係的意思,所以要找到初始狀態值,在 i = 0 或 j = 0 時,一個是 0 件物品、一個是揹包容量爲 0,其價值對應爲 0。之後的狀態值在此基礎上可以不斷找到得出。

代碼實現

因爲本題不是 LeetCode 原題,所以解法代碼沒有沿用 Class 那種格式,只是定義了函數:

# n 對應個數,c 對應揹包容量,w 爲物品重量列表,v 物品價值列表
def bag_value(n,c,w,v):
	# 嵌套的列表解析式生成 c x n 的二維數組、列表
    dp = [[None for j in range(c+1)] for i in range(n+1)]
    # 爲初始狀態 i=0 或 j=0 時賦值 0 
    for i in range(n+1):
        for j in range(c+1):
            if i == 0:
                dp[i][j]=0
            if j == 0:
                dp[i][j]=0
    # 仍然遍歷二維數組            
    for i in range(1,n+1):
        for j in range(1,c+1):
        	# 若揹包容量小於要裝的物品重量,那麼該物品不會被裝入、狀態不變
            if j<w[i-1]:
                dp[i][j] = dp[i-1][j]
            # 若有可能裝該物品,取裝或不裝該物品狀態下最大價值
            else:
                dp[i][j] = max(dp[i-1][j-w[i-1]]+v[i-1],dp[i-1][j])
    # 最終返回 n、c 值下的 dp 價值
    return dp[n][c]

n = 5
c = 10
w = [2, 2, 6, 5, 4]
v = [6, 3, 5, 4, 6]
result = bag_value(n,c,w,v)
# 可以得到 result 值爲 15

這裏值得注意的是,在不確定是否裝該物品時,對上一個狀態的取值並沒有取我們之前提到的 dp [ i-1 ] [ j ] 而是對這裏的揹包容量處理、調整至最大容量減去第 i 件物品的重量,這是爲了保證裝完該物品後不超出揹包容量限制,而 dp 本身對揹包容量是有遍歷的,所以選取的是最精準的上一狀態。

感想

刷題刷到動態規劃,很大的感受是我這刷題實施得太晚了,早幾年就好了,之前對這些概念、算法完全沒有意識。現在補過,只能說好過之後來補。

同時,潛意識裏就覺着動態規劃很難,所以選的策略是跳出題目,看有經驗大牛的整理專題,在他們的引領下熟悉這些題目的套路,先學習、掌握要點後再通過練習來提高熟練度。

今天看這類型的題目看的腦袋大,就簡單記這麼些吧,明兒見~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章