ダイナミックプログラミング
複雑な問題を解決するための比較的簡単な方法のサブ問題に元の問題による方法。
ダイナミックプログラミングは、多くの場合、次善の下部のプロパティの問題や課題を重ねるために適しています。
基本的な考え方
与えられた問題を解決するために、我々は、組み合わせたソリューションは、元の問題の解に到達するために下位問題ために、その異なる部分(すなわち部分問題を)理解する必要があります。通常、非常にのみ、それによって計算量を減らし、それぞれの子のために一回の動的計画法で問題を解決しようとするため、多くのサブ問題で同様:ステータの問題に対する解決策が計算されると、それはメモリストレージである、あなたは同じサブ問題を必要とするので、次回ときダイレクトルックアップテーブルのソリューション。このアプローチでは、入力の大きさについて繰り返し質問の数は指数関数的な成長に特に有用でした。
最適なサブ構造:最適解は、その問題の次善のソリューションが含まれている、問題は構造的な次善を有することを特徴とします。
重複部分問題は:トップダウン再帰アルゴリズムソリューションは、各サブ問題は、常に新たな問題が生じていないときに、いくつかのサブ問題は、反復です。動的なプログラミングアルゴリズムは、このサブ問題の重複性質上、一度だけ、各副問題の解決策を使用することであり、そのソリューションは、できるだけ多くを使用して、これらのサブ問題の解決後、テーブルを保存します。
分割統治と動的計画
一般的に:両方は、元の問題は、準最適な構造特性を有する必要が副問題(簡単に解決するための小さなプログラム)小規模の数に分ける分割ルールの元の問題です。その後、サブマージの問題の解決策、元の問題の解決策を形成します。
異なっている:独立したとして分解分割方法の下位問題は、再帰によって行わ。
分解され、動的プログラミングサブ問題は、お互いに、重なる部分との接触を持っていると理解、通常、反復DOを覚えておく必要があります。
例
少なくとも変動問題を与えます
最小コインチェンジャー質問は:コインの異なる金種の数(各硬貨の数無限の数)のすべての種類を考慮すると、コインのいくつかの金種のお金のいくつかの組み合わせと、コインの最小数のように。(アルゴリズムの詳細)
実生活では、我々は$ 1を見つけるために、このような変更は13元、私たちが最初に見つける10元を必要とするときのように、貪欲なアルゴリズムを使用する2元を見つけるために傾向があります。これは、現実の生活のコイン(法案)の種類に特別なのです。私たちが利用可能に変更した場合1,2,5,9,10です。私たちは$ 18に変更すると、戦略貪欲アルゴリズムは次のとおりです。10 + 5 + 2 + 1、4、しかし、明らかにあなたは2 9元、デザイン、動的プログラミングを使用することができます。
class DynamicProgramming:
def __init__(self):
pass
def min_change(self, coin_list, change, min_coins, coins_used):
for cents in range(change + 1): # 依次循环从0到所需兑换面值的每一个面值
coin_count = cents # 初始化最优解为当前面值数,即兑换硬币个数,最大为每个都是1元硬币
new_coin = 1 # 初始化找零硬币面值列表中的面值
for j in [c for c in coin_list if c <= cents]: # 在不大于要找零的硬币面值列表中循环
if min_coins[cents - j] + 1 < coin_count: # 寻找最小的min_coins[cents - j]+1,其中j属于coin_list
coin_count = min_coins[cents - j] + 1 # 临时保存当前面值的最优解,当前最优硬币个数
new_coin = j # 将当前硬币面值j临时保存为当前找零面值在找零硬币面值列表中的对应值
min_coins[cents] = coin_count # 记录当前找零面值在找零最优解列表中的最优解
coins_used[cents] = new_coin # 记录当前找零面值在找零硬币面值列表中对应的值
return min_coins[change] # 返回待找零数值的最优解
# 获取最终找零的硬币面值
def print_coins(self, coins_used, change):
coins = []
while change > 0:
this_coin = coins_used[change] # 从找零硬币面值列表中获取对应的硬币面值
coins.append(this_coin)
change = change - this_coin # 去除该面值后继续循环获取
return coins
dp = DynamicProgramming()
change = 12
coin_list = [1, 5, 7, 10]
coins_used = [0] * (change + 1)
coin_count = [0] * (change + 1)
res = dp.min_change(coin_list, change, coin_count, coins_used)
coins = dp.print_coins(coins_used, change)
print("最少硬币:", res)
print("硬币兑换列表:", coins)
最長共通部分列
class DynamicProgramming:
def __init__(self):
pass
def LCS_length(self, seq1, seq2):
'''
name: 最长公共子序列
desc:
:param seq1: 序列1
:param seq2: 序列2
:return: 最长序列表, 最长个数
'''
m, n = len(seq1) + 1, len(seq2) + 1
Lsc = []
for i in range(m):
'''
注意python列表初始化的问题
'''
elem = []
for j in range(n):
if i == 0 or j == 0:
elem.append(0)
else:
elem.append(None)
Lsc.append(elem)
for i in range(1, m):
for j in range(1, n):
if seq1[i - 1] == seq2[j - 1]: # -1 保持序列一致
Lsc[i][j] = Lsc[i - 1][j - 1] + 1
else:
Lsc[i][j] = max(Lsc[i - 1][j], Lsc[i][j - 1])
return Lsc, Lsc[m-1][n-1]
def show_lcs(self, Lcs, seq1, seq2):
'''
:param Lcs: 最长序列表
:param seq1: 序列1
:param seq2: 序列2
:return: 公共序列
'''
i, j = len(seq1), len(seq2)
show = []
while i != 0 and j != 0:
if seq1[i-1] == seq2[j-1]:
i -= 1
j -= 1
show.append(seq1[i])
elif Lcs[i-1][j] < Lcs[i][j-1]:
j -= 1
elif Lcs[i][j-1] <= Lcs[i-1][j]:
i -= 1
return show
0-1ナップザック問題
class DynamicProgramming:
def __init__(self):
pass
def knap_sack_01(self, kvs, capacity):
m, n = len(kvs) + 1, capacity + 1
M = []
for i in range(m):
'''
注意python列表初始化的问题
'''
elem = []
for j in range(n):
if i == 0 or j == 0:
elem.append(0)
else:
elem.append(None)
M.append(elem)
for i in range(1, m):
for j in range(1, n):
if j - kvs[i-1][0] >= 0:
M[i][j] = max(M[i-1][j], M[i-1][j-kvs[i-1][0]] + kvs[i-1][1])
else:
M[i][j] = M[i-1][j]
return M, M[m-1][n-1]
def show_sack(self, M, kvs):
show = []
m, n = len(M) - 1, len(M[0]) - 1
while True:
if M[m][n] == M[m-1][n]:
m -= 1
else:
show.append(kvs[m-1])
n = n - kvs[m - 1][0]
m -= 1
if m == 0:
break
return show
最適なバイナリ検索ツリー
class DynamicProgramming:
def __init__(self):
pass
def optimal_BST(self, p, q):
'''
name: 最优二叉搜索树
:param p:
:param q:
:return:
'''
n = len(p) # n = N + 1
root = [[0] * n for i in range(n)]
e = [[0] * (n + 1) for i in range(n + 1)] # 当前的搜索期望
w = [[0] * (n + 1) for i in range(n + 1)] # 每个子树上的概率和
for i in range(1, n + 1):
# 当j = i-1就是虚拟键的情况
e[i][i-1] = q[i-1]
w[i][i-1] = q[i-1]
for l in range(1, n):
for i in range(1, n - l + 1):
j = i + l - 1
e[i][j] = 0x7fffffff # 先赋值无穷
w[i][j] = w[i][j-1] + p[j] + q[j] # w递归 ,注意pq的下标相同,也就是假如当用到k2,d2时他们调用取得是相同的下标,所以p,q长度不同把p前面弄了一个-1
for r in range(i, j + 1):
t = e[i][r - 1] + e[r + 1][j] + w[i][j] # e[i][j]的递归
if t < e[i][j]:
e[i][j], root[i][j] = t, r # e[i][j]取最小值
return root
def construct_optimal_BST(self, root, i, j):
n = len(root) - 1
if i == 1 and j == n:
print('root is {}'.format(root[1][n]))
if i < j:
print("k{}是k{}的左孩子".format(root[i][root[i][j]-1], root[i][j]))
self.construct_optimal_BST(root, i, root[i][j]-1)
if root[i][j] + 1 < j:
print("k{}是k{}的右孩子".format(root[root[i][j] + 1][j], root[i][j]))
self.construct_optimal_BST(root, root[i][j]+1, j)
if i == j:
print("d{}是K{}的左孩子".format(i - 1, i))
print("d{}是K{}的右孩子".format(i, i))
if i > j:
print("d{}是K{}的右孩子".format(j, j))
p = [-1, 0.15, 0.1, 0.05, 0.10, 0.20]
q = [0.05, 0.10, 0.05, 0.05, 0.05, 0.10]
root = dp.optimal_BST(p, q)
print(root)
print(len(root))
dp.construct_optimal_BST(root, 1, len(root)-1)
ます。https://my.oschina.net/gain/blog/3059919で再現