动态规划
动态规划概叙
动态规划算法的概念
动态规划算法与分治法类似,都是将原问题分解称若干个子问题,求解子问题,然后结合这些子问题的解获得原问题的解。但是动态规划分解的子问题不互相独立,故有些子问题被重复计算,动态规划会将子问题的解保存下来,这样可以避免大量重复计算
动态规划的基本要素
-
最优子结构性质:最优解问题包含了子问题的最优解问题
-
重叠子问题性质:问题分解后,子问题会被重复计算。
动态规划算法的步骤
- 找出最优解的性质,并刻画其结构特征
- 递归地定义最优解
- 以自底向上的方式计算最优解
- 根据计算最优值时的消息,构造最优解
范例
矩阵连乘问题
问题描述
给定n个矩阵 A 1 , A 2 , … … , A n {\boldsymbol{A_1},\boldsymbol{A_2},……,\boldsymbol{A_n}} A1,A2,……,An,其中 A i \boldsymbol{A_i} Ai与 A i + 1 \boldsymbol{A_{i+1}} Ai+1是可乘的。考虑怎么加括号能使 A 1 A 2 ⋅ ⋅ A n \boldsymbol{A_1}\boldsymbol{A_2}··\boldsymbol{A_n} A1A2⋅⋅An的计算量最小。
分析最优解的结构
计算 A n \boldsymbol{A_n} An的最优次序所包含的计算矩阵子链 A [ 1 : k ] \boldsymbol{A{[1:k]}} A[1:k]和 A [ k + 1 : n ] \boldsymbol{A[k+1:n]} A[k+1:n]的次序也是最优的。
证明
如果计算 A [ 1 : k ] \boldsymbol{A[1:k]} A[1:k]的次序所需要的计算量更少,则用此次序替换原来计算 A [ 1 : n ] \boldsymbol{A[1:n]} A[1:n]的计算量将比最有次序所需的计算量更少,这是个矛盾。
递归关系
设计算 A [ i : j ] \boldsymbol{A[i:j]} A[i:j]所需的最少数乘次数为 m [ i ] [ j ] m[i][j] m[i][j],则原问题的最优值为 m [ 1 ] [ n ] m[1][n] m[1][n]。、
有:
m
[
i
]
[
j
]
=
{
0
i
=
j
m
i
n
(
m
[
i
]
[
j
]
+
m
[
k
+
1
]
[
j
]
+
p
i
−
1
p
k
p
j
)
i
<
j
m[i][j]=\begin{cases} 0\quad i=j\\ min(m[i][j]+m[k+1][j]+p_{i-1}p_{k}p_{j})\quad i<j \end{cases}
m[i][j]={0i=jmin(m[i][j]+m[k+1][j]+pi−1pkpj)i<j
代码
void MatrixChain(int *p,int n,int **m,int **s)
{
for(int i=1;i<=n;i++)m[i][i]=0;
for(int r=2;r<=n;r++)
{
for(int i=1;i<=n;i++)
{
int j = i+r-1;
m[i][j] = m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1;k<j;k++)
{
int t = m[i][k]+m[k][j]+p[i-1]*p[k]*p[j];
if(t<m[i][j])
{
m[i][j]=t;
s[i][j]=k;
}
}
}
}
}
构造最优解
void Traceback(int i,int j,int**s)
{
if(i==j)
{
cout<<"A"<<i;
return;
}
cout<<"(";
Traceback(i,s[i][j],s);
cout<<")";
cout<<"(";
Traceback(s[i][j]+1,j,s);
cout<<")";
}
最长公共子序列
问题描述
给定两个序列 X = { x 1 , x 2 , … … , x m } X=\left\{x_1,x_2,……,x_m\right\} X={x1,x2,……,xm}和 Y = { y 1 , y 2 , y 3 , … … , y n } Y=\left\{y_1,y_2,y_3,……,y_n\right\} Y={y1,y2,y3,……,yn},找出 X X X和 Y Y Y的最长公共子序列
最长公共子序列的结构
设序列 X = { x 1 , x 2 , x 3 , … … , x m } X=\left\{x_1,x_2,x_3,……,x_m\right\} X={x1,x2,x3,……,xm}和 Y = { y 1 , y 2 , … … , y n } Y=\left\{y_1,y_2,……,y_n\right\} Y={y1,y2,……,yn}的最长公共子序列 Z = { z 1 , z 2 , … … , z k } Z=\left\{z_1,z_2,……,z_k\right\} Z={z1,z2,……,zk}。则
- 若 x m = y n x_m=y_n xm=yn,则 z k = x m = y n z_k=x_m=y_n zk=xm=yn,且 Z k − 1 Z_{k-1} Zk−1是 X m − 1 X_{m-1} Xm−1和 Y n − 1 Y_{n-1} Yn−1的最长公共子序列
- 若 x m ≠ y n x_m\neq y_n xm=yn 且 z k ≠ x m z_k \neq x_m zk=xm,则 Z Z Z是 X m − 1 X_{m-1} Xm−1和 Y Y Y的最长公共子序列
- 若 x m ≠ y n x_m\neq y_n xm=yn 且 z k ≠ y n z_k \neq y_n zk=yn,则 Z Z Z是 X X X和 Y n − 1 Y_{n-1} Yn−1的最长公共子序列
递归结构
用
c
[
i
]
[
j
]
c[i][j]
c[i][j]记录序列
X
i
X_i
Xi和
Y
j
Y_j
Yj的最长公共子序列的长度。其中
X
=
{
x
1
,
x
2
,
…
…
,
x
i
}
X=\left\{x_1,x_2,……,x_i\right\}
X={x1,x2,……,xi};
Y
j
=
{
y
1
,
y
2
,
…
…
,
y
j
}
Y_j = \left\{y_1,y_2,……,y_j\right\}
Yj={y1,y2,……,yj}。
c
[
i
]
[
j
]
=
{
0
i
>
0
;
j
=
0
c
[
i
−
1
]
[
j
−
1
]
+
1
i
,
j
>
0
;
x
i
=
y
j
m
a
x
c
[
i
]
[
j
−
1
]
,
c
[
i
−
1
]
[
j
]
i
,
j
>
0
;
x
i
≠
y
j
c[i][j]=\begin{cases} 0\quad i>0;j=0\\ c[i-1][j-1]+1\quad i,j>0;x_i=y_j \\ max{c[i][j-1],c[i-1][j]}\quad i,j>0;x_i \neq y_j\\ \end{cases}
c[i][j]=⎩
⎨
⎧0i>0;j=0c[i−1][j−1]+1i,j>0;xi=yjmaxc[i][j−1],c[i−1][j]i,j>0;xi=yj
代码
void LCSLength(int m,int n,char*x,char*y,int**c,int**b)
{
int i,j;
for(int i=1;i<=m;i++)c[i][0]=0;
for(int i=1;i<=n;i++)c[0][i]=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(x[i]==y[j])
{
c[i][j]=c[i-1][j-1]+1;
b[i][j]=1;
}
else if(c[i-1][j]>c[i][j-1])
{
c[i][j]=c[i-1][j];
b[i][j]=2;
}
else if(c[i][j-1]>c[i-1][j])
{
c[i][j]=c[i][j-1];
b[i][j]=3;
}
}
}
}
构造最优解
void traceback(int i,int j,char*x,int**b)
{
if(i==0||j==0)return;
else if(b[i][j]==1)
{
traceback(i-1,j-1,x,b);
printf("%c",x[i]);
}
else if(b[i][j]==2)
{
traceback(i-1,j,x,b);
}
else if(b[i][j]==3)
traceback(i,j-1,x,b);
}
最大字段和
描述
给定由n个整数(可能为负整数)组成的序列 a 1 , a 2 , … … , a n a_1,a_2,……,a_n a1,a2,……,an,求该序列形如 ∑ k = i j a k \sum_{k=i}^{j}a_k ∑k=ijak
的字段和的最大值。当所有整数均为负整数时定义其最大字段和为0。依次定义,所求最优值为
max
{
0
,
max
1
≤
i
≤
j
≤
n
∑
k
=
i
j
a
k
}
\max\left\{0,\max\limits_{1 \leq i \leq j \leq n}\sum\limits_{k=i}^{j}a_k\right\}
max{0,1≤i≤j≤nmaxk=i∑jak}
解决办法
分治法解决
将所给的序列 a [ 1 : n ] a[1:n] a[1:n]分为长度相等的两段 a [ 1 : n ] a[1:n] a[1:n]分为长度相等的两段 a [ 1 : n / 2 ] a[1:n/2] a[1:n/2]和 a [ n / 2 + 1 : n ] a[n/2+1:n] a[n/2+1:n],分别求出这两段的最大字段和,则 a [ 1 : n ] a[1:n] a[1:n]的最大字段和有三种情形:
- a [ 1 : n ] a[1:n] a[1:n]的最大子段和与 a [ 1 : n / 2 ] a[1:n/2] a[1:n/2]的最大子段和相同
- a [ 1 : n ] a[1:n] a[1:n]的最大子段和与 a [ n / 2 + 1 , n ] a[n/2+1,n] a[n/2+1,n]的最大子段和相同
- a [ 1 : n ] a[1:n] a[1:n]的最大子段和为 ∑ k = i j a k \sum\limits_{k=i}^{j}a_k k=i∑jak。
int MaxSubSum(int*a,int left,int right)
{
int sum =0;
if(left==right)sum=a[left]>0?a[left]:0;
else{
int mid=(left+right)/2;
int lsum = MaxSubSum(a,left,mid);
int rsum = MaxSubSum(a,mid+1,right);
int ls=0,lefts=0;
for(int i=mid;i>=left;i--)
{
lefts+=a[i];
if(ls<lefts)ls=lefts;
}
int rs=0,rights=0;
for(int i=mid+1;i<=right;i++)
{
rights+=a[i];
if(rs<rights)rs=rights;
}
sum=max(lsum,max(rsum,ls+rs));
}
return sum;
}
动态规划
设 b [ j ] = max 1 ≤ i ≤ j { ∑ k = i j a [ k ] } ( 1 ≤ j ≤ n ) b[j]=\max\limits_{1 \leq i \leq j}\left\{\sum\limits_{k=i}^{j}a[k]\right\}(1 \leq j \leq n) b[j]=1≤i≤jmax{k=i∑ja[k]}(1≤j≤n)
则有如下递归表达式:
b
[
j
]
=
max
{
b
[
j
−
1
]
+
a
[
j
]
,
a
[
j
]
}
1
≤
j
≤
n
b[j]=\max\left\{b[j-1]+a[j],a[j]\right\}\quad 1 \leq j \leq n
b[j]=max{b[j−1]+a[j],a[j]}1≤j≤n
int MaxSum(int n,int*a)
{
int sum=0,b=0;
for(int i=0;i<n;i++)
{
b = max(b+a[i],a[i]);
if(sum<b)sum=b;
}
return sum;
}
凸多边形最优三角剖分
描述
给定凸多边形 P = { v 0 , v 1 , … … , v n − 1 } P=\left\{v_0,v_1,……,v_{n-1}\right\} P={v0,v1,……,vn−1},以及定义在由凸多边形的边和弦组成的三角形上的权函数 w w w,要求确定该凸多边形的三角剖分,使得该三角剖分所对应的权最小。
最优子结构性质
若凸 n + 1 n+1 n+1边形 P = v 0 , v 1 , … … , v n P={v_0,v_1,……,v_n} P=v0,v1,……,vn的最优三角剖分 T T T包含三角形 v 0 v k v n ( 1 ≤ k ≤ n − 1 ) v_0v_kv_n(1 \leq k \leq n-1) v0vkvn(1≤k≤n−1),则 T T T的权为三角形 v 0 v k v n v_0v_kv_n v0vkvn的权、子多边形 { v 0 , v 1 , … … , v k } \left\{v_0,v_1,……,v_k\right\} {v0,v1,……,vk}和 { v k + 1 , v k + 2 , … … , v n } \left\{v_{k+1},v_{k+2},……,v_n\right\} {vk+1,vk+2,……,vn}的权之和。
递归结构
定义
t
[
i
]
[
j
]
t[i][j]
t[i][j]为凸子多边形
{
v
i
−
1
,
v
i
,
…
…
,
v
j
}
\left\{v_{i-1},v_{i},……,v_{j}\right\}
{vi−1,vi,……,vj}的最优三角剖分对应的权函数值
t
[
i
]
[
j
]
=
{
0
i
=
j
min
i
≤
k
≤
j
{
t
[
i
]
[
k
]
+
t
[
k
+
1
]
[
j
]
+
w
(
v
i
−
1
v
k
v
j
)
}
i
<
j
t[i][j]=\begin{cases} 0\quad i=j \\ \min\limits_{i \leq k \leq j}\left\{t[i][k]+t[k+1][j]+w(v_{i-1}v_kv_j)\right\}\quad i<j \end{cases}
t[i][j]=⎩
⎨
⎧0i=ji≤k≤jmin{t[i][k]+t[k+1][j]+w(vi−1vkvj)}i<j
代码
void MinWeightTrianglation(int n,int **t,int**s)
{
for(int i=1;i<=n;i++)
t[i][i]=0;
for(int r=2;r<=n;r++)
{
for(int i=1;i<=n-r+1;i++)
{
int j = i+r-1;
t[i][j]=t[i+1][j]+w(i-1,i,j);
s[i][j]=i;
for(int k=i+1;k<i+r-1;k++)
{
int u = t[i][k]+t[k+1][j]+w(i-1,k,j);
if(u<t[i][j])
{
t[i][j]=u;
s[i][j]=k;
}
}
}
}
}
背包问题
问题描述
给定 n n n中物品和一背包。物品 i i i的重量是 w i w_i wi,其价值为 v i v_i vi,背包容量为 w i w_i wi。
递归关系
m ( i , j ) m(i,j) m(i,j)是背包容量为j,可选择物品为 i , i + 1 , … … , n i,i+1,……,n i,i+1,……,n时0-1背包问题的最优值。
有:
m
(
i
,
j
)
=
{
max
m
(
i
+
1
,
j
)
,
m
(
i
+
1
,
j
−
w
i
)
+
v
i
j
≤
w
i
m
(
i
+
1
,
j
)
0
≤
j
<
w
i
m(i,j)=\begin{cases} \max{m(i+1,j),m(i+1,j-w_i)+v_i}\quad j \leq w_i\\ m(i+1,j)\quad 0 \leq j < w_i \end{cases}
m(i,j)={maxm(i+1,j),m(i+1,j−wi)+vij≤wim(i+1,j)0≤j<wi
m ( n , j ) = { v n j ≥ w n 0 0 ≤ j < w n m(n,j)=\begin{cases} v_n\quad j \geq w_n\\ 0 \quad 0 \leq j < w_n \end{cases} m(n,j)={vnj≥wn00≤j<wn
代码
void Knapsack(int*v,int*w,int c,int n,int **m)
{
for(int j=w[n];j<=c;j++)m[n][j]=v[n];
for(int j=0;j<min(w[n],c);j++)m[n][j]=0;
for(int i=n-1;i>=1;i--)
{
for(int j=0;j<=c;j++)
{
if(j>=w[i])m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
else m[i][j]=m[i+1][j];
}
}
}
void traceback(int **m,int*w,int c,int n,int*x)
{
for(int i=1;i<n;i++)
{
if(m[i][c]==m[i+1][c])x[i]=0;
else{
x[i]=1;
c-=w[i];
}
}
x[n]=(m[n][c])?1:0;
}