数据结构之动态规划问题
數據結構中動態規劃應該算得上是你避不開的一道檻了吧!其重要性不言而喻,今天就整理下學習筆記分享出來。希望對讀者朋友也能有幫助,文章基本框架如下:
什么是動態規劃
小偷的背包問題
LeetCode刷題
什么是動態規劃
定義
動態規劃(英語:Dynamic programming,簡稱DP)是一種在數學、管理科學、計算機科學、經濟學和生物信息學中使用的,通過把原問題分解為相對簡單的子問題的方式求解復雜問題的方法。動態規劃常常適用于有重疊子問題和最優子結構性質的問題,動態規劃方法所耗時間往往遠少于樸素解法。
上述是維基百科的解釋。能夠看的出來最為關鍵的點有這樣3個。
最優子結構
最小子問題邊界
狀態轉移函數(拆分轉化方法)
最優子結構指的是某一階段復雜問題可以拆解成之前某些階段的子問題;邊界則是指對于最小子問題的解;狀態轉移方法指的是復雜某階段復雜問題和之前階段的轉化關系。
舉例解釋
我們可以以斐波那契數列(Fibonacci)為例!
1, 1, 2, 3, 5, 8, 13 ,21 …
如果把第n個斐波拉契數記作 F(n),那么怎樣利用動態規劃來解釋這個問題呢?
先談最小子問題邊界,當然是最前邊的兩個數F(1)和F(2)。
F(1)=F(2)=1。
最優子結構則是當前階段斐波拉契數可以由之前斐波拉契數計算而出,計算關系則是我們說的狀態轉移方法。這里的狀態轉移函數為:
F(n)=F(n-1)+F(n-2),n>2
小偷的背包問題
上述斐波拉契數的例子只是用來舉例便于理解動態規劃的3個特點。真正經典的動態規劃題目是小偷的背包問題:
一個小偷闖進一家珠寶店,他只背了一個可承重W的背包,珠寶店里有N件物品,其重量分別為w1,w2……wN,其價值分別為v1,v2……vN。那么問題來了,小偷該選擇偷哪些東西用背包帶走呢?(假設都為整數單位)
用動態規劃的思路來解決這個題目,可以把物品的種類和書包承重力當成狀態,繪制出一個二維表格如下,行表示種類從1到N,列表示承重從0到W。
| 1 | ||||
| 2 | ||||
| …… | ||||
| N |
再定義2個列表存儲N類物品各自的重量和價值:
w=[w1,w2,……wN]
v=[v1,v2,^vN]
先思考整體過程
首先假設只有一件物品重量w[0]價值v[0],假設背包承重能力從0到W,被偷的最大價值為res,也就是第一行從左到右的過程。
不難想象,這時只用判斷背包承重是否能夠承受第一行對應物品,不能則res=0,能則被偷走,res=v[0]。
| 1 | 0 | v[0] | …… | v[0] |
| 2 | ||||
| …… | ||||
| N |
確定第一行(邊界)不難,關鍵在于如何找到狀態轉移函數,即res[i][j]是如何從之前狀態獲取計算的。res[i][j]表示共i+1件物品,j承重的時候,能偷走的最大價值。(加1是因為列表索引從0開始)
選擇邊界并確定狀態轉移函數
重點來了!當已知res[i-1][:]所有狀態,現在有第i+1件物品可以選擇,我們該怎么判斷?
首先判斷第i+1件物品是否超出背包當前承重(即w[i]>j),超出則肯定放不進去,最大價值和有無第i+1件物品無關,即res[i][j]=res[i-1][j]。
當可以放進去的時候,有兩個選擇,放或不放,選擇不放該物品,則依舊是res[i][j]=res[i-1][j];選擇放該物體則最大價值是res[i][j]=v[i]+res[i-1][j-w[i]],表示放了之后剩余載重可帶走的最大價值。
兩種選擇取其大者即可!上述邏輯代碼如下,也是核心代碼。
for?i?in?range(1,N):#從第二行開始????for?j?in?range(C+1):
????????#這里是關鍵的狀態轉移函數
????????if?j?<?w[i]:
????????????res[i][j]?=?res[i-1][j]
????????else:
????????????res[i][j]?=?max(res[i-1][j],?v[i]+res[i-1][j-w[i]])#注意這里,應當設置承重為0的情況(即j從0到C+1);可能存在j=w[i]
return?res
當返回了res后,其實res[N-1][C]即為所求的最大價值。
但是!如果我要你告訴我你選擇哪幾件物品的時候能取得上述最大值呢?思考一分鐘再往后看!
其實,這會我們反過來看就比較簡單了,因為上述核心就是對比res[i][j]和res[i-1][j],能夠想到如果二者不相等的時候,必然說明res[i][j]對應的物品偷走了(沒偷走二者應當相等),若取走了該件物品,背包載重則減少了對應的承重。所以這樣就能夠判斷出小偷偷走的是那些物品了,需要注意的是當i=1的時候無法判斷res[0][j]對應行是否偷走,這里應當利用剩余承重是否為0了。
有點繞口,基本上是逐行把自己代碼的思路講解出來的,而不是直接給代碼,希望對小白友好些。
代碼實現
talk is cheap,show me the code:
#?Author:小詹#Date:2019-4-18
#動態規劃的背包類問題,Python實現
def?bag(N,C,w,v):
????'''
????N:物品種類
????C:背包承重
????w:物品種類對應的重量,列表
????v:物品種類對應的價值,列表
????'''
????res?=?[[0?for?j?in?range(C+1)]?for?i?in?range(N)]?#全0表格,1-N件物品,0-C承重
????for?j?in?range(C+1):#初始化第一行邊界
????????if?j?<?w[0]:
????????????res[0][j]?=?0
????????else:
????????????res[0][j]?=?v[0]
#?????print(res)
????for?i?in?range(1,N):#從第二行開始
????????for?j?in?range(C+1):
????????????#這里是關鍵的狀態轉移函數
????????????if?j?<?w[i]:
????????????????res[i][j]?=?res[i-1][j]
????????????else:
????????????????res[i][j]?=?max(res[i-1][j],?v[i]+res[i-1][j-w[i]])#注意這里,應當設置承重為0的情況(即j從0到C+1);可能存在j=w[i]
????return?res
def?show(N,C,w,res):
????'''
????通過比較res[i][j]和res[i-1][j]是否相等判斷是否裝了第i+1件物品;
????小詹這里沒有充分發揮列表下標從0開始的特點,導致要做+1操作,能理解就好
????'''
????print('背包能放下物體最大價值為:',res[N-1][C])
????print(res)
????j?=?C
????pick?=?[]
????for?i?in?range(N-1,0,-1):
#?????????print(i)
????????if?res[i][j]?>?res[i-1][j]:
????????#因為res中從0開始是第1行,這里i-1對應第i行;將第i行和i-1行對比,如果不一樣則表示背包裝了第i行對應的物品
????????????pick.append(i+1)
????????????j-=w[i-1]???????????????
????if?j?>?0:
????????pick.append(1)
????print('背包依次裝了第',pick,'件物品')
def?main():
????N?=?5
????C?=?10
????w?=?[2,?2,?6,?5,?4]
????v?=?[6,?3,?5,?4,?6]
????res?=?bag(N,C,w,v)
????show(N,C,w,res)
if?__name__?=='__main__':
????main()????
上述就是完整代碼。(碼字不易,轉用請注明出處)
LeetCode刷題
嗯哼,如果你把前兩部分徹底搞懂了,對動態規劃至少也算是有個初步了解了吧!
熟能生巧,當然得在力扣上找點題目練練手吧?這里有3道題可以去嘗試一下噢!
1.LeetCode第5題————最長回文字串
2.LeetCode第10題————正則表達式匹配
3.LeetCode第32題————最長有效括號
4.Leetcode第62題————不同路徑
5.Leetcode第63題————不同路徑2
其中第5題和第10題可以參考小詹之前打卡記錄帖~
LeetCode打卡系列1-20題~
點擊閱讀原文可查看小詹博客對應內容噢!
總結
以上是生活随笔為你收集整理的数据结构之动态规划问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百道Python面试题实现,搞定Pyth
- 下一篇: 面经系列 | Python,数据结构,神