【Chapter 5】Dynamic Programming(下)
Dynamic Programming
上一节对矩阵链乘和LCS问题进行了形式化的分析,本节给出矩阵链乘及LCS的伪代码,并给出二者从DP到备忘录版本的转化。
矩阵链乘
Bottom-Up
pseudo code:
Matrix_Chain_Order(p): n=p.length-1 //p指的是矩阵规模序列,由上一节的分析知p的长度为矩阵个数+1 let m[1...n, 1...n] and s[1...n-1,2...n] be new tables for i=1 to n: m[i,i] = 0 //i=j时,矩阵链仅包含单个矩阵,不需要进行计算 for l=2 to n: //其中,l代表的是矩阵链的长度 for i=1 to n-l+1: //比如,时,n-l+1=3 j = i+l-1 //j=1+2-1=2 m[i,j] = INFINITE //将m[i,j]赋予一个很大的初值 for k=i to j-1: //寻找最优括号化的分裂点 q = m[i,k] + m[k+1,j] + p[i-1]*p[k]*p[j] if q<m[i,j]: m[i,j] = q s[i,j] = k return m and s
Top-Down
pseudo code:
Memoized_Matrix_Chain(p): n = p.length-1 let m[1...n, 1...n] be a new table for i=1 to n for j=1 to n m[i,j] = INFINITE //将m数组全部初始化为非常大的数,此后如果 //m中记录的值小于INFINITE,说明它的值得到了更新,即已经被备忘 return Lookup_Chain(m,p,1,n) Lookup_Chain(m,p,i,j): if m[i,j] < INFINITE: return m[i,j] //如果m[i,j]<INFINITE,说明它已经有备忘录当中的值了,此时剪枝 if i == j: m[i,j] = 0 else: for k=i to j-1: q = Lookup_Chain(m,p,i,k) + Lookup_Chain(m,p,k+1,j) + p[i-1]*p[k]*p[j] if q < m[i,j]: m[i,j] = q //S[i,j] = k return m[i,j]
LCS Problem
Bottom-Up
pseudo code:
LCS_Length(X, Y): // 相比Matrix Chain,LCS Problem非常的直观易理解 m = X.length n = Y.length let b[1...m, 1...n] and c[0...m, 0...n] be new tables for i=0 to m: c[i][0] = 0 for j=0 to n: c[0][j] = 0 for i=1 to m: for j=1 to n: if X[i] == Y[i]: c[i][j] = c[i-1][j-1] + 1 b[i][j] = '↖' else if c[i-1][j] > c[i][j-1]: c[i][j] = c[i-1][j] b[i][j] = '↑' else: c[i][j] = c[i][j-1] b[i][j] = '←' return c and b PRINT_LCS(b, X, i, j): // 类似于使用后序遍历的方法求解 if i == 0 or j == 0: return if b[i][j] == '↖': PRINT_LCS(b, X, i-1, j-1) print(X[i]) else if b[i][j] == '↑': PRINT_LCS(b, X, i-1, j) else: PRINT_LCS(b, X, i, j-1)
Top-Down
下面给出备忘录版本的LCS,伪代码的实现参考了:
- https://walkccc.me/CLRS/Chap15/15.4/
- https://blog.csdn.net/weixin_43931548/article/details/106077765
pseudo-code:
LCS_Recur(X, Y, i, j): if c[i][j] > -1: return c[i][j] //c[i][j]的值已经被备忘,剪枝 if i ==0 or j == 0: return c[i][j] = 0 if X[i] == Y[j]: return c[i][j] = LCS_Recur(X, Y, i-1, j-1) + 1 return c[i][j] = max(LCS_Recur(X, Y, i-1, j), LCS_Recur(X, Y, i, j-1))
0-1背包
由于0-1背包的相关知识占据了课程的很大部分,涉及动态规划中的0-1背包,贪心算法中的分数背包、0-1背包的2近似算法以及0-1背包的FPTAS算法等,因此在此处对0-1背包在动态规划中涉及到的内容进行记录。
问题描述
给定
n
n
n种物品和一个背包。物品
i
i
i的重量为
w
i
w_i
wi,价值为
v
i
v_i
vi,背包的容量为
C
C
C,请问应该如何选择装入背包当中的物品,使得装入背包中的物品的总价值最大?
0-1背包是特殊的整数规划问题:
max
∑
i
=
1
n
v
i
x
i
\max\sum^n_{i=1}v_ix_i
max∑i=1nvixi
s
.
t
.
s.t.
s.t.
∑
i
=
1
n
w
i
x
i
≤
C
\sum^n_{i=1}w_ix_i\leq{C}
∑i=1nwixi≤C
x
i
∈
{
0
,
1
}
,
1
≤
i
≤
n
x_i\in\{0,1\}, 1\leq{i}\leq{n}
xi∈{0,1},1≤i≤n
建立递归式
对于计科人应该是老生常谈了,经典的二维0-1背包递归式为:
m
(
i
,
j
)
=
max
{
m
(
i
+
1
,
j
)
,
m
(
i
+
1
,
j
−
w
i
)
+
v
i
}
,
j
≥
w
i
m(i,j)=\max\{m(i+1,j), m(i+1,j-w_i)+v_i\}, j\geq{w_i}
m(i,j)=max{m(i+1,j),m(i+1,j−wi)+vi},j≥wi
m
(
i
,
j
)
=
m
(
i
+
1
,
j
)
,
0
≤
j
<
w
i
m(i,j)=m(i+1,j), 0\leq{j}\lt{w_i}
m(i,j)=m(i+1,j),0≤j<wi
👆使用二维数组
m
m
m来记录背包当中的状态,其中
i
i
i代表第
i
i
i个物品,而
j
j
j表示当前背包容量,
m
(
i
,
j
)
m(i,j)
m(i,j)表示在背包容量为
j
j
j时,背包中物品的总价值。
第一行代表:如果当前背包容量大于等于第
i
i
i个物品的重量,可以考虑将该物品放入,需要比较将它放入或是不放入哪个价值更高;
第二行代表:当前背包容量已经小于第
i
i
i个物品的重量,无法将物品放入。
算法的时间复杂度为
O
(
n
C
)
O(nC)
O(nC),但是当
C
>
2
n
C\gt{2^n}
C>2n时,时间复杂度会达到
O
(
n
2
n
)
O(n2^n)
O(n2n)。
本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/4b7693b1ed.html
