今天整理了一下關於動態規劃的內容,道理都知道,但是python來描述的方面參考較少,整理如下,希望對你有所幫助,實驗代碼均經過測試。
請先好好閱讀如下內容–什麼是動態規劃?
摘錄於《算法圖解》
以上的都建議自己手推一下,然後知道怎麼回事,核心的部分是142頁核心公式,待會代碼會重現這個過程,推薦沒有算法基礎的小夥伴看這本書《算法圖解》很有意思的書,講的很清晰,入門足夠
更深入的請閱讀python算法-動態規劃寫的不錯,可以參考
爲什麼要使用動態規劃?
From:動態規劃是什麼,意義在哪裏?!!!!
首先我們要知道爲什麼要使用(Dynamic programming)dp,我們在選擇dp算法的時候,往往是在決策問題上,而且是在如果不使用dp,直接暴力效率會很低的情況下選擇使用dp.
那麼問題來了,什麼時候會選擇使用dp呢,一般情況下,我們能將問題抽象出來,並且問題滿足無後效性,滿足最優子結構,並且能明確的找出狀態轉移方程的話,dp無疑是很好的選擇。
- 無後效性通俗的說就是隻要我們得出了當前狀態,而不用管這個狀態怎麼來的,也就是說之前的狀態已經用不着了,如果我們抽象出的狀態有後效性,很簡單,我們只用把這個值加入到狀態的表示中。
- 最優子結構(自下而上):在決策問題中,如果,當前問題可以拆分爲多個子問題,並且依賴於這些子問題,那麼我們稱爲此問題符合子結構,而若當前狀態可以由某個階段的某個或某些狀態直接得到,那麼就符合最優子結構
- 重疊子問題(自上而下):動態規劃算法總是充分利用重疊子問題,通過每個子問題只解一次,把解保存在一個需要時就可以查看的表中,每次查表的時間爲常數,如備忘錄的遞歸方法。斐波那契數列的遞歸就是個很好的例子
- 狀態轉移:這個概念比較簡單,在抽象出上述兩點的的狀態表示後,每種狀態之間轉移時值或者參數的變化。
小結
- 動態規劃: 動態規劃表面上很難,其實存在很簡單的套路:當求解的問題滿足以下兩個條件時, 就應該使用動態規劃:
- 主問題的答案 包含了 可分解的子問題答案 (也就是說,問題可以被遞歸的思想求解)
- 遞歸求解時, 很多子問題的答案會被多次重複利用
- 動態規劃的本質思想就是遞歸, 但如果直接應用遞歸方法, 子問題的答案會被重複計算產生浪費, 同時遞歸更加耗費棧內存, 所以通常用一個二維矩陣(表格)來表示不同子問題的答案, 以實現更加高效的求解。
-
Talk is cheap ,Show me the code
翻閱很多資料,貌似python描述的比較少,這裏總結一下,用前面的圖解中的僞代碼重構下
揹包問題
多謝rubik_wong–0/1揹包問題,代碼參考如下
# 這裏使用了圖解中的吉他,音箱,電腦,手機做的測試,數據保持一致
w = [0, 1, 4, 3, 1] #n個物體的重量(w[0]無用)
p = [0, 1500, 3000, 2000, 2000] #n個物體的價值(p[0]無用)
n = len(w) - 1 #計算n的個數
m = 4 #揹包的載重量
x = [] #裝入揹包的物體,元素爲True時,對應物體被裝入(x[0]無用)
v = 0
#optp[i][j]表示在前i個物體中,能夠裝入載重量爲j的揹包中的物體的最大價值
optp = [[0 for col in range(m + 1)] for raw in range(n + 1)]
#optp 相當於做了一個n*m的全零矩陣的趕腳,n行爲物件,m列爲自揹包載重量
def knapsack_dynamic(w, p, n, m, x):
#計算optp[i][j]
for i in range(1, n + 1): # 物品一件件來
for j in range(1, m + 1): # j爲子揹包的載重量,尋找能夠承載物品的子揹包
if (j >= w[i]): # 當物品的重量小於揹包能夠承受的載重量的時候,才考慮能不能放進去
optp[i][j] = max(optp[i - 1][j], optp[i - 1][j - w[i]] + p[i]) # optp[i - 1][j]是上一個單元的值, optp[i - 1][j - w[i]]爲剩餘空間的價值
else:
optp[i][j] = optp[i - 1][j]
#遞推裝入揹包的物體,尋找跳變的地方,從最後結果開始逆推
j = m
for i in range(n, 0, -1):
if optp[i][j] > optp[i - 1][j]:
x.append(i)
j = j - w[i]
#返回最大價值,即表格中最後一行最後一列的值
v = optp[n][m]
return v
print '最大值爲:' + str(knapsack_dynamic(w, p, n, m, x))
print '物品的索引:',x
#最大值爲:4000
#物品的索引: [4, 3]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
優化揹包問題的遞歸方法
def MaxVal2(memo , w, v, index, last):
"""
得到最大價值
w爲widght
v爲value
index爲索引
last爲剩餘重量
"""
global numCount
numCount = numCount + 1
try:
#以往是否計算過分支,如果計算過,直接返回分支的結果
return memo[(index , last)]
except:
#最底部
if index == 0:
#是否可以裝入
if w[index] <= last:
return v[index]
else:
return 0
#尋找可以裝入的分支
without_l = MaxVal2(memo , w, v, index - 1, last)
#如果當前的分支大於約束
#返回歷史查找的最大值
if w[index] > last:
return without_l
else:
#當前分支加入揹包,剪掉揹包剩餘重量,繼續尋找
with_l = v[index] + MaxVal2(memo , w, v , index - 1, last - w[index])
#比較最大值
maxvalue = max(with_l , without_l)
#存儲
memo[(index , last)] = maxvalue
return maxvalue
w = [0, 1, 4, 3, 1] # 東西的重量
v = [0, 1500, 3000, 2000, 2000] # 東西的價值
numCount = 0
memo = {}
n = len(w) - 1
m = 4
print MaxVal2(memo , w, v, n, m) , "caculate count : ", numCount
# 4000 caculate count : 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
優化斐波那契數列的遞歸方法
多謝Python科學實驗—-動態規劃,也就是對應上面的重疊子問題的方法,備忘錄的遞歸方法
#Dynamic Method Experiment
import matplotlib.pyplot as plt
count=0;
#blank
def f(n):
global count
count=count+1
if n==1:
return 1
elif n==0:
return 1
else:
return f(n-1)+f(n-2)
# function calls count
def calc_f(n):
global count
count=0
f(n)
return count
#using memorization
mem={}
def mem_f(n):
global count,mem
count=count+1
if n in mem:
return mem[n]
else:
if n==1:
result=1
elif n==0:
result=1
else:
result=mem_f(n-1)+mem_f(n-2)
mem[n]=result
return result
def mem_calc_f(n):
global count
global mem
mem={}
count=0
mem_f(n)
return count
x=range(1,15)
y=[]
y2=[]
for i in x:
c=mem_calc_f(i)
y.append(c)
c2=calc_f(i)
y2.append(c2)
print "規模爲%d時計算了%d次 i=%d時,val=%d"%(i,c,i,mem_f(i))
print "規模爲%d時計算了%d次 i=%d時,val=%d"%(i,c2,i,f(i))
plt.plot(x,y)
plt.plot(x,y2)
plt.show()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
它的基本思想就是記錄已經計算過的值,避免重複計算。
如果使用裝飾器的寫法,則會優雅很多
from functools import wraps
def memo(func):
cache={}
@wraps(func)
def wrap(*args):
if args not in cache:
cache[args]=func(*args)
return cache[args]
return wrap
@memo
def fib(i):
if i<2: return 1
return fib(i-1)+fib(i-2)
fib(2)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
一些利用DP的筆試題
CPU雙核問題
網易筆試—動態規劃: 題目的大概意思:一種雙核CPU的兩個核能夠同時的處理任務,現在有n個已知數據量的任務需要交給CPU處理,假設已知CPU的每個核1秒可以處理1kb,每個核同時只能處理一項任務。n個任務可以按照任意順序放入CPU進行處理,現在需要設計一個方案讓CPU處理完這批任務所需的時間最少,求這個最小的時間。
輸入包括兩行:
第一行爲整數n(1 ≤ n ≤ 50)
第二行爲n個整數length[i](1024 ≤ length[i] ≤ 4194304),表示每個任務的長度爲length[i]kb,每個數均爲1024的倍數。
輸出一個整數,表示最少需要處理的時間。
問題實質是動態規劃問題,把數組分成兩部分,使得兩部分的和相差最小。
如何將數組分成兩部分使得兩部分的和的差最小?參考博客http://www.tuicool.com/articles/ZF73Af
思路:
差值最小就是說兩部分的和最接近,而且各部分的和與總和的一半也是最接近的。假設用sum1表示第一部分的和,sum2表示第二部分的和,SUM表示所有數的和,那麼sum1+sum2=SUM。假設sum1
w = [0, 3072, 3072, 7168, 3072, 1024] # 假設進入處理的的任務大小
w = map(lambda x:x/1024,w) # 轉化下
p = w # 這題的價值和任務重量一致
n = sum(w)/2 +1 # 揹包承重爲總任務的一半
optp = [[0 for j in range(n+1)] for i in range(len(w))]
for i in range(1,len(p)):
for j in range(1,n+1):
if j >= p[i]:
optp[i][j] = max(optp[i-1][j],p[i]+optp[i-1][j-w[i]])
else:
optp[i][j] = optp[i-1][j]
print optp[-1][-1]
print optp
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
# 揹包矩陣入下所示,第一列和第一行無效佔位符
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 3, 3, 3, 3, 3, 3, 3],
[0, 0, 0, 3, 3, 3, 6, 6, 6, 6],
[0, 0, 0, 3, 3, 3, 6, 7, 7, 7],
[0, 0, 0, 3, 3, 3, 6, 7, 7, 9],
[0, 1, 1, 3, 4, 4, 6, 7, 8, 9]]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
LIS問題
longest increasing subsequence問題,
- 寫的很棒,不再贅述DP動態規劃(Python實現)
# 講DP基本都會講到的一個問題LIS:longest increasing subsequence
# http://www.deeplearn.me/216.html
lis = [2 ,1, 5, 3, 6 ,4 ,8 ,9, 7]
d = [1]*len(lis)
res = 1
for i in range(len(lis)):
for j in range(i):
if lis[j] <= lis[i] and d[i] < d[j]+1:
d[i] = d[j]+1
if d[j] > res:
res = d[j]
print res
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
LCS問題
一個非常好的圖解教程:動態規劃 最長公共子序列 過程圖解
# 根據圖解教程寫的僞代碼,其實最後評論裏面的代碼就是我添加上去的
s1 = [1,3,4,5,6,7,7,8]
s2 = [3,5,7,4,8,6,7,8,2]
d = [[0]*(len(s2)+1) for i in range(len(s1)+1) ]
for i in range(1,len(s1)+1):
for j in range(1,len(s2)+1):
if s1[i-1] == s2[j-1]:
d[i][j] = d[i-1][j-1]+1
else:
d[i][j] = max(d[i-1][j],d[i][j-1])
print "max LCS number:",d[-1][-1]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
給定一個有n個正整數的數組A和一個整數sum
給定一個有n個正整數的數組A和一個整數sum,求選擇數組A中部分數字和爲sum的方案數。
當兩種選取方案有一個數字的下標不一樣,我們就認爲是不同的組成方案。
輸入描述:
輸入爲兩行:
第一行爲兩個正整數n(1 ≤ n ≤ 1000),sum(1 ≤ sum ≤ 1000)
第二行爲n個正整數A[i](32位整數),以空格隔開。
- 1
- 2
- 3
- 4
- 5
輸出描述:
輸出所求的方案數
- 1
示例1
輸入
5 15
5 5 10 2 3
- 1
- 2
輸出
4
- 1
#動態規劃算法。dp[i][j]代表用前i個數字湊到j最多有多少種方案。
#dp[i][j]=dp[i-1][j]; //不用第i個數字能湊到j的最多情況
#dp[i][j]+=dp[i-1][j-value[i]];用了i時,只需要看原來湊到j-value[i]的最多情況即可。並累加
num_ = 5
sum_ = 10
line = [5 ,5 ,10 ,2 ,3]
optp = [[1]+[0]*sum_ for i in range(num_+1)] # 第一列爲1的原因是和爲0的時候只有一種取法,就是什麼都不取
for i in range(1,num_+1):
for j in range(1,sum_+1):
if j - line[i-1] >=0:
optp[i][j] = optp[i-1][j] + optp[i-1][j-line[i-1]]
else:
optp[i][j] = optp[i-1][j]
print optp
0 1 2 3 4 5 6 7 8 9 10
0 [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
5 [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
5 [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1],
10 [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2],
2 [1, 0, 1, 0, 0, 2, 0, 2, 0, 0, 2],
3 [1, 0, 1, 1, 0, 3, 0, 2, 2, 0, 4]]
# 轉化爲揹包問題後,開始推,(注意第一行和第一列是預至位)比如說第一個數字是5,那麼從構成和爲1,怎麼取?當然沒得取,直到構成和爲5的時候,開始執行,如果用這個5,那麼還剩下5-5=0的和,0的和取法只有1,而如果不用5,取法只有0,所以爲1,之後重複推,再說第二個5,直到和爲5之前,都是0取法,到了5之後,兩種取法,一種是要不要這個新的5,如果要這個新的5,那麼剩下的和即5-5=0,一種取法,如果這個新的5不取,那以前能取到和爲5就上次循環中的一種取法,所以合起來兩種取法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
一個數組有 N 個元素,求連續子數組的最大和
一個數組有 N 個元素,求連續子數組的最大和。 例如:[-1,2,1],和最大的連續子數組爲[2,1],其和爲 3
輸入描述:
輸入爲兩行。
第一行一個整數n(1 <= n <= 100000),表示一共有n個元素
第二行爲n個數,即每個元素,每個整數都在32位int範圍內。以空格分隔。
- 1
- 2
- 3
輸出描述:
所有連續子數組中和最大的值。
- 1
示例1
輸入
3
-1 2 1
- 1
- 2
輸出
3
- 1
# 採用動態規劃的方法
# 設dp[i]表示以第 i個元素爲結尾的連續子數組的最大和,則遞推方程式爲 dp[i]=max{dp[i-1]+a[i], a[i]};
num = raw_input("")
line = raw_input("")
line = map(lambda x:int(x),line.split(" "))
num = int(num)
d =[0]*(num-1)
d.insert(0,line[0])
for i in range(1,num):
d[i] = max(d[i-1]+line[i],line[i])
print max(d)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
X*Y的網格迷宮
有一個X*Y的網格,小團要在此網格上從左上角到右下角,只能走格點且只能向右或向下走。請設計一個算法,計算小團有多少種走法。給定兩個正整數int x,int y,請返回小團的走法數目。
輸入描述:
輸入包括一行,逗號隔開的兩個正整數x和y,取值範圍[1,10]。
- 1
輸出描述:
輸出包括一行,爲走法的數目。
- 1
示例1
輸入
3 2
- 1
輸出
10
- 1
# 動態規劃,使用遞推方程d[i][j] = d[i-1][j] + d[i][j-1]
# 因爲可能從兩個方向走到同一個點,所以從上到下爲一種走法,從左到右是另一種走法
# 注意題目給的是x*y方格,所以是(x+1)*(y+1)個點
line = map(int, raw_input("").split(" "))
x = line[0]
y = line[1]
d = [[0]*(y+2) for i in range(x+2)]
for i in range(1,x+2):
for j in range(1,y+2):
if i==j and i==1:
d[i][j] = 1
else:
d[i][j] = d[i-1][j] + d[i][j-1]
print d[-1][-1]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
暗黑字符串
一個只包含’A’、’B’和’C’的字符串,如果存在某一段長度爲3的連續子串中恰好’A’、’B’和’C’各有一個,那麼這個字符串就是純淨的,否則這個字符串就是暗黑的。例如:
BAACAACCBAAA 連續子串”CBA”中包含了’A’,’B’,’C’各一個,所以是純淨的字符串
AABBCCAABB 不存在一個長度爲3的連續子串包含’A’,’B’,’C’,所以是暗黑的字符串
你的任務就是計算出長度爲n的字符串(只包含’A’、’B’和’C’),有多少個是暗黑的字符串。
輸入描述:
輸入一個整數n,表示字符串長度(1 ≤ n ≤ 30)
- 1
輸出描述:
輸出一個整數表示有多少個暗黑字符串
- 1
示例1
輸入
3
- 1
輸出
21
- 1
思路解析
#方式二,這麼low的方式是我根據上面的解析寫的。遞歸所以速度慢
num = int(raw_input(""))
def dark(num):
if num == 1:
return 3
elif num==2:
return 9
else:
return 2*dark(num-1) + dark(num-2)
print dark(num)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
# 方式一:別人家的代碼
n = int(raw_input())
dp = [0]*31
dp[0] = 3
dp[1] = 9
for i in xrange(2, n):
dp[i] = 2*dp[i-1]+dp[i-2]
print dp[n-1]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
最後
紙上得來終覺淺,這句話放在什麼時候都一樣,自己覺得動態規劃比較瞭解了,其實瞭解個屁,需要重新打打基礎!以後再過來更新理解。
致謝
<link href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css" rel="stylesheet">
</div>