動態規划——DP算法(Dynamic Programing)


一、斐波那契數列(遞歸VS動態規划)

1、斐波那契數列——遞歸實現(python語言)——自頂向下

遞歸調用是非常耗費內存的,程序雖然簡潔可是算法復雜度為O(2^n),當n很大時,程序運行很慢,甚至內存爆滿。

1 def fib(n):
2     #終止條件,也就是遞歸出口
3     if n == 0 or n == 1:
4         return 1
5     else:
6         #遞歸條件
7         return (fib(n-1) + fib(n - 2))

2、斐波那契數列——動態規划實現(python語言)——自底向上

動態規划——將需要重復計算的問題保存起來,不需要下次重新計算。對於斐波那契數列,算法復雜度為O(n)。

1 def dp_fib(n):
2     #初始化一個數組,用於存儲記錄計算的結果。
3     res = [None] * (n + 1)
4     #前兩項設置為1。
5     res[0] = res[1] = 1
6     #自底向上,將計算結果存入數組內。
7     for i in range(2, (n + 1)):
8         res[i] = res[i-1] + res[i-2]
9     return res[n]

3、方法概要

   (1)構造一個公式,它表示一個問題的解是與它的子問題的解相關的公式:

     

  (2)為這些子問題做索引,以便於它們能夠在表中更好的存儲與檢索(用數組存儲)。

  (3)以自底向上的方法來填寫這個表格;首先填寫最小的子問題的解。

  (4)這就保證了當我們解決一個特殊的子問題時,可以利用比它更小的所有可利用的子問題的解。

總之,因為在上世紀40年代(計算機普及很少時),這些規划設計是與“列表”方法相關的,因此被稱為動態規划——Dynamic Programing。

 

 二、動態規划算法——思想簡介

1、DP算法思想

   (1)將待求解的問題分解稱若干個子問題,並存儲子問題的解而避免計算重復的子問題,並由子問題的解得到原問題的解。

   (2)動態規划算法通常用於求解具有某種最有性質的問題。

   (3)動態規划算法的基本要素:最優子結構性質和重疊子問題。

      最優子結構性質:問題的最優解包含着它的子問題的最優解。即不管前面的策略如何,此后的決策必須是基於當前狀態(由上一次的決策產生)的最優決策。

      重疊子問題:在用遞歸算法自頂向下解問題時,每次產生的子問題並不總是新問題,有些問題被反復計算多次。對每個子問題只解一次,然后將其解保存起來,

            以后再遇到同樣的問題時就可以直接引用,不必重新求解。

2、DP算法——解決問題的基本特征

   (1)動態規划一般求解最值(最優、最大、最小、最長)問題;

   (2)動態規划解決 的問題一般是離散的,可以分解的(划分階段的)。

   (3)動態規划結局的問題必須包含最優子結構,即可以有(n-1)的最優推導出n的最優。

3、DP算法——解決問題的基本步驟

   動態規划算法的四個步驟:

    (1)刻畫最優解的結構特性。(一維、二維、三維數組);

    (2)遞歸的定義最優解。(狀態轉移方程)

    (3)以自底向上的方法來計算最優解。

    (4)從計算得到的解來構造一個最優解。

 4、求解例子——求階乘 n!

 

 1 #遞歸實現求階乘
 2 def multiply(n):
 3     if n == 0 or n == 1:
 4         return 1
 5     return n * multiply(n -1)
 6 
 7 
 8 #動態規划實現求階乘
 9 def dp_multiply(n):
10     temp = [None] * (n + 1)
11     temp[0] = 1
12     temp[1] = 1
13     for i in range(2, n + 1):
14         temp[i] = i * temp[i - 1]
15     return temp[n]

 三、動態規划——常見例題

1、求解最長不降子序列

 

  (1)方法一:普通方法,算法復雜度為O(n^2)。

      假設原始的數列為數組 a

      分析:

        刻畫結構特性:用F[ i ] 表示前 i 項最長不下降子序列的長度;

        狀態轉移方程:如果a [ i ] >=a [ j ],  F[i] = max(F[i], F[j] + 1)  其中,0 <= j < i

        數據存儲:自底向上求解最小子結構最優解存入數組

 其中,pre[ i ]表示以元素a [ i ] 為結尾的最長不降序列的前一個元素索引(也就是以a[i]結尾的最長不降序列的倒數第二個元素)。存儲這個值是為了方便輸出最長的不降序列。

 

 

 1 def Longest_Increaseing(a):
 2     F = [1] * len(a)
 3     pre = [0] * len(a)
 4     for i in range(1, len(a)):
 5         for j in range(i):
 6             if a[i] >= a[j]:
 7                 F[i] = max(F[i], F[j] + 1)
 8                 pre[i] = j
 9     return F, pre
10 a = [5,2,8,6,3,6,9,7]
11 F, pre = Longest_Increaseing(a)

#這里只是能獲得兩個數組,其中F[i]的最大值就是最長不降序列的長度。

接下來,輸出最長的不降序列的元素值,請看下面的代碼:

 1 def Longest_Increaseing(a):
 2     F = [1] * len(a)
 3     pre = [0] * len(a)
 4     for i in range(1, len(a)):
 5         for j in range(i):
 6             if a[i] >= a[j]:
 7                 F[i] = max(F[i], F[j] + 1)
 8                 pre[i] = j
 9     return F, pre
