級別: ★☆☆☆☆
標簽:「算法」「DP策略」「動態(tài)規(guī)劃」
作者: MrLiuQ
審校: QiShare團隊
本篇將介紹動態(tài)規(guī)劃相關(guān)知識。
一、簡介
動態(tài)規(guī)劃(Dynamic Programming,簡稱DP)。
它的核心思想是把一個復雜的大問題拆成若干個子問題,通過解決子問題來逐步解決大問題。
注意:使用動態(tài)規(guī)劃思想有個前提:當且僅當每個子問題都是離散的(即每個子問題都不依賴于其他子問題時),才能使用動態(tài)規(guī)劃。
二、動態(tài)規(guī)劃之“0-1背包問題”
現(xiàn)在有這么一個場景,
“你”是一名“小偷”,你帶了個包去“偷東西”,。
條件1:每個商品只有一個,要么拿,要么不拿。(0-1背包問題)
條件2:你最多拿得動4kg
的東西。(固定大小,可不裝滿)
商品 | 價格 | 重量 |
---|---|---|
商品A | 3000元 | 4kg |
商品B | 2000元 | 3kg |
商品C | 1500元 | 1kg |
商品D | 2000元 | 1kg |
在有限的重量條件下,如何“偷”,賺的錢最多?
方案一:簡單算法(可行,不推薦)
暴力枚舉出所有商品的排列組合,
舍去所有超出重量要求的組合,
從中挑一個最大的。
可行,但是太慢了,每多一件商品都會多2倍的組合。
方案測評:時間復雜度 O(2n),超級超級慢,不推薦。
方案二:貪心算法(不可行)
用上篇介紹的貪心算法計算。
通過某個貪心策略(拿最貴的、拿性價比最高的商品)來得出近似解。
方案測評:這種方案接近最優(yōu)解,是近似解,但不一定是最優(yōu)解,故不可行。
方案三:動態(tài)規(guī)劃(可行,推薦)
- 原理:先解決子背包最優(yōu),再解決大背包最優(yōu)。
先繪制出一張表格,一會我們一列一列慢慢填。(PS:體會動態(tài)規(guī)劃的算法過程)
表格:(實際上對應了一個二維數(shù)組)
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | ||||
商品B | ||||
商品C | ||||
商品D |
先解讀一下這個表格,
行:代表了商品行(對應i
),
列:代表了重量列(對應j
),
格:代表當前的已有的商品、已有重量下所能拿的最大價值。
好了,下面我們開始一列一列的填:
第一行,只有商品A(價值:3000,重量:4kg)
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | / | |||
商品B | ||||
商品C | ||||
商品D |
第二行,有商品A(價值:3000,重量:4kg)與商品B(價值:2000,重量:3kg)
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | / | |||
商品B | / | |||
商品C | ||||
商品D |
第三行,有商品A(價值:3000,重量:4kg)、商品B(價值:2000,重量:3kg)商品C(價值:1500,重量:1kg)
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | / | |||
商品B | / | |||
商品C | 1500 | |||
商品D |
第四行,有商品A(價值:3000,重量:4kg)、商品B(價值:2000,重量:3kg)、商品C(價值:1500,重量:1kg)、商品D(價值:2000,重量:1kg)
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | / | |||
商品B | / | |||
商品C | 1500 | |||
商品D | 2000 |
大家有沒有發(fā)現(xiàn),這里填寫每個表格時的算法可表示為:
對應行的商品的重量超過當前子背包的重量,就取上一行單元格的值,
商品的重量能裝下當前子背包,則取下面兩者的較大值:
- 上一個單元格的值(
cell[i-1][j]
)- 當前商品的價值 + 剩余空間的價值(
cell[i-1][j-當前商品的重量所對應的列號]
)
下面填第二列:
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | / | / | ||
商品B | / | / | ||
商品C | 1500 | 1500 | ||
商品D | 2000 | 3500 |
第三列:
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | / | / | / | |
商品B | / | / | 2000 | |
商品C | 1500 | 1500 | 2000 | |
商品D | 2000 | 3500 | 3500 |
第四列:
商品\ 子背包最大重量 | 1kg | 2kg | 3kg | 4kg |
---|---|---|---|---|
商品A | / | / | / | 3000 |
商品B | / | / | 2000 | 3000 |
商品C | 1500 | 1500 | 2000 | 3500 |
商品D | 2000 | 3500 | 3500 | 4000 |
于此反復判斷即可,這樣每個單元格都是最優(yōu)解,通過解決子問題,推導出最終最優(yōu)解。
這就是動態(tài)規(guī)劃,是不是很簡單呢?
轉(zhuǎn)換成Python
代碼:
def package_dp(a, b, flag, n):
c = [[0 for i in range(n)] for j in range(n)]
for j in range(n):
c[0][j] = 0
for i in range(n):
c[i][0] = 0
for j in range(n):
if b[i]>flag[j]:
c[i][j] = c[i-1][j]
else:
temp1 = a[i] + c[i-1][j-b[i]]
temp2 = c[i-1][j]
c[i][j] = max(temp1,temp2)
print c[i][j]
print ("")
return c
price = [0, 3000, 2000, 1500, 2000]
weight = [0, 4, 3, 1, 1]
flag = [0, 1, 2, 3, 4]
package_dp(price, weight, flag, 5)
三、細節(jié)問題
子背包拆分問題:按照 所有商品 的最大公約數(shù)(也有可能存在小數(shù))去拆子背包。
讓所有的商品都能被剛好裝下。通過子背包的最優(yōu)解 => 推導出 => 全背包的最優(yōu)解。
這個過程的思想,就是DP思想(動態(tài)規(guī)劃的核心思想)
四、動態(tài)規(guī)劃的應用場景
本文舉了背包與矩陣連乘的例子,其實思路都是一樣的。
只是應用場景不同,常見的應用場景有以下幾個:
- 0-1背包問題( ??)
- 矩陣連乘法( ??)
- 硬幣找零
- 字符串相似度
- 最長公共子序列
- 最長遞增子序列
- 最大連續(xù)子序列和/積
- 有代價的最短路徑
- 瓷磚覆蓋(狀態(tài)壓縮DP)
- 工作量劃分
參考資料:
推薦文章:
Dart基礎(chǔ)(一)
Dart基礎(chǔ)(二)
Dart基礎(chǔ)(三)
Dart基礎(chǔ)(四)
iOS 短信驗證碼倒計時按鈕
iOS 環(huán)境變量配置
iOS 中處理定時任務的常用方法