动态规划问题

动态规划问题同分治策略一致,都是将大问题划分为若干小问题

而动态规划问题将问题分解为不是相互独立的子问题,利用这一特点,用一个表来记录所有已经解决子问题的答案,不管该子问题以后是否被用到,只要被计算过,就将其结果填入表中

动态规划基本要素

  • 最优子结构:整体问题的最优解包含子问题的最优解
  • 重叠子问题:有些子问题被反复计算多次
  • 备忘录方法:为了避免重复求解,建立备忘录存储已经求解过的问题结果

1.矩阵连乘问题

给定n个矩阵{A1,A2,A3,...,AN},考察这n个矩阵的乘积

A1A2A3...An

由于矩阵乘法满足结合率,故矩阵连乘有多种计算次序,这种计算次序可以用加括号的方式来确定

方法一:穷举法

列出所有可能的计算次序,并计算出每一种计算次序相应的数乘次数

方法二:动态规划

将矩阵连乘积 AiA(i+1)A(i+2)...A(j)记为A[i:j],考察其最优计算次序,从k处断开,那么计算量为

先从小问题的计算开始,每一步均选出子问题的最优解,最终得到问题的最优解

可以看到计算次序为<1,2>,<1,3>...<1,3>,<2,4>...<4,6>,<1,4>... 这样进行计算的

例子

针对上述矩阵的最优解

m[i][j]表示每个子问题的最优解,s[i][j]表示每次问题选择断点k,然后两部分计算如下

void MatrixChain(int *p,int n,int **m,int **s){
    for (int i = 1; i <= n; i++) m[i][i] = 0;//自己到自己代价为0
    for (int r = 2; r <= n; r++)//每个斜线的循环,共画出n-1条(去掉了对角线)
        for (int i = 1; i <= n –(r-1); i++)//每次的乘法计算循环,最开始的时候为n-1次然后次数越来越少
        {
            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+1][j] + p[i-1]*p[k]*p[j];
                if (t < m[i][j]) { m[i][j] = t; s[i][j] = k;}
        }
}

输出最优计算次序

public static void TraceBack (int [][]s,int i,int j)
{
    if (i==j) return;
    TraceBack(s, i, s[i][j]);
    TarceBack(s,s[i][j]+1,j);
    System.out.println(“Multiply A”+i+”,”+s[i][j]+”and A”+(s[i][j]+1)+”,”+j);
}

2.最长公共子序列

若给定序列 X  =  {X1,X2,...,Xm} 则Z为X的子序列是指存在一个严格递增的下标序列使得对于所有的j = 1,2,3...,k 均存在Zj = Xij

例子:序列Z={B,C,D,B}是序列X={A,B,C,B, D,A,B}的子序列,相应的递增下标序列为{2,3,5, 7}。

给定2个序列X和Y,当另一序列Z既是X的子序列又是 Y的子序列时,称Z是序列X和Y的公共子序列。

目标:找出X和Y的最长公共子序列

设序列X={x1 ,x2 ,…,xm}和Y={y1 ,y2 ,…,yn }的最长公共子序列为 Z={z1 ,z2 ,…,zk } 

  • 若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。
  • 若xm≠yn且zk≠xm,则Z是Xm-1和Y的最长公共子序列。
  • 若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公共子序列。

n,m,k是序列长度,而减去1表示去掉最后一个元素

对于上述性质1,在直到最后一个公共元素是zk的时候,去掉三个序列中的该元素,那么就可以知道最长公共子序列

对于性质2,如果xm不是最后一个公共子元素,去掉无影响,性质三同理

得到如下递归公式

用c[i][j]记录序列Xi和Yj的最长公共子序列的长度。

第三个递推代表着如果xi不等于yj的时候,看看哪一个序列减去之后最长子序列长度会不变,当减到xi = yj的时候,长度加一,index均减一,继续递归

void LCSLength(int m,int n,char *x,char *y,int **c,int **b)
{
 int i,j;
 for (i = 1; i <= m; i++) c[i][0] = 0;
 for (i = 1; i <= n; i++) c[0][i] = 0;//当两个子序列其中一个长度为0的时候,最长子序列长度为0
 for (i = 1; i <= m; i++)
     for (j = 1; j <= n; j++) {
         if (x[i]==y[j]) {
             c[i][j]=c[i-1][j-1]+1; b[i][j]=1;}//使用b[i][j]来标记三种情况
         else if (c[i-1][j]>=c[i][j-1]) {
                 c[i][j]=c[i-1][j]; b[i][j]=2;}
         else { c[i][j]=c[i][j-1]; b[i][j]=3; }
 }
}

note:这里解释一下三种个情况,分别为最后一位相等,X序列的最后一位是公共子序列,Y序列的最后一位是公共子序列

void LCS(int i,int j,char *x,int **b)
{
     if (i ==0 || j==0) return;
     if (b[i][j]== 1){ LCS(i-1,j-1,x,b); cout<<x[i]; }//情况一:两者相等
     else if (b[i][j]== 2) LCS(i-1,j,x,b);//情况二:Y序列最后
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值