動態規劃

算法思想

將待求解的問題分解爲若干個子問題,按順序求解子問題,同時前一子問題的解,爲後一子問題的求解提供了有用的信息。python

算法優勢

針對每個狀態只須要進行一次運算,以後就能夠重複利用這個狀態的值,從而減小了大量沒必要要的重複計算。也就是,一旦出現重複的子問題求解,優先考慮動態規劃方式求解,通常都會得到不少優化算法

算法特色

  • 求解子問題時只保存針對當前子問題的最優解
  • 重疊子問題,減小重複計算,對每個子問題只解一次,將其不一樣階段的不一樣狀態保存在一個數組中。
  • 子問題每每不是互相獨立的

動態規劃條件

  • 總問題可劃分爲多個子問題
  • 最優化原理,經過子問題的最優解能夠得到整個問題的最終最優解
  • 無後效性。當前狀態的子問題的解不會被以後狀態的子問題影響
  • 子問題的重疊性。動態規劃的子問題不獨立,單個子問題的最優解可使用以前已解決的子問題的解,解決冗餘,以空間換時間的技術,這也是動態規劃的優勢

求解步驟

  • 子問題的劃分。 按照必定的順序把整個問題劃分爲若干個規模相等的子問題
  • 子問題狀態的肯定。根據問題需求和子問題的各個屬性肯定子問題的」狀態「,同時須要知足無後效性。
  • 推導狀態轉移方程。 狀態轉移指的就是根據上一個狀態(或者叫上一個子問題的解)來獲取當前子問題解的過程
  • 邊界條件和初始值的肯定。因爲動態規劃是根據以前子問題的解來推導當前子問題的解,因此最初狀態的值必須肯定。邊界條件是用來描述結束狀態的,若是當前狀態徹底到達邊界,便視爲已經到達了最終狀態。

算法實例

問題1:斐波那契數列

數列由1,1開始,以後的斐波那契數列係數就由以前的兩數相加。數組

Sequence: 112358132134,……

狀態轉移方程:優化

詳細代碼spa

#!/usr/bin/python #-*-coding:utf-8 -*-

import numpy as np #遞歸方式
def rec_fib(n): if(n == 0): return 0 elif(n == 1): return 1
    else: fib = rec_fib(n-1) + rec_fib(n-2) return fib #使用變量存儲中間結果
def dp_fib(n): a, b = 0, 1
    while(n): a, b = b, a+b n -= 1
    return a if __name__ == '__main__': print(rec_fib(6)) print(dp_fib(6))

問題2:收益最大

縱軸是所做的任務編號,以及收益;橫軸是任務所對應的時間。選取規則爲任務時間不能重疊,使得收益最大。code

任務編號:       12345678 任務對應收益v(i):51846324

第i個任務選狀態(選和不選)最優解opt(i):blog

pre(i)爲以前的狀態,對應任務編號的先前狀態表:遞歸

i:       1  2  3  4  5  6  7   8 pre(i): 0  0  0  1  0  2  3   5 # 任務編號的先前狀態表 opt(i): 5  5  8  9  9  9  10  13   # 指望結果

詳細代碼utf-8

#!/usr/bin/python #-*-coding:utf-8 -*-

import numpy as np pre = [0, 0, 0, 1, 0, 2, 3, 5] arr = [1, 2, 3, 4, 5, 6, 7, 8] V =  [5, 1, 8, 4, 6, 3, 2, 4] # 遞歸方式
def rec_values(arr, n): if(n == 0): return 0 else: A = V[n - 1] + rec_values(arr, pre[n - 1]) B = rec_values(arr, n - 1) return max(A, B) # 數組存放中間值
def dp_values(arr): opt = np.zeros(len(arr) + 1) opt[0] = 0 for i in range(1, len(arr) + 1): A = V[i - 1] + opt[pre[i - 1]] B = opt[i - 1] opt[i] = max(A, B) return opt[len(opt) - 1] if __name__ == '__main__': print(rec_values(arr, 8)) # 13
    print(dp_values(arr)) # 13

問題3:不相鄰元素總和最大

給定一個數組,選取一些元素使得總和最大,選取規則爲不能連續取兩個元素,即選取的元素之間至少要間隔一個其它元素,每一個元素都有選與不選兩種可能,使得用動態規劃求解。ci

arr: 1,2,4,1,7,8,3

狀態轉移方程

出口條件

opt[0] = arr[0] opt[1] = max(arr[0], arr[1])

詳細代碼

#!/usr/bin/python #-*-coding:utf-8 -*-

import numpy as np arr = [1, 2, 4, 1, 7, 8, 3] #遞歸方式
def rec_opt(arr, i): if(i == 0): return arr[0] elif(i == 1): return max(arr[0], arr[1]) else: A = rec_opt(arr, i-2) + arr[i] B = rec_opt(arr, i-1) return max(A, B) #使用數組存儲中間結果
def dp_opt(arr): opt = np.zeros(len(arr)) opt[0] = arr[0] opt[1] = max(arr[0], arr[1]) for i in range(2, len(arr)): A = opt[i-2] + arr[i] B = opt[i-1] opt[i] = max(A, B) return opt[len(arr) - 1] if __name__ == '__main__': print(rec_opt(arr, 6)) print ( dp_opt(arr))

問題4:尋找和爲定值的數

給定一個數組,選取一些元素使得總和爲給定的值。

arr:  33441252 s: 9

狀態轉移方程

