https://blog.youkuaiyun.com/u013309870/article/details/75193592
这篇博客讲的非常好。
思想
其思想就是通过 记住求过的解来节省时间,在递归的算法中用特别爽。
菲波拉契算法
def fib0(n):
if (n <= 0):
return 0
if (n == 1):
return 1;
return fib0(n-1) + fib0(n-2)
0 1 2 3 5 8 13 21…
算法的执行流程:
很多节点都被重复执行,浪费时间。
因此记住已经调用过的节点以便重复使用,可以大幅节约时间。
自顶向下的备忘录法
def fib1_c(n, Memo):
if(Memo[n] != -1):
//如果已经求出了fib(n)的值直接返回,否则将求出的值保存在Memo备忘录中。
return Memo[n]
if(n <= 2):
Memo[n] = 1
else: Memo[n] = fib1_c(n-1, Memo) + fib1_c(n-2, Memo)
return Memo[n]
def fib1(n):
if (n<=0):
return 0;
Memo = np.zeros(n+1)
for i in range(0,n+1):
Memo[i] = -1
return fib1_c(n, Memo)
自底向上的动态规划
def fib2(n):
if(n <= 0):
return n;
memo_0,memo_1 = 0, 1
for i in range(2, n+1):
memo = memo_0 + memo_1
memo_0 = memo_1
memo_1 = memo
return memo
钢条切割
算法导论中的钢条切割问题:
def cut(p,n):
if(n==0):
return 0
q = -1
for i in range(1, n+1):
q = max(q,p[i-1]+cut(p,n-i))
return q;
def cut_c(p, n, r):
q = -1
if(r[n] >= 0):
return r[n]
if(n == 0):
q = 0
else:
for i in range(1, n+1):
q = max(q, p[i-1] + cut_c(p, n-i, r))
r[n] = q
return q
def cutMemo(p, n):
r = np.zeros(len(p)+1) - 1
return cut_c(p,n,r)
#自底向上的动态规划
def buttom_up_cut(p, n):
r = np.zeros(n + 1)
for i in range(1, n+1):
q = -1
for j in range(1,i+1):
q = max(q,p[j-1] + r[i-j])
r[i] = q
return r[n]
p=[1,5,8,9,10,17,17,20,24,30]
t00 = time.clock()
y4 = cut(p, 10)
t01 = time.clock()
t0 = t01 - t00
print(t0)
y5 = cutMemo(p, 5)
t10 = time.clock()
y6 = buttom_up_cut(p, 10)
t11 = time.clock()
t1 = t11 - t10
print(t1)
背包问题
这个例子讲的很清楚:
https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html
动态规划最关键的就是填表,i表示你前i个物品,j表示背包的承重,(i,j)单元格表示在可选前i个物品在承重j的选择下,如何使价值最大。
def findMax(w, value, y): #动态规划 填表
for i in range(1,len(w)): # 第i件物品
for j in range(1,np.size(y,1)): #j所代表的是承重j的情况
if(j < w[i]): # 第i件装不进
#y[i][j]代表前i件物品在承重j的情况下的最大价值
y[i][j] = y[i-1][j] #y[1][1]=y[0][1]
else: # 能装
#不装价值大
if(y[i-1][j] > y[i-1][j-w[i]] + value[i]):
y[i][j] = y[i-1][j]
#装了价值大
else:
y[i][j] = y[i-1][j-w[i]] + value[i]
#一直遍历到i=0结束为止,所有解的组成都会找到
然后是递归查找:
# 递归查找
def findWhat(i, j, w, value): #寻找有i件可选的情况下承重为j的时候 解的组成方式
if(i >= 0):
if(y[i][j] == y[i-1][j]):
item[i] = 0 # 标记物品未被选中
findWhat(i-1, j, w, value) #
elif(j-w[i]>=0 and y[i][j] == y[i-1][j-w[i]] + value[i]):
item[i] = 1 # 标记物品已被选中
findWhat(i-1, j-w[i], w, value) #回到装第i个物品之前的位置
return item
在上面的findmax程序中,记录下了整个表的情况,如果数据量太大,这会影响到内存开销,实际上我们的函数可以只记录我们想看的那一行,即只想知道在i个可选物品的情况下,j承重下的最大价值。
由
f
(
i
,
j
)
=
m
a
x
{
f
(
i
−
1
,
j
)
,
f
(
i
−
1
,
j
−
w
[
i
]
)
+
v
a
l
u
e
(
i
)
}
f(i,j) = max\{ f(i-1,j), f(i-1, j - w[i]) + value(i)\}
f(i,j)=max{f(i−1,j),f(i−1,j−w[i])+value(i)}
可知,欲知道
f
(
i
,
j
)
f(i,j)
f(i,j),只需知道
f
(
i
−
1
,
:
)
f(i-1,:)
f(i−1,:)这一样的价值.
# 利用状态转移公式简化空间,对j逆序访问,不然的话后面会被前面的已经修改的干扰
def findMaxBetter(w, value, y):
B = np.zeros([np.size(y,1)+1,1])
# B = y
for i in range(1,len(w) ):
#j需要逆序,因为不然的话
for j in range(np.size(y,1) ,0,-1):
if(j-w[i]>=0 and B[j] <= B[j - w[i]] + value[i] ):
B[j] = B[j - w[i]] + value[i]
# B(j)= max{B(j), B(j-w(i))+v(i)};
# 只考虑前i个物品的情况下的 不同的承重j对应的最优价值
return B
使用动态规划的条件
原问题较难,但可拆分成一个一个的子问题,且子问题的解显而易见。在递归算法的基础上,将解决过的子问题记录下来来避免重复计算。
简而言之:最优子结构+重叠子问题