一 、動態規劃(Dynamic Programming)
動態規劃是解決優化問題的最強大的設計技術。
分治法將問題劃分為不相交的子問題,然后遞歸地解決子問題,然后結合其解決方案來解決原始問題。
當子問題不是獨立的時,例如當它們共享相同的子問題時,將使用動態編程。在這種情況下,分治法可能會做比必要的事情更多的工作,因為它可以多次解決同一個子問題。
動態編程只解決一次每個子問題,并將結果存儲在表中,以便在需要時可以重復檢索它。
動態規劃是一種自下而上的方法-我們解決所有可能的小問題,然后結合以獲得更大問題的解決方案。
動態規劃是算法設計的一種范式,其中,通過實現子問題解決方案以及出現“ 最優原理 ” 的組合來解決優化問題。
動態規劃的特點:
當問題具有以下特征時,動態規劃將起作用:
- Optimal Substructure--最優子結構:如果最優解決方案包含最優子解決方案,那么問題將表現出最優子結構。
- Overlapping Subproblems--子問題重疊:當遞歸算法將重復訪問相同的子問題時,則問題將具有子問題重疊。
如果問題具有最優子結構,則可以遞歸定義最佳解決方案。如果一個問題有重疊的子問題,那么我們可以通過只計算每個子問題一次來改進遞歸實現。
如果問題沒有最佳子結構,則沒有基礎來定義遞歸算法以找到最佳解決方案。如果一個問題沒有重疊的子問題,那么使用動態編程將無濟于事。
如果子問題的空間足夠大(即輸入大小為多項式),則動態編程可能比遞歸更有效。
動態規劃的要素
基本上,三個要素是動態規劃算法的特征:
- Substructure--子結構:將給定問題分解為較小的子問題。用較小問題的解決方案表示原始問題的解決方案。
- Tablestructure--表結構:解決了子問題后,將結果存儲到表中的子問題中。這樣做是因為子問題解決方案已被多次重用,并且我們不想一遍又一遍地重復解決相同的問題。
- Bottom-up Computation--自下而上的計算:使用表格,將較小的子問題的解決方案組合起來以解決較大的子問題,并最終得出解決問題的解決方案。
注意:自下而上是指:
- 從最小的子問題開始。
- 結合他們的解決方案,可以解決規模不斷擴大的子問題。
- 直到解決原始問題為止。
動態規劃的組成部分
- Stages--階段:問題可以分為幾個子問題,稱為子階段。階段是給定問題的一小部分。例如,在最短路徑問題中,它們是由圖的結構定義的。
- States--狀態:每個階段都有幾個與之相關的狀態。最短路徑問題的狀態是到達的節點。
- Decision--決策:在每個階段,可以有多個選擇,應從其中做出最佳決策。在每個階段做出的決定都應該是最佳的;這稱為階段決策。
- Optimical policy--最優政策:這是一個決定每個階段決策的規則;如果策略是全局最優的,則稱為最優策略。這被稱為最佳貝爾曼原理。
- 給定當前狀態,其余每個狀態的最佳選擇不取決于先前的狀態或決策。在最短路徑問題中,沒有必要知道我們如何獲得節點,只要獲取結果就行。
- 考慮到階段j + 1已解決,因此存在一種遞歸關系,該關系確定階段j的最佳決策。
- 最后階段必須通過調用自身解決。
動態規劃算法的發展
它可以分為四個步驟:
- 表征最佳解決方案的結構。
- 遞歸定義最佳解決方案的值。與分治法一樣,將問題遞歸分為兩個或多個最佳部分。這有助于確定解決方案的外觀。
- 從下至上計算最佳解決方案的值(從最小的子問題開始)
- 從較小的子問題的計算值構造整個問題的最佳解決方案。
動態規劃的應用
- 0/1背包問題
- 數學優化問題
- 所有對最短路徑問題
- 可靠性設計問題
- 最長公共子序列(LCS)
- 飛行控制和機器人控制
- 分時:它計劃作業以最大化CPU使用率
二、編輯距離(Edit Distance)
概念
編輯距離的作用主要是用來比較兩個字符串的相似度的
基本的定義如下所示:
編輯距離,又稱Levenshtein距離(萊文斯坦距離也叫做Edit Distance),是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數,如果它們的距離越大,說明它們越是不同。許可的編輯操作包括將一個字符替換成另一個字符,插入一個字符,刪除一個字符。
這個概念是由俄羅斯科學家Vladimir Levenshtein在1965年提出來的,所以也叫 Levenshtein 距離。它可以用來做DNA分析,拼字檢測,抄襲識別等等。總是比較相似的,或多或少我們可以考慮編輯距離。
在概念中,我們可以看出一些重點那就是,編輯操作只有三種。插入,刪除,替換這三種操作,我們有兩個字符串,將其中一個字符串經過上面的這三種操作之后,得到兩個完全相同的字符串付出的代價是什么就是我們要討論和計算的。
編輯距離算法的迭代公式:
- 長度為m的字符串A,len(A) = m
- 長度為n的字符串B,len(B) = n
則A到B的編輯距離dp公式如下:
Q2: 為什么d是一個[m+1][n+1]大小的二維數組,為什么d數組要比字符串長度大一?
A2: 考慮A、B都為空字符串,我們還是需要一個[1][1]大小的數組記錄其編輯距離為0。更進一步也就是說,我們假設字符串A為"AC",則我們需要考慮['', 'A', 'AC']三種情況。
Q1: 如何理解d[i][j]的計算公式?
A1: 第(i,j)個位置的計算需要依賴于和它相鄰的三個元素(i-1,j)、(i, j-1)和(i-1,j-1),關鍵是哪一個對應刪除,哪一個對應于插入,哪一個對應于替換?如果此時A[i]不等于B[j],則(下面為全文最重要部分):
對于(i-1,j-1)時,d(i-1, j-1)表示完成從A[0,i-1]到B[0,j-1]的編輯次數,即現在A[0,i-1]=B[0,j-1],對于(i,j),我們直接把A[i]替換成B[j]即完成編輯。因此(i-1,j-1)對應于把A[i]用B[j]替換的一次操作
對于(i-1, j)時,d(i-1, j)表示完成從A[0, i-1]到B[0, j]的編輯次數,即現在A[0,i-1]=B[0,j],對于(i,j),我們直接把A[i]刪除即可完成編輯,因此(i-1,j)對應于把A[i]刪除的一次操作
對于(i, j-1)時,d(i, j-1)表示完成從A[0, i]到B[0, j-1]的編輯次數,即現在A[0,i]=B[0,j-1],對于(i,j),我們直接用B[j]插入到A[i]的位置即可完成編輯,因此(i,j-1)對應于把B[j]插到A[i]的一次操作
代碼示例:
# A Naive recursive Python program to fin minimum number
# operations to convert str1 to str2
def editDistance(str1, str2, m, n):
# If first string is empty, the only option is to
# insert all characters of second string into first
if m == 0:
return n
# If second string is empty, the only option is to
# remove all characters of first string
if n == 0:
return m
# If last characters of two strings are same, nothing
# much to do. Ignore last characters and get count for
# remaining strings.
if str1[m-1]== str2[n-1]:
return editDistance(str1, str2, m-1, n-1)
# If last characters are not same, consider all three
# operations on last character of first string, recursively
# compute minimum cost for all three operations and take
# minimum of three values.
return 1 + min(editDistance(str1, str2, m, n-1), # Insert
editDistance(str1, str2, m-1, n), # Remove
editDistance(str1, str2, m-1, n-1) # Replace
)
# Driver program to test the above function
str1 = "sunday"
str2 = "saturday"
print(editDistance(str1, str2, len(str1), len(str2)) )
# This code is contributed by Bhavya Jain
結果:
3
參考鏈接:
分治法(Divide-and-Conquer Algorithm)經典例子分析
What is Dynamic Programming
分治法(Divide-and-Conquer Algorithm)經典例子分析
編輯距離算法(Edit Distance)
Edit Distance | DP-5