10 a = [5,2,8,6,3,6,9,7]
11 F, pre = Longest_Increaseing(a)
12 
13 #最長序列的索引
14 k = F.index(max(F))
15 #輸出序列的列表
16 result = [None] * F[k]
17 flag = True
18 Len = F[k]
19 while flag:
20     result[Len - 1] = a[k]
21     k = pre[k]
22     if k == 0:
23         flag = False
24     Len -= 1
25 print(result)

#輸出結果:[2, 3, 6, 9]


 

  (2)方法二:時間復雜度為O(n * log(n))

     參考博文:最長不下降子序列 NlogN && 輸出序列       https://www.cnblogs.com/milky-w/p/8431333.html

 

2、求解最長的公共子序列

 

 

 

 

 

 

 

 求解最長公共子序列代碼如下(python語言):

 1 import numpy as np
 2 def LCS(str1, str2):
 3     #獲取兩個序列的長度
 4     m = len(str1)
 5     n = len(str2)
 6     #生成一個存儲計算子問題的二位矩陣,並將元素初始化為0。
 7     #這個矩陣的尺寸比兩個序列的尺寸分別大1個單位。
 8     #對於這個矩陣,第一行和第一列元素值必然為0。
 9     #C[i][j]的含義是:Xi = (x1, x2, x3,..., xi)和Yj = (y1, y2, x3,..., yj)的最長公共子序列
10     C = np.zeros((m+1, n+1), dtype=int)
11     b = np.zeros((m+1, n+1), dtype=int)
12 
13     for i in range(1, m+1):
14         for j in range(1, n+1):
15             #請注意這里為什么是i-1和j-1,因為其實C[1][1]表示的是
16             # 兩個序列的首個元素的最長公共子序列,對應的是str1[0]和str2[0]
17             if str1[i-1] == str2[j-1]:
18                 C[i][j] = C[i-1][j-1] + 1
19                 b[i][j] = 1      #表示對角線方向
20             else:
21                 if C[i][j-1] <= C[i-1][j]:
22                     b[i][j] = 2     #表示朝上方向
23                 else:
24                     b[i][j] = 3     #表示朝左方向
25                 C[i][j] = max(C[i][j-1], C[i-1][j])
26     return C, b
27 
28 test1 = ['b', 'd','c', 'a', 'b', 'a']
29 test2 = ["a","b","c","b","d","a","b"]
30 a, b = LCS(test2, test1)
31 
32 print(a)
#矩陣a存儲的是公共子序列的長度,最大值就是最大公共子序列的長度

[[0 0 0 0 0 0 0]
[0 0 0 0 1 1 1]
[0 1 1 1 1 2 2]
[0 1 1 2 2 2 2]
[0 1 1 2 2 3 3]
[0 1 2 2 2 3 3]
[0 1 2 2 3 3 4]
[0 1 2 2 3 4 4]]

33 print(b)
#這里: 1表示對角線方向、2表示朝上、3表示朝左,主要是為了求具體的子序列用的。

[[0 0 0 0 0 0 0]
[0 2 2 2 1 3 1]
[0 1 3 3 2 1 3]
[0 2 2 1 3 2 2]
[0 1 2 2 2 1 3]
[0 2 1 2 2 2 2]
[0 2 2 2 1 2 1]
[0 1 2 2 2 1 2]]

 

 接下來是輸出最長公共子序列:

 1 import numpy as np
 2 def LCS(str1, str2):
 3     #獲取兩個序列的長度
 4     m = len(str1)
 5     n = len(str2)
 6     #生成一個存儲計算子問題的二位矩陣,並將元素初始化為0。
 7     #這個矩陣的尺寸比兩個序列的尺寸分別大1個單位。
 8     #對於這個矩陣,第一行和第一列元素值必然為0。
 9     #C[i][j]的含義是:Xi = (x1, x2, x3,..., xi)和Yj = (y1, y2, x3,..., yj)的最長公共子序列
10     C = np.zeros((m+1, n+1), dtype=int)
11     b = np.zeros((m+1, n+1), dtype=int)
12 
13     for i in range(1, m+1):
14         for j in range(1, n+1):
15             #請注意這里為什么是i-1和j-1,因為其實C[1][1]表示的是
16             # 兩個序列的首個元素的最長公共子序列,對應的是str1[0]和str2[0]
17             if str1[i-1] == str2[j-1]:
18                 C[i][j] = C[i-1][j-1] + 1
19                 b[i][j] = 1      #表示對角線方向
20             else:
21                 if C[i][j-1] <= C[i-1][j]:
22                     b[i][j] = 2     #表示朝上方向
23                 else:
24                     b[i][j] = 3     #表示朝左方向
25                 C[i][j] = max(C[i][j-1], C[i-1][j])
26     return C, b
27 
28 def Print_Lcs(b, X, i , j):
29     if i == 0 or j == 0:
30         return
31     if b[i][j] == 1:
32         Print_Lcs(b, X, i-1, j-1)
33         print(X[i-1])  #為什么是i-1,因為b矩陣的行比X的行長一個單位,而且只輸出相等的值,表示公共元素。
34     elif b[i][j] == 2:
35         Print_Lcs(b, X, i-1, j)
36     else:
37         Print_Lcs(b, X, i, j-1)
38 
39 
40 if __name__ == '__main__':
41     test1 = ['b', 'd','c', 'a', 'b', 'a']
42     test2 = ["a","b","c","b","d","a","b"]
43     a, b = LCS(test2, test1)
44     Print_Lcs(b, test2, 7, 6)

#輸出的結果是: b、c、b、a 。(請注意這里結果不唯一,因為最長子序列長度為4, 存在三個序列長度為4的子序列)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM