动态规划算法(2)

动态规划(2)

动态规划算法设计要点

  1. 引入参数界定子问题的边界. 注意子问题的重叠程度.(边界表示)
  2. 确定子问题依赖关系. 给出带边界参数的优化函数定义与优化函数的递推关系,找到递推关系的初值. (转移方程)
  3. 考虑是否需要标记函数
  4. 采用自底向上的实现技术,从最小的子问题开始迭代计算,计算中用备忘录保留优化函数核标记函数的值。

8.5 求解最长公共子序列问题

问题描述:求给定两个序列的最长公共子序列。字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后形成的序列。

  • 如果Z是X和Y的LCS;那么任何Z的前缀都是某个X的前缀和某个Y的前缀的LCS,(最优子结构)
子问题界定

参数 i 和 j 界定子问题
X 的终止位置是 i,Y 的终止位置是 j
Xi =<x1, x2, …, xi>,Yj =<y1, y2, …, yj>
在这里插入图片描述

确定子问题的依赖关系–优化函数的地推方程

【问题求解】若设A=<a1, a2, …, am>,B=<b1, b2, …, bn>

定义C[i,j]=|LCS(X[1,i],Y[1,j])|
在这里插入图片描述

标记函数

标记函数dp[i,j], 值为
在这里插入图片描述

例题

输入:X=<A,B,C,B,D,A,B>, Y=<B,D,C,A,B,A>,

标记函数:
在这里插入图片描述

解:X[2],X[3], X[4], X[6], 即 B, C, B, A

LCS(X,Y,m,n)   
   1.  for  i←1  to  m  do
   2.       C[i,0]←0
   3.  for  i←1  to  n  do
   4.       C[0,i]←0
   5.  for  i←1  to  m  do               
   6.     for  j←1  to  n  do
   7.             if  X[i]=Y[j] 
   8.                      C[i,j]←C[i-1,j-1]+1
   9.                      B[i,j]←’↖’
   10.           else if  C[i-1,j] ≥ C[i,j-1] 
   11.                           C[i,j]←C[i-1,j]
   12.                           B[i,j]←’↑’
   13.                 else      
   14.                              C[i,j]←C[i,j-1]   
   15.                           B[i,j]←’←’

利用标记函数构造解
Structure Sequence(B, i, j)
输入:B[i,j]
输出:X与Y的最长公共子序列 
   1.  if  i=0  or  j=0  then  return  //一个序列为空
   2.  if  B[i,j] =“↖”  
   3.      输出X[i]
   4.      Structure Sequence(B, i-1, j-1) 
   5.  else  if  B[i,j]=“↑” 
                     Structure Sequence (B, i-1, j)
   6.        else    Structure Sequence (B, i, j-1)   

算法的计算复杂度
计算优化函数和标记函数:时间为O(mn)
构造解:每一步至少缩小X 或 Y 的长度,时间Θ(m+n)
空间:Θ(mn)

定义二维动态规划数组dp

其中dp[i][j]为子序列(a0,a1,…,ai-1)和(b0,b1,…,bj-1)的最长公共子序列的长度。每考虑字符a[i]或b[j]都为动态规划的一个阶段(共经历约m×n个阶段)。

  • 情况1:a[i-1]=b[j-1](当前两个字符相同)
    在这里插入图片描述

  • 情况2:a[i-1]≠b[j-1](当前两个字符不同)
    在这里插入图片描述

    dp[i][j]为子序列(a0,a1,…,ai-1)和(b0,b1,…,bj-1)的最长公共子序列的长度。 对应的状态转移方程如下:

    dp[i][j]=0				  i=0或j=0―边界条件
    dp[i][j]=dp[i-1][j-1]+1		  a[i-1]=b[j-1]
    dp[i][j]=MAX(dp[i][j-1],dp[i-1][j])	  a[i-1]≠b[j-1]
    
    如何求由dp求出LCS?
    • 当dp[i] [j] ≠ dp[i] [j-1](左边)并且dp[][][][][][][][][][][][][][][][][i] [j] ≠ dp[i-1] [j](上方)值时:
      a[i-1]=b[j-1]

      • 将a[i-1]添加到LCS中。
    • dp[i][j]=dp[i][j-1]:与左边相等 → j--
      dp[i][j]=dp[i-1][j]:与上方相等 → i--
      与左边、上方都不相等:a[i-1]或者b[j-1]属于LCS   i--,j--
      
    • 例如,X=(a,b,c,b,d,b),m=6,Y=(a,c,b,b,a,b,d,b,b),n=9。
      在这里插入图片描述

求解代码
#define MAX 51				//序列中最多的字符个数
//问题表示
int m,n;
string a,b;				//求解结果表示
int dp[MAX][MAX];			//动态规划数组
vector<char> subs;			//存放LCS

