一.動態規劃,一般用於求解最優化子結構問題和子問題重疊的狀況算法
(1)最優子結構:問題的最優解由相關子問題的最優解組合而成,而這些子問題能夠獨立求解app
(2)子問題重疊:不一樣的子問題具備公共的子子問題。好比:問題4 能夠分出(3,2,1,0)四種子問題,但其中問題3和問題2均可以分出問題0和問題1,這就是公共的子子問題ide
算法描述:優化
1.刻畫一個最優解的結構特徵spa
最優子結構:問題的最優解由相關子問題的最優解組合而成,而這些子問題能夠獨立求解設計
(1)原問題涉及多個子問題,3d
(2)假定某種策略產生的子問題能能夠獲得最優解code
(3)給定可得到的最優解的策略後,肯定該策略會產生哪些子問題以及如何刻畫子問題空間blog
(4)做爲構成原問題最優解的組成部分,每一個子問題的解就是它自己的最優解,並且這些子問題的解不互相影響遞歸
2.遞歸地定義最優解的值
(1)子問題的無關性:子問題相互之間不共享資源
(2)子問題的重疊性:兩個子問題其實是同一個子問題,只是做爲不一樣問題的子問題出現而已
重疊子問題的求解:問題的遞歸算法會反覆地求解相同的子問題,而不是一直生成新的子問題
3.計算最優解的值,一般採用自底向上的方法
自底向上法:恰當地定義子問題的規模,使任何子問題的求解都只依賴更小的子問題的求解,對子問題按規模從小到大求解並保存
4.利用計算出的信息構造一個最優解
原問題最優解通常是子問題最優解的和,外加選擇子問題的策略
二:動態規劃和分治法
1.相同:兩者都要求原問題具備最優子結構性質,都是將原問題分紅若干個子問題
2.不一樣:
(1)分治法:子問題之間相互獨立,遞歸求解,合併子問題的解成爲原問題的解
(2)動態規劃:子問題具備重疊性,通常自底向上求解,最優解須要合理地構造
三.鋼條切割
1.問題描述:已知不一樣長度的鋼條價格不一樣,問給定一段鋼條,怎樣切割才能使價格收益最大?
2.簡單分析:通常來講。長度爲n英寸的鋼條共有2**(n-1)中不一樣的切割方案(在距離鋼條左端 i 英寸處,總能夠選擇切割或不切割兩種狀況)
3.通常求解:把規模n的問題分解成兩段規模分別是 i 和 n-i 的子問題,接着求解這兩段的最優切割收益,並在全部可能的兩段切割方案中選取組合收益最大者,構成原問題最優解
4.更簡單的遞歸求解:固定左邊切下長度爲 i 的一段,只對右邊長度 n-i 的一段進行切割(遞歸求解)
1 #固定左邊切割的i段,只對右邊n-i進行切割(求解最優解) 2 def cut_rod(p, n): 3 if n == 0: 4 return 0 5 q = float("-inf") 6 for i in range(0, n): 7 q = max(q, p[i] + cut_rod(p, n - i - 1)) 8 return q 9 10 p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30] 11 for i in range(1, 11): 12 print("長度爲", i, "的鋼條的切割最大收益爲:", end='') 13 print(cut_rod(p, i)) 14 ------------------------------------------------------------------- 15 長度爲 1 的鋼條的切割最大收益爲:1 16 長度爲 2 的鋼條的切割最大收益爲:5 17 長度爲 3 的鋼條的切割最大收益爲:8 18 長度爲 4 的鋼條的切割最大收益爲:10 19 長度爲 5 的鋼條的切割最大收益爲:13 20 長度爲 6 的鋼條的切割最大收益爲:17 21 長度爲 7 的鋼條的切割最大收益爲:18 22 長度爲 8 的鋼條的切割最大收益爲:22 23 長度爲 9 的鋼條的切割最大收益爲:25 24 長度爲 10 的鋼條的切割最大收益爲:30
5.動態規劃:仔細安排求解順序,對每一個子問題只求解一次,並將結果保存下來。用空間來節省時間
1 #帶備忘的自頂向下法 2 def memorized_cut_rod(p, n): 3 #p價格列表,n規模大小 4 r = []#每一個子問題的解的列表,先初始化全爲負無窮 5 for i in range(n): 6 r.append(float("-inf")) 7 return memorized_cut_rod_aux(p, n, r) 8 9 10 def memorized_cut_rod_aux(p, n, r): 11 #r[i]規模爲i的最優切割收益 12 if r[n - 1] >= 0:#若是子問題的解在備忘錄中存在,則直接返回 13 return r[n - 1] 14 if n == 0:#若是長度爲0,則收益爲0 15 q = 0 16 else: 17 q = float("-inf")#先初始化收益爲 18 for i in range(0, n):#計算收益,取組合收益最大者,固定左邊收益,遞歸求街右邊最優解 19 q = max(q, p[i] + memorized_cut_rod_aux(p, n - i - 1, r)) 20 r[n - 1] = q#把收益保存到備忘錄中 21 return q 22 23 print("動態規劃方法:") 24 p=[1,5,8,9,10,17,17,20,24,30] 25 for i in range(1,11): 26 print("長度爲",i,"的鋼條的切割最大收益爲:",end='') 27 print(memorized_cut_rod(p,i)) 28 ----------------------------------------------- 29 動態規劃方法: 30 長度爲 1 的鋼條的切割最大收益爲:1 31 長度爲 2 的鋼條的切割最大收益爲:5 32 長度爲 3 的鋼條的切割最大收益爲:8 33 長度爲 4 的鋼條的切割最大收益爲:10 34 長度爲 5 的鋼條的切割最大收益爲:13 35 長度爲 6 的鋼條的切割最大收益爲:17 36 長度爲 7 的鋼條的切割最大收益爲:18 37 長度爲 8 的鋼條的切割最大收益爲:22 38 長度爲 9 的鋼條的切割最大收益爲:25 39 長度爲 10 的鋼條的切割最大收益爲:30
1 #自底向上 2 def bottom_up_cut_rod(p,n): 3 #p收益列表,n問題規模 4 r=[]#r子問題的解的列表 5 for i in range(n+1): 6 r.append(float("-inf")) 7 r[0]=0 8 for j in range(n):#升序求解規模爲j的解 9 q=float("-inf") 10 for i in range(j+1): 11 q=max(q,p[i]+r[j-i]) 12 r[j+1]=q#把規模爲j的子問題的最優解存入r[j+1] 13 return r[n] 14 15 p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30] 16 print("動態規劃方法2:") 17 for i in range(1,11): 18 print("長度爲",i,"的鋼條的切割最大收益爲:",end='') 19 print(bottom_up_cut_rod(p,i)) 20 ----------------------------------------------------------------- 21 動態規劃方法2: 22 長度爲 1 的鋼條的切割最大收益爲:1 23 長度爲 2 的鋼條的切割最大收益爲:5 24 長度爲 3 的鋼條的切割最大收益爲:8 25 長度爲 4 的鋼條的切割最大收益爲:10 26 長度爲 5 的鋼條的切割最大收益爲:13 27 長度爲 6 的鋼條的切割最大收益爲:17 28 長度爲 7 的鋼條的切割最大收益爲:18 29 長度爲 8 的鋼條的切割最大收益爲:22 30 長度爲 9 的鋼條的切割最大收益爲:25 31 長度爲 10 的鋼條的切割最大收益爲:30
1 #每一個子問題不只保存最優收益值,還保存對應的切割方案 2 def extended_bottom_up_cut_rod(p,n): 3 r=[]#保存最優解 4 s=[]#保存最優解對應的第一段鋼條的切割長度 5 for i in range(n+1): 6 r.append(float("-inf")) 7 s.append(float("-inf")) 8 r[0]=0 9 s[0]=0 10 #j+1表示鋼條的長度,i+1表示第一段的長度 11 for j in range(n): 12 q=float("-inf") 13 for i in range(j+1): 14 if q<p[i]+r[j-i]: 15 q=p[i]+r[j-i] 16 s[j+1]=i+1 17 r[j+1]=q 18 return r,s 19 20 def print_cut_rod_solution(p,n): 21 #打印長度n的完整最優切割方案 22 (r,s)=extended_bottom_up_cut_rod(p,n) 23 while n>0: 24 print(s[n],' ',end='') 25 n=n-s[n] 26 print() 27 28 p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30] 29 for i in range(1,11): 30 print("長度爲",i,"的鋼條的切割最大收益的切割方案爲:",end='') 31 print_cut_rod_solution(p,i) 32 ----------------------------------------------------------------------- 33 長度爲 1 的鋼條的切割最大收益的切割方案爲:1 34 長度爲 2 的鋼條的切割最大收益的切割方案爲:2 35 長度爲 3 的鋼條的切割最大收益的切割方案爲:3 36 長度爲 4 的鋼條的切割最大收益的切割方案爲:2 2 37 長度爲 5 的鋼條的切割最大收益的切割方案爲:2 3 38 長度爲 6 的鋼條的切割最大收益的切割方案爲:6 39 長度爲 7 的鋼條的切割最大收益的切割方案爲:1 6 40 長度爲 8 的鋼條的切割最大收益的切割方案爲:2 6 41 長度爲 9 的鋼條的切割最大收益的切割方案爲:3 6 42 長度爲 10 的鋼條的切割最大收益的切割方案爲:10
四.矩陣鏈
1.前提:(1)矩陣乘法知足結合律(2)兩矩陣相乘時應具備相容性:前一個矩陣的列數等於後一個矩陣的行數
(3)A(10*100)A(100*5)A(5*50)的規模10*100*5次標量乘法,而A(10*100)【A(100*5)A(5*50)】的規模10*5*50次標量乘法,雖然結果同樣,但計算速度不一樣
2.問題描述:多個A1A2...An矩陣相乘,試給出最快的相乘順序,其中Ai的規模爲pi-1*pi(4)徹底括號化:單一矩陣,或者是兩個徹底括號化的矩陣乘積鏈的積,且已外加括號
3.通常分析:窮舉顯然不可取,對於一個n個矩陣的鏈,P(n)表示可供選擇的括號化方案的數量,任意徹底括號化矩陣能夠表示成兩個徹底括號化的部分積相乘的形式,而兩個部分積的劃分點在第k個矩陣和第k+1個矩陣之間簡而言之,一個矩陣鏈能夠分紅兩個矩陣鏈相乘
4.動態規劃求解:
(1)最優子結構:問題的最優解由相關子問題的最優解組合而成,而這些子問題能夠獨立求解
原問題分解:A[i]...A[j],假設它最優括號化方案的分割點在A[k],則把原問題分爲兩個子問題,A[i]...A[k]和A[k+1]...A[j],這兩個子問題均可以獨立求出他們的最優解
(組合)計算A[i]...A[j]的代價等於計算A[i]...A[k]和A[k+1]...A[j]的代價,再加上二者相乘的計算代價
(2)一個遞歸求解方案
假設最優分割點k是已知的,記m[i,j]表示計算矩陣A[i,j]的代價,矩陣A(i)的大小爲p(i-1)*p(i),則由上得m[i,j]=m[i,k]+m[k+1,j]+p(i-1)p(k)p(j)
又k其實只有j-i中取值,則取其中使代價最大的k
(3)計算子問題最優解的值,對於A[1]...A[n]從底到上遞歸計算它的每一個子問題的最優解,並保存
(4)構造原問題最優解,子問題最優解組合成原問題最優解
1 #計算子問題最優代價 2 def matrix_chain_order(p): 3 #p輸入序列p(0)-p(n) 4 n = len(p) - 1#n矩陣鏈長度 5 m = [[0 for j in range(0, n + 1)] for i in range(0, n + 1)]#m矩陣保存每個子問題的最優計算代價 6 s = [[0 for j in range(0, n + 1)] for i in range(0, n + 1)]#s矩陣記錄最優值對應的分割點k 7 for l in range(2, n + 1): 8 for i in range(1, n - l + 2): 9 j = i + l - 1 10 m[i][j] = float("inf") 11 for k in range(i, j): 12 q = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j] 13 if q < m[i][j]:#取對於[i,j]中每個k,取達到最優值(最小值)的哪個 14 m[i][j] = q 15 s[i][j] = k 16 return m, s 17 18 #構造最優解 19 def print_optimal_parens(s, i, j): 20 #s矩陣保存子問題最優解的矩陣 21 if i == j: 22 print("A", i, end='') 23 else: 24 print("(", end='') 25 print_optimal_parens(s, i, s[i][j]) 26 print_optimal_parens(s, s[i][j] + 1, j) 27 print(")", end='') 28 29 p = [30,35,15,5,10,20,25] 30 m, s = matrix_chain_order(p) 31 print(m) 32 print(s) 33 print_optimal_parens(s,1 , 6) 34 ----------------------------------------------------------------- 35 [[0, 0, 0, 0, 0, 0, 0], [0, 0, 15750, 7875, 9375, 11875, 15125], [0, 0, 0, 2625, 4375, 7125, 10500], [0, 0, 0, 0, 750, 2500, 5375], [0, 0, 0, 0, 0, 1000, 3500], [0, 0, 0, 0, 0, 0, 5000], [0, 0, 0, 0, 0, 0, 0]] 36 [[0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 3, 3, 3], [0, 0, 0, 2, 3, 3, 3], [0, 0, 0, 0, 3, 3, 3], [0, 0, 0, 0, 0, 4, 5], [0, 0, 0, 0, 0, 0, 5], [0, 0, 0, 0, 0, 0, 0]] 37 ((A 1(A 2A 3))((A 4A 5)A 6))
五.最長公共子序列(longest-common-subsequence)(LCS)
1.前提:(1)子序列:x是y的一個子序列:序列x包含序列y,並且y在x中的下標嚴格遞增
(2)公共子序列:序列z既是序列x的子序列,又是序列y的子序列,則z爲x和y的公共子序列
2.問題描述:求序列x和序列y的最長的公共子序列
3.動態規劃求解:
(1)最優子結構特徵:定義X的第i前綴爲Xi=(x1...xi),兩個序列的LCS包含兩個序列的前綴的LSC
(2)一個遞歸解:定義c[i,j]表示Xi和Yj的LCS長度
(3)計算LCS的長度
(4)構造LCS
1 #計算LCS長度 2 def lcs_length(X, Y): 3 #LCS長度 4 m = len(X) 5 n = len(Y) 6 c = [[0 for j in range(n + 1)] for i in range(m + 1)]#c矩陣保存X和Y的LCS長度 7 b = [[" " for j in range(n + 1)] for i in range(m + 1)]#b保存子問題最優解 8 for i in range(1, m + 1): 9 for j in range(1, n + 1): 10 #計算每一個i,j的最優解, 11 # 僅僅依賴因而否xi=yi以及c[i-1,j],c[i,j-1],c[i-1,j-1]的值,這些值都會在c[i,j]之間計算出來 12 if X[i - 1] == Y[j - 1]: 13 c[i][j] = c[i - 1][j - 1] + 1 14 b[i][j] = "向左上" 15 elif c[i - 1][j] >= c[i][j - 1]: 16 c[i][j] = c[i - 1][j] 17 b[i][j] = "向上" 18 else: 19 c[i][j] = c[i][j - 1] 20 b[i][j] = "向左" 21 return c, b 22 23 #構造LCS,根據LCS長度 24 def print_lcs(b, X, i, j): 25 if i == 0 or j == 0: 26 return 27 if b[i][j] == "向左上": 28 print_lcs(b, X, i - 1, j - 1) 29 print(X[i - 1], end='') 30 elif b[i][j] == "向上": 31 print_lcs(b, X, i - 1, j) 32 else: 33 print_lcs(b, X, i, j - 1) 34 35 X = ["A", "B", "C", "B", "D", "A", "B"] 36 Y = ["B", "D", "C", "A", "B", "A"] 37 c, b = lcs_length(X, Y) 38 for i in range(len(X) + 1): 39 for j in range(len(Y) + 1): 40 print(c[i][j], ' ', end='') 41 print() 42 for i in range(len(X) + 1): 43 for j in range(len(Y) + 1): 44 print(b[i][j], ' ', end='') 45 print() 46 print_lcs(b, X, len(X), len(Y)) 47 --------------------------------------------- 48 0 0 0 0 0 0 0 49 0 0 0 0 1 1 1 50 0 1 1 1 1 2 2 51 0 1 1 2 2 2 2 52 0 1 1 2 2 3 3 53 0 1 2 2 2 3 3 54 0 1 2 2 3 3 4 55 0 1 2 2 3 4 4 56 57 向上 向上 向上 向左上 向左 向左上 58 向左上 向左 向左 向上 向左上 向左 59 向上 向上 向左上 向左 向上 向上 60 向左上 向上 向上 向上 向左上 向左 61 向上 向左上 向上 向上 向上 向上 62 向上 向上 向上 向左上 向上 向左上 63 向左上 向上 向上 向上 向左上 向上 64 BCBA
六.最優二叉搜索樹
1.背景:要在一堆數據中進行查找操做,把每條數據關聯一個關鍵字,每條數據的搜索頻率不一樣,有的頻繁地查找,有的不多進行查找,把全部關鍵字(在關鍵字序列中的)和僞關鍵字(不在關鍵字序列中的)設計成二叉樹的結構,從上到下表示查找順序,要求頻繁出現的關鍵字靠近樹根
2.問題描述:對於一個給定的機率集合,咱們但願構造一棵搜索代價最小的二叉搜索樹
3.注意:二叉搜索樹不必定是高度最矮的,並且,機率最高的關鍵字也不必定出如今二叉搜索樹的根結點
4.動態規劃求解:
(1)最優子結構特徵
考慮一棵二叉搜索樹的任意子樹,關鍵字ki...kj和僞關鍵字d(i-1)...dj,若是它是一棵最優二叉搜索樹的子樹,那它必然也是最優的
(2)遞歸算法
子問題域:求解包含關鍵字ki...kj的最優二叉搜索樹
求解:從ki...kj中選擇一個根節點kr,構造ki...kr-1的左子樹(最優二叉搜索樹)和kr+1...kj的右子樹(最優二叉搜索樹)
(3)計算指望搜索代價
1 def optimal_bst(p, q, n): 2 #p關鍵字機率列表,q僞關鍵字機率列表,n輸入規模 3 #e代價矩陣,w機率矩陣,root記錄包含關鍵字ki...kj的子樹的根 4 e = [[0 for j in range(n + 1)] for i in range(n + 2)] 5 w = [[0 for j in range(n + 1)] for i in range(n + 2)] 6 root = [[0 for j in range(n + 1)] for i in range(n + 1)] 7 for i in range(n + 2): 8 e[i][i - 1] = q[i - 1] 9 w[i][i - 1] = q[i - 1] 10 for l in range(1, n + 1): 11 for i in range(1, n - l + 2): 12 j = i + l - 1 13 e[i][j] = float("inf") 14 w[i][j] = w[i][j - 1] + p[j] + q[j] 15 for r in range(i, j + 1): 16 t = e[i][r - 1] + e[r + 1][j] + w[i][j] 17 if t < e[i][j]: 18 e[i][j] = t 19 root[i][j] = r 20 return e, root 21 22 23 24 p = [0, 0.15, 0.1, 0.05, 0.1, 0.2] 25 q = [0.05, 0.1, 0.05, 0.05, 0.05, 0.1] 26 e, root = optimal_bst(p, q, 5) 27 for i in range(5 + 2): 28 for j in range(5 + 1): 29 print(e[i][j], " ", end='') 30 print() 31 for i in range(5 + 1): 32 for j in range(5 + 1): 33 print(root[i][j], " ", end='') 34 print() 35 ----------------------------------------------------- 36 0 0 0 0 0 0.1 37 0.05 0.45000000000000007 0.9 1.25 1.75 2.75 38 0 0.1 0.4 0.7 1.2 2.0 39 0 0 0.05 0.25 0.6 1.2999999999999998 40 0 0 0 0.05 0.30000000000000004 0.9 41 0 0 0 0 0.05 0.5 42 0 0 0 0 0 0.1 43 0 0 0 0 0 0 44 0 1 1 2 2 2 45 0 0 2 2 2 4 46 0 0 0 3 4 5 47 0 0 0 0 4 5 48 0 0 0 0 0 5