首页 > 代码库 > [algorithm]求最长公共子序列问题

[algorithm]求最长公共子序列问题

最直白方法:时间复杂度是O(n3), 空间复杂度是常数

reference:http://blog.csdn.net/monkeyandy/article/details/7957263

/**

 ** copyright@andy

 ** http://blog.csdn.net/MonkeyAndy

 **/

首先介绍动态规划方法的相关知识

动态规划方法的基本思想:

分成若干个子问题,先求解子问题,然后根据子问题的解求得原问题的解。经分解得到的子问题往往不是互相独立的。可重复利用!

其核心思想就是分治,分治方法的特点是分解后的子问题相对独立,可以通过简单的合并算法得到原问题的解!

动态规划方法的应用对象:
优化问题: 

  1. 一个优化问题可能有很多可行解,每个解都有一个求解代价,
  2.  我们希望选择一个具有最少代价的解。
  3. 一个优化问题可能有多个优化解。

动态规划方法:
当一个优化问题可分为多个子问题,子问题的解在构造上一级问题的求解过程中被重复使用。 这样可以节省计算时间与空间。

 

 

动态规划算法的步骤:

 

  1. 分析优化解的结构
  2. 递归地定义最优解的代价
  3. 自底向上地计算优化解的代价保存之,并获取构造最优解的信息。
  4.  根据计算优化解的代价,构造优化解
 
可用动态规划方法求解的问题必须满足的条件
     1. 具有优化子结构(Optimal substructure)
     如果一个问题的优化解包含了他的子问题的优化解,则称此问题具有优化结构。如,A1A2A3A4A5A6 的优化解是 (A1 ( A2A3)) ((A4A5)A6),其中A1 ( A2A3)和(A4A5)A6),分别是A1A2A3和A4A5A6的解。
        优化结构是应用动态规划方法的一个条件。是必要条件!
     2. 具有重叠子问题(Overlapping sub-problems)  
       如果递归算法求解一个优化问题时,需要反复求解相同的子问题,则称该优化问题具有重叠子问题。
       动态规划算法利用该性质,以求得的子问题的解保存后,自底向上地,重复利用子问题的解获得上级问题的解,节省空间开销和时间开销。
       如果不具备重叠子问题的性质动态规划方法的空间开销和时间开销很大。利用动态规划算法没有意义!
 
求最长公共子序列
 
 1. 子序列定义
    设x=(x1, x2, ... xm)是一个序列, z=(z1, z2, ... zm) 是x的一个子序列,如果存在1?i1<i2<...<ik<m, 使xij=zj
    z=(z1, z2, ... zm)即是x=(x1, x2, ... xm)中删除一些元素以后得到的序列。    


    2.公共子序列定义
    Z是序列X与Y的公共子序列,如果Z是X的子序列且Z是Y的子序列。
     
    3.最长公共子序列问题(LCS)
    输入:X=(x1, x2, ... xn),Y=(y1, y2, ...ym)
    输出:X与Y的最长公共子序列Z
 
最长公共子序列结构分析
        定义. 设X=(x1 , x2 , ... xn) 是一个序列, Xi表示X的第i个前缀,即 Xi=(x1 , ... xi )。
        例.  X=(A,B,D,C,A),  X1=(A),  X2=(A,B),  X3=(A,B,D)
 
优化解的结构
    定理1(LCS的优化结构)设X=(x1 , ... xm), Y=(y1 , y2 , ... yn)是两个序列,Z=(z1 , z2 , ... zk) 是X与Y的LCS。下列结论成立:
  1.               如xm= yn, 则zk=xm=yn, Zk-1是Xm-1和Yn-1的LCS,即,LCS(X,Y)=LCS(Xm-1,Yn-1)+xm
  2.               若xm != yn,且zk!=xm,则Z是Xm-1和Y的LCS,即   LCS(X,Y)=LCS(Xm-1,Y)
  3.               若xm != yn , 且zk !=yn,则Z是X与Yn-1的LCS,即    LCS(X,Y)=LCS(X,Yn-1)
 
例如:
 
 
 
建立求解LCS长度的递归方程
 
If xm= yn, xm属于X和Y的LCS,必须求解Xm-1和Yn-1的LCS。
If xm !=yn , 必须求解Xm-1和Y的LCS以及X和Yn-1 的LCS,长者是X和Y的LCS。C[i,j] 表示Xi与Yj的LCS的长度,则建立求解LCS长度的递归方程
 
LCS长度的计算
    1.基本思想
       计算C[i,j] 需先计算C[i-1,j-1]、C[i,j-1]、C[i-1,j]
 
C[i-1,j-1]C[ i, j-1]
C[i-1,   j]C[  i,   j]

  先计算出第0行与第0列,然后逐行计算。
 


2.   算法
 
 
 
[cpp] view plaincopy
 
  1. /** 
  2. *copyright@andy 
  3. *date@ 2012/09/08 
  4. * m : x的长度 
  5. *  n : y的长度 
  6. *  c : xi与yi的LCS的长度 
  7. *  b : 用于标记xi和yi的关系 
  8. **/  
  9. void LCSLength( int m, int n, char *x, char *y int **c, Type **b )  
  10. {  
  11.     int i, j;  
  12.     /*c 初始化*/  
  13.     for ( i=1; i<=m; i++) c[ 0 ][ i ]=0;  
  14.     for( j=1; j<=n, j++) c[ j ][0 ]=0;  
  15.     for ( i=1; i<=m; i++)  
  16.         for( j=1; j<=n, j++)  
  17.         {  
  18.             if(x[i]==y[j])  //xi = yj 时LCS 增1  
  19.             {  
  20.                 c[ i ][ j ]=c[ i-1 ][ j-1]+1;  
  21.                 b[ i ][ j ]=‘\‘‘;  
  22.             }  
  23.             else if ( c[i-1][j] >= c[i][j-1]) //当xi ≠ yj时保存长子序列  
  24.             {  
  25.                 c[ i ][ j ]=c[ i-1 ][ j];  
  26.                 b[ i ][ j ]=‘|‘;  
  27.             }  
  28.             else  
  29.             {  
  30.                 c[ i ][ j ]=c[ i ][ j-1];  
  31.                 b[ i ][ j ]=‘--‘;  
  32.             }  
  33.         }  
  34. }  


 
3.   构造LCS算法
 
[cpp] view plaincopy
 
  1. /** 
  2. *copyright@andy 
  3. *date@ 2012/09/08 
  4. * i : x的长度 
  5. *  j : y的长度 
  6. *  x : x字符串 
  7. *  b : 用于标记xi和yi的关系 
  8. **/  
  9. void LCS( int i, int j, char *x, Type **b )  
  10. {  
  11.     if ( i==0 || j==0) return;  
  12.     if (b[ i ][ j ] ==‘\‘‘)//当xi = yj 时,输出xi ,并删除xi 和 yj ,之后在子序列中继续求解LCS  
  13.     {  
  14.         LCS(i-1, j-1, x, b);  
  15.         out<<x[ i ];  
  16.     }  
  17.     else if ( b[i][j] == ‘|‘)//当Xi-1 和 Yj 的LCS不小于Xi 和 Yj-1 的LCS 时  
  18.         LCS(i-1, j, x, b);    //在Xi-1 和 Yj中继续求解LCS;否则,在Xi 和 Yj-1 中继续求解LCS;  
  19.     else  
  20.         LCS( i, j-1, x, b);  
  21. }  

 
LCSLength算法的时间复杂性O(mn)

[algorithm]求最长公共子序列问题