void LCSlength()			//求dp
{  int i,j;
   for (i=0;i<=m;i++)			//将dp[i][0]置为0,边界条件
      dp[i][0]=0;
   for (j=0;j<=n;j++)			//将dp[0][j]置为0,边界条件   
      dp[0][j]=0;
   for (i=1;i<=m;i++)
      for (j=1;j<=n;j++)		//两重for循环处理a、b的所有字符
      {  if (a[i-1]==b[j-1])		//情况(1)
            dp[i][j]=dp[i-1][j-1]+1;
         else				//情况(2)
            dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
      }

void Buildsubs()		    //由dp构造从subs
{  int k=dp[m][n];		    //k为a和b的最长公共子序列长度
   int i=m;
   int j=n;
   while (k>0)			    //在subs中放入最长公共子序列(反向)
     if (dp[i][j]==dp[i-1][j])
	 i--;
     else if (dp[i][j]==dp[i][j-1])
	 j--;
     else			    //与上方、左边元素值均不相等
     {	 subs.push_back(a[i-1]);  //subs中添加a[i-1]
	 i--; j--; k--;
     }
}

算法分析

LCSlength算法中使用了两重循环,所以对于长度分别为m和n的序列,求其最长公共子序列的时间复杂度为O(m×n)。空间复杂度为O(m×n)。

8.6 求解最长递增子序列问题

问题描述

给定一个无序的整数序列a[0…n-1],求其中最长递增子序列的长度。

例如,a[]={2,1,5,3,6,4,8,9,7},n=9,其最长递增子序列为{1,3,4,8,9},结果为5。

问题求解

设计动态规划数组为一维数组dp,dp[i]表示a[0…i]中以a[i]结尾的最长递增子序列的长度。

对应的状态转移方程如下:

dp[i]=1				0≤i≤n-1
dp[i]=max(dp[i],dp[j]+1)	若a[i]>a[j],0≤i≤n-1,0≤j≤i-1

求出dp后,其中最大元素即为所求。

求解代码
//问题表示
int a[]={2,1,5,3,6,4,8,9,7};
int n=sizeof(a)/sizeof(a[0]);
//求解结果表示
int ans=0;
int dp[MAX];
void solve(int a[],int n)
{  int i,j;
   for(i=0;i<n;i++)
   {  dp[i]=1;
      for(j=0;j<i;j++)
      {  if (a[i]>a[j])
           dp[i]=max(dp[i],dp[j]+1);
      }
   }
   ans=dp[0];#招最大
   for(i=1;i<n;i++)
     ans=max(ans,dp[i]);
}

算法分析

solve()算法中含两重循环,时间复杂度为O(n2)。

8.7 求解编辑距离问题

问题描述

设A和B是两个字符串。现在要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有3种:

  1. 删除一个字符
  2. 插入一个字符
  3. 将一个字符替换为另一个字符
    在这里插入图片描述
问题求解

设字符串A、B的长度分别为m、n,分别用字符串a、b存放。

  • 子问题界定:a[1… n]和b[1… m]表示两个序列,子问题:a[1… i]和b[1… j],边界(i, j)
    在这里插入图片描述

  • 确定子问题间的依赖关系–优化函数的递推方程

在这里插入图片描述

算法的时间复杂度是O(nm)

两种特殊情况:

设计一个动态规划二维数组dp,其中dp[i] [j]表示a[0…i-1](1≤i≤m)与b[0…j-1](1≤j≤n)的最优编辑距离

  • 当B串空时,要删除A中全部字符转换为B,即dp[i] [0]=i(删除A中i个字符,共i次操作);
  • 当A串空时,要在A中插入B的全部字符转换为B,即dp[0] [j]=j(向A中插入B的j个字符,共j次操作)

对于非空的情况,当a[i-1]=b[j-1]时,这两个字符不需要任何操作,即dp[i] [j]=dp[i-1] [j-1]。

当a[i-1]≠b[j-1]时,以下3种操作都可以达到目的:

  • 将a[i-1]替换为b[j-1],有:dp[i] [j]=dp[i-1] [j-1]+1(一次替换操作的次数计为1)。
  • 在a[i-1]字符后面插入b[j-1]字符,有:dp[i][j]=dp[i][j-1]+1(一次插入操作的次数计为1)。
  • 删除a[i-1]字符,有:dp[i][j]=dp[i-1][j]+1(一次删除操作的次数计为1)。

此时dp[i][j]取3种操作的最小值

算法分析

] [j]=dp[i-1] [j-1]。

当a[i-1]≠b[j-1]时,以下3种操作都可以达到目的:

  • 将a[i-1]替换为b[j-1],有:dp[i] [j]=dp[i-1] [j-1]+1(一次替换操作的次数计为1)。
  • 在a[i-1]字符后面插入b[j-1]字符,有:dp[i][j]=dp[i][j-1]+1(一次插入操作的次数计为1)。
  • 删除a[i-1]字符,有:dp[i][j]=dp[i-1][j]+1(一次删除操作的次数计为1)。

此时dp[i][j]取3种操作的最小值

算法分析

solve()算法中有两重循环,对应的时间复杂度为O(mn)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值