【資料結構與演算法】三個經典案例帶你瞭解動態規劃
一、什麼是動態規劃
文章開頭說到,動態規劃是透過解決一個個小問題從而解決整個大問題的,因此我們一般會建立一個 陣列或 表來記錄每一個小問題的解決過程,那麼我們就能很清楚的看到整個問題的解決過程。
建立一個表其實就是透過陣列巢狀陣列的方式來實現的,假如我們需要一個下圖所示的表
我們只需要透過陣列內巢狀陣列即可
我們下面的三個案例也會反覆用到這樣的表,所以一定要理解
二、案例一:斐波那契數列
斐波那契數列是遞迴中最經典的一個例子,並且程式碼寫起來也極其得簡潔
看似簡潔的程式碼,其實內部有很多不足之處,我們可以用樹結構來分析一下遞迴的過程,假設我們要獲取斐波那契數列中第6個數,則遞迴過程如下圖所示
很明顯得看到,遞迴過程中,有很多值重複求了不止一次,例如 4 和 3 ,若我們要獲取得數比6還大的話,重複求值的現象會更加的明顯,這無疑是很消耗效能的,因此我們可以透過動態規劃的方式來消除這種現象。
假設我們要獲取斐波那契數列第n個數的值,首先我們可以建立個陣列,從第一個數開始,在該陣列中記錄每個索引位置上的值
這種方法就是一個最簡單的動態規劃,即透過陣列的形式記錄著求取斐波那契數列這個大問題過程中每一個小問題的值,最後可以直接透過陣列獲取到我們想要的值。
因為其沒有重複求值的缺點,因此效率肯定比遞迴的效率高,我們可以來驗證一下
從結果我們能很明顯地看到,僅僅第40個值對於動態規劃來說根本不算什麼,使用的時間幾乎為0,而遞迴卻因重複求值使用了2s以上的時間
三、案例二:尋找最大公共子串
首先先來看看問題需求:這裡有兩個字串,即
raven
和
havoc
,現在我們要封裝一個函式,來獲取這兩個字串的所有最大公共子串,結果就是最大的公共子串為
av
,並且最大的公共子串長度為2
首先看到這個問題,我覺得一般大家想到的辦法都是跟圖示一樣
但這是一種簡單粗暴的方法,把它用程式碼實現的話,中間也會有很多的重複比較的部分,所以我們這裡也可以透過動態規劃來解決此辦法,即建立一個表,用來記錄第一個字串的每一個字元跟第二個字串每一個字元的比較結果,最後再透過觀察表來判斷最大公共子串和最大公共子串的長度
假設現在有這樣兩個字串:
abaccd
和
badacef
,我們要求它倆的最大公共子串
可以先建一個 8 * 7 的表,如圖所示
行的表頭表示的是第一個字串的第n個字元;列的表頭表示的是第二個字串第m個字元
因此行的表頭或列的表頭為0對應的格子應當都為0,因為字串沒有第0個字元,最少是從第1個開始的,結果如下:
我們先找到行表頭為1的這一行從左往右看,表示拿第一個字串的第一個字元與第二個字串的每一個字元進行比較,若不相同,則在對應格子裡填0,表示的是連續相同字元的長度為0;若相同,則先看看該格子的左上角那個格子裡的數
n
是多少,然後在該格子裡填
n + 1
為什麼當相同時要在該格子中填入比左上角的值大1的數呢?因為左上角的格子表示的是第一個字串當前字元的前一個字元與第二個字串當前字元的前一個字元比較後的連續相同字元長度
我們來看一下第一行的填寫過程: /yunnan/
第二行表示的是拿第一個字串的第二個字元與第二個字串的每個字元的比較,過程如圖所示:
第三行表示的是拿第一個字串的第三個字元與第二個字串的每個字元的比較,過程如圖所示:
在上圖第三行的填寫過程中,第一個字串的第三個字元與第二個字串的第二個字元比較相同時,我們檢視了一下該格子左上角的值,即判斷了第一個字元當前字元的前一個字元與第二個字元當前字元的前一個字元比較後的連續字元長度為多少
剩下的三行填寫過程如下圖所示:
最終的表格如下圖所示:
/xian/
從表中我們可以看到,最大的公共子串長度為2,一共有兩個長度為2的公共子串,分別是第一個字串的第2個字元到第3個字元和第一個字串的第3個字元到第4個字元,即
ba
和
ac
根據上面的方法,我們來用程式碼封裝一下求取最大公共子串的函式
我們用上述例子來驗證一下該函式是否正確,同時我還列印了一下表的結果,大家可以跟例項中的比對一下是否正確
四、案例三:揹包問題
揹包問題也算是一個非常經典的問題,假設現在你的面前有4種珠寶,它們的重量分別為
3
、
3
、
4
、
5
,它們的價值分別為
4
、
6
、
7
、
9
,現在你有一個能裝下重量為
8
的物品,請問你會如何挑選才能使利益最大化?
當然最簡單的辦法就是寫出所有的組合,然後計算每種組合的價值,然後就能獲得利益最大化的方案
這用遞迴實現是非常簡單的,程式碼如下
正如我們文章開頭所說的,這樣的遞迴效率總歸是不太高的,因此我們要將其用動態規劃實現,並且我們將需求改變一下,不光要求出最大收益價值,還要知道是拿了哪幾樣物品。 xinyang/
同樣的,我們先建立一個表,用來記錄每一種物品在任一揹包容量下的最大收益
很明顯,當揹包容量為0時,我們能獲得的最大收益一定為0;表中物品編號為0的這一行全部都要填上0,因為這是我們新增的對照行,並沒有編號為0的物品,因此結果如圖所示:
現在我們從編號為1的物品開始,判斷其在揹包容量為
1 ~ 8
的情況下,我們能獲取到的最大利益為多少。顯而易見,物品1的重量為3,因此當揹包容量小於3時,最大收益都為0;當揹包容量大於等於3時,因為還沒有考慮別的物品,因此我們能獲取的最大收益就等於物品1的價值,即等於4,結果如圖所示:
接著我們考慮編號為2的物品在揹包容量為
1 ~ 8
的情況下,我們能獲取到的最大利益為多少。
首先知道物品2的重量為3,因此在揹包容量小於3時,我們無法放入物品2,那麼此時的最大收益就等於在當前揹包容量下,放入物品1的最大收益;
當揹包容量大於等於3時,我們能放入物品2,因此我們現在有兩種選擇:第一種就是不放物品2,那麼我們就只能放物品1,所以我們能獲得的最大收益就等於在此揹包容量下放入物品1的最大收益;第二種就是放物品2,因為我們已經放了物品2了,只剩一個物品1了,所以此時的最大收益就等於物品2的價值 + 揹包剩餘容量下放入物品1的最大收益。我們要取這兩種情況中收益最大的方案
填表過程如下圖所示: /tianjing/
接著我們又考慮編號為3的物品在揹包容量為
1 ~ 8
的情況下,我們能獲取到的最大利益為多少。
首先知道物品3的重量為4,因此在揹包容量小於4時,我們無法放入物品3,那麼我們還需要考慮的就有物品1和物品2,從上一步驟得知,物品2的最大收益時在考慮了物品1的基礎上得出的,因此我們只需要考慮放入物品2的最大收益即可,那麼此時的最大收益就等於在當前揹包容量下,放入物品2的最大收益;
當揹包容量大於等於4時,我們能放入物品4,與上一個步驟類似,我們有兩種選擇,即放物品3和不放物品3
填表結果如下圖所示:
同理,最後一行的填表過程如下圖所示:
最終的填表結果如下圖所示:
在表中可以很明顯地看到,我們在揹包容量為8的情況下,能獲取到的最大收益為15
此時,我們還需要倒著推回去,判斷一下是拿了哪幾樣物品才獲取到的最大收益
首先找到最大收益對應的格子為物品4,然後我們判斷一下該收益是否等於前一種物品(物品3)的最大收益,若等於,則表示沒有放入物品4;否則表示放入了物品4。 luoyang/
為什麼會這樣判斷呢?因為我們說過,在判斷一個物品在某揹包容量下的最大收益時,當物品重量大於揹包容量或者我們選擇不放入該物品時,此時的最大收益就等於前一種物品在此揹包容量下的最大收益
所以這裡能判斷,我們放入了物品4,則此時揹包容量只剩
8 - 5 = 3
,所以我們找到物品3在揹包容量等於3情況下最大收益對應的格子,同樣判斷一下上一種物品(物品2)的最大收益是否等於此格子中的最大收益,當前判斷為相等,因此我們沒有放入物品3
當前揹包容量仍為3,我們找到物品2在揹包容量等於3情況下最大收益對應的格子,判斷當前最大收益不等於上一種物品(物品1)在揹包容量為3情況下的最大收益,因此我們放入了物品2
則此時揹包容量為
3 - 3 = 0
了,無法再放入任何物品了,所以我們就可以得出
結論,我們在放入物品2和物品4的情況下收益最大,最大收益價值為15
上面講解了揹包問題的動態規劃思路,下面我們用程式碼來實現一下
我們透過上面舉得例子來驗證一下封裝好的函式的正確性,為了方便大家進行驗證,我同時列印了程式碼中的表,可以和前面例子中最終填完的表進行比對
可以看到,利用動態規劃的方式解決揹包問題,我們能清晰地看到整個問題地解決過程,還可以透過回溯的方式知道是放入了哪些物品獲得的最大收益
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2727595/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 動態規劃入門——動態規劃與資料結構的結合,在樹上做DP動態規劃資料結構
- [資料結構與演算法]-動態規劃之揹包演算法終極版資料結構演算法動態規劃
- 【演算法與資料結構】經典排序演算法總結演算法資料結構排序
- 前端你應該瞭解的資料結構與演算法前端資料結構演算法
- 資料庫與動態規劃資料庫動態規劃
- 【演算法學習筆記】動態規劃與資料結構的結合,在樹上做DP演算法筆記動態規劃資料結構
- Java實現十個經典排序演算法(帶動態效果圖)Java排序演算法
- 動態規劃經典問題----最長公共子序列動態規劃
- 動態規劃之經典數學期望和概率DP動態規劃
- 一文帶你入門動態規劃動態規劃
- 演算法(七):圖解動態規劃演算法圖解動態規劃
- 瞭解這幾個大資料應用案例,讓你更瞭解大資料!大資料
- JavaScript 資料結構與演算法之美 - 十大經典排序演算法JavaScript資料結構演算法排序
- 理論+實踐,帶你掌握動態規劃法動態規劃
- 演算法-動態規劃演算法動態規劃
- 演算法_動態規劃演算法動態規劃
- 動態規劃演算法動態規劃演算法
- 【LeetCode動態規劃#12】詳解買賣股票I~IV,經典dp題型LeetCode動態規劃
- 動態規劃演算法原理與實踐動態規劃演算法
- 動態規劃小結動態規劃
- 動態規劃 總結動態規劃
- Leetcode 題解演算法之動態規劃LeetCode演算法動態規劃
- 動態規劃之 KMP 演算法詳解動態規劃KMP演算法
- 演算法系列-動態規劃(1):初識動態規劃演算法動態規劃
- [leetcode初級演算法]動態規劃總結LeetCode演算法動態規劃
- Java資料結構與排序演算法 (三)Java資料結構排序演算法
- 前端演算法 - 動態規劃前端演算法動態規劃
- 為什麼你學不過動態規劃?告別動態規劃,談談我的經驗動態規劃
- 乾貨:圖解演算法——動態規劃系列圖解演算法動態規劃
- 帶你瞭解小程式生態
- leetcode總結——動態規劃LeetCode動態規劃
- 帶你瞭解動態路由協議OSPF基礎路由協議
- 用Python解決資料結構與演算法問題(三):線性資料結構之棧Python資料結構演算法
- 資料結構與演算法(一):帶你瞭解時間複雜度和空間複雜度到底是什麼?資料結構演算法時間複雜度
- 貪心演算法與動態規劃的區別演算法動態規劃
- 深入瞭解Redis資料結構Redis資料結構
- 資料結構與演算法-資料結構(棧)資料結構演算法
- 資料結構與演算法-圖解版資料結構演算法圖解