其中,subset(arr, i, s)表示爲數組arr當前第i個數字須要計算的和爲s

出口條件

if s ==0: return true elif i == 0: return arr[0] == s elif arr[i] > s: return subset(arr, i-1, s)

使用二維數組進行存儲結果

arr   i    0  1  2  3  4  5  6  7  8  9 s 3     0 F F F F T F F F F F 34    1 T 4     2 T 12    3 T 5     4 T 2     5    T

詳細代碼

#!/usr/bin/python #-*-coding:utf-8 -*- 

import numpy as np arr = [3,34,4,12,5,2] # 遞歸方式
def rec_subset(arr,i,s): if s == 0: return True elif i == 0: return arr[0]==s elif arr[i] > s: return rec_subset(arr,i-1,s) else: A=rec_subset(arr,i-1,s-arr[i]) B=rec_subset(arr,i-1,s) return A or B def rec_subset_test(): print(rec_subset(arr, len (arr)-1, 9))      #True
    print(rec_subset(arr, len (arr)-1, 10))     #True
    print(rec_subset(arr, len (arr)-1, 11))     #True
    print(rec_subset(arr, len (arr )-1, 12))    #True
    print(rec_subset(arr, len (arr)-1, 13 ))    #False

# 二維數組存儲結果
def dp_subset(arr, S): subset = np.zeros((len(arr), S+1), dtype = bool) # 表示對一個二維數組,取該二維數組第一維中的全部數據,第二維中取第0個數據,即取全部行的第1個數據
    subset[:, 0] = True # 全部列的第1個數據
    subset[0, :] = False subset[0, arr[0]] = True #i=0時,arr[0] = s = 3

    for i in range(1, len(arr)): for s in range(1, S+1): if(arr[i] > s): subset[i, s] = subset[i-1, s] else: A = subset[i-1, s-arr[i]] B = subset[i-1, s] subset[i, s] = A or B # r,c分別爲二維數組的行數和列數 
    r,c = subset.shape return subset[r-1, c-1] def dp_subset_test(): print(dp_subset(arr, 9))      #True
    print(dp_subset(arr, 10))     #True
    print(dp_subset(arr, 11))     #True
    print(dp_subset(arr, 12))     #True
    print(dp_subset(arr, 13))     #False

# 當.py文件被直接運行時,if下的代碼塊將被運行; # 當.py文件以模塊形式被導入時,if下的代碼塊不被運行。
if __name__ == '__main__': rec_subset_test() dp_subset_test()

 問題5:簡單01揹包

給定 n 種物品和一個容量爲 W 的揹包,物品 i 的重量是 wi,其價值爲 vi。應該如何選擇裝入揹包的物品,使得裝入揹包中的物品的總價值最大?

6種物品狀況: v = [8, 10, 6, 3, 7, 2] w = [4, 6, 2, 2, 5, 1] 揹包體積W爲12

狀態轉移方程

c(i,w)表示選擇第i件物品時獲得的物品最大總價值。

出口條件

if(i==0 or w==0)  return 0 if(wi>W) return c(i-1)(w)

使用二維數組進行存儲結果

i, j
揹包容量
0 1 2 3 4 5 6 7 8 9 10 11 12
物品編號 0
1
2
3
4
5
6
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 8 8 8 8 8 8 8 8 8
0 0 0 0 8 8 10 10 10 10 18 18 18
0 0 6 6 8 8 14 14 16 16 18 18 24
0 0 6 6 9 9 14 14 17 17 19 19 24
0 0 6 6 9 9 14 14 17 17 19 21 24
0 2 6 8 9 11 14 16 17 19 19 21 24

 

詳細代碼

#!/usr/bin/python #-*-coding:utf-8-*-

import numpy as np arr = [1, 2, 3, 4, 5, 6] #每一個物品編號
v   = [8, 10, 6, 3, 7, 2] #每一個物品價值
w   = [4, 6, 2, 2, 5, 1] #每一個物品重量
 W = 12 #揹包總價值

# 動態規劃
def rec_pack(v, w, n, W): if(n<=0 or W <= 0): return 0 elif(w[n-1] > W): return rec_pack(v, w, n-1, W) else: A = rec_pack(v, w, n-1, W-w[n-1]) + v[n-1] B = rec_pack(v, w, n-1, W) return max(A, B) # 數組存放中間值
def dp_pack(v, w, n, W): V = np.zeros((len(v) + 1, W+1), dtype = np.int32) V[:, 0] = 0 V[0, :] = 0 for i in range(1, len(v) + 1): for j in range(1, W+1): if(w[i-1] > j): #物品重量 > 揹包重量
                V[i,j] = V[i-1, j] else: A = v[i-1] + V[i-1, j-w[i-1]] B = V[i-1, j] V[i,j] = max(A, B) r,c = V.shape ''' for i in range(0, len(v) + 1): print("\n") #打印二維數組的值 for j in range(1, W+1): print("%d "% V[i,j]) '''
    return V[r-1, c-1] if __name__ == '__main__': print(dp_pack(v, w, 6, 12))     # 24
    print(rec_pack(v, w, 6, 12))    # 24

問題6:走臺階

有n級臺階,一我的每次上一級或者兩級,問有多少種走完n級臺階的方法

問題7:最長公共子序列

找到兩個字符串間的最長公共子序列。假設有兩個字符串sudjxidjs和xidjxidpolkj,其中djxidj就是他們的最長公共子序列。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息