第三章 动态规划

动态规划

动态规划概叙

动态规划算法的概念

动态规划算法与分治法类似,都是将原问题分解称若干个子问题,求解子问题,然后结合这些子问题的解获得原问题的解。但是动态规划分解的子问题不互相独立,故有些子问题被重复计算,动态规划会将子问题的解保存下来,这样可以避免大量重复计算

动态规划的基本要素

  • 最优子结构性质:最优解问题包含了子问题的最优解问题

  • 重叠子问题性质:问题分解后,子问题会被重复计算。

动态规划算法的步骤

  • 找出最优解的性质,并刻画其结构特征
  • 递归地定义最优解
  • 以自底向上的方式计算最优解
  • 根据计算最优值时的消息,构造最优解

范例

矩阵连乘问题

问题描述

给定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]+pi1pkpj)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} Zk1 X m − 1 X_{m-1} Xm1 Y n − 1 Y_{n-1} Yn1的最长公共子序列
  • 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} Xm1 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} Yn1的最长公共子序列
递归结构

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[i1][j1]+1i,j>0;xi=yjmaxc[i][j1],c[i1][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,1ijnmaxk=ijak}

解决办法
分治法解决

将所给的序列 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=ijak
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]=1ijmax{k=ija[k]}(1jn)

则有如下递归表达式:
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[j1]+a[j],a[j]}1jn

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,……,vn1},以及定义在由凸多边形的边和弦组成的三角形上的权函数 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(1kn1),则 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\} {vi1,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=jikjmin{t[i][k]+t[k+1][j]+w(vi1vkvj)}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,jwi)+vijwim(i+1,j)0j<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)={vnjwn00j<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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值