【串和序列处理 6】LCS 最长公共子序列

LCS:又称 最长公共子序列。 其中子序列(subsequence)的概念不同于串的子串。它是一个不一定连续但按顺序取自字符串X中的字符序列。 例如:串"AAAG"就是串“CGATAATTGAGA”的一个子序列。

 

 

字符串的相似性问题可以通过求解两个串间的最长公共子序列(LCS)来得到。 当然如果使用穷举算法列出串的所有子序列,一共有2^n种,而每个子序列是否是另外一个串的子序列又需要O(m)的时间复杂度,因此这个穷举的方法时间复杂度是O(m*(2^n))指数级别,效率相当的可怕。我们采用动态规划算法来解决这个问题。

 

 

动态规划算法解决最长公共子序列

 

假如我们有两个字符串:X=[0,1,2....n]  Y=[0,1,2...m]。我们定义L(i, j)为X[0...i]与Y[0...j]之间的最长公共子序列的长度。通过动态规划思想(复杂问题的最优解是子问题的最优解和子问题的重叠性质决定的)。我们考虑这样两种情况:

 

(1)  当X[i]=Y[j]时, L(i, j)=L(i-1, j-1)+1 。证明很简单。

(2)  当X[i]!=Y[j]时, 说明此事X[0...i]和Y[0...j]的最长公共子序列中绝对不可能同时含有X[i]和Y[j]。那么公共子序列可能以X[i]结尾,可能以Y[j]结尾,可以末尾都不含有X[i]或Y[j]。因此

                               L(i, j)= MAX{L(i-1 , j), L(i, j-1)}

 

LCS动态规划Java源代码

Java代码  收藏代码
  1. package net.hr.algorithm.string;  
  2.   
  3. /** 
  4.  * 最长公共子串LCS 
  5.  * @author heartraid 
  6.  */  
  7. public class LCS {  
  8.   
  9.     /**字符串X的字符数组*/  
  10.     private char[] charArrayX=null;  
  11.     /**字符串Y的字符数组*/  
  12.     private char[] charArrayY=null;  
  13.   
  14.     public LCS(String sa,String sb){  
  15.         charArrayX=new char[sa.length()+1];  
  16.         System.arraycopy(sa.toCharArray(),0,charArrayX,1,sa.length());  
  17.         charArrayY=new char[sb.length()+1];  
  18.         System.arraycopy(sb.toCharArray(),0,charArrayY,1,sb.length());  
  19.     }  
  20.       
  21.     /** 
  22.      * 得到最长公共子序列的长度 
  23.      */  
  24.     public void getLCS(){  
  25.           
  26.         int[][] length=new int[charArrayX.length+1][charArrayY.length+1];  
  27.           
  28.         for(int m=1;m<charArrayX.length;m++)  
  29.             for(int n=1;n<charArrayY.length;n++){  
  30.                 if(charArrayX[m]==charArrayY[n]){  
  31.                     length[m][n]=length[m-1][n-1]+1;  
  32.                 }  
  33.                 else  
  34.                     length[m][n]=max(length[m-1][n],length[m][n-1]);  
  35.                       
  36.             }  
  37.         //打印最长公共子序列  
  38.         String lcstr="";  
  39.         int x=charArrayX.length-1;  
  40.         int y=charArrayY.length-1;  
  41.         while(x>=1&&y>=1){  
  42.             if(charArrayX[x]==charArrayY[y]){  
  43.                 lcstr=charArrayX[x]+lcstr;  
  44.                 x--;  
  45.                 y--;  
  46.             }else{  
  47.                 if(length[x-1][y]<=length[x][y-1])  
  48.                     y--;  
  49.                 else  
  50.                     x--;      
  51.             }  
  52.         }  
  53.         System.out.println("最长公共子序列为:"+lcstr+" [length="+lcstr.length()+"]");  
  54.     }  
  55.     /** 
  56.      * 取最大值 
  57.      */  
  58.     private int max(int m,int n){  
  59.         return m>n?m:n;  
  60.     }     
  61.       
  62.     /** 
  63.      * 测试 
  64.      */  
  65.     public static void main(String[] args) {  
  66.         LCS lcs=new LCS("GTTCCTAATA","CGATAATTGAGA");  
  67.         lcs.getLCS();  
  68.     }  
  69.   
  70. }  

     

这里解释一下上面的代码,其中getLength()的作用是递归获取最长公共子串的长度,并得到递归过程中每个子串之间最长公共子串长度的状态表lcs[][],这张表运行的结果如下:

 

      C G A T A A T T G A G A

   0 0 0 0 0 0 0 0 0 0 0 0 0 
G 0 0 1 1 1 1 1 1 1 1 1 1 1 
T 0 0 1 1 2 2 2 2 2 2 2 2 2 
T 0 0 1 1 2 2 2 3 3 3 3 3 3 
C 0 1 1 1 2 2 2 3 3 3 3 3 3 
C 1 1 1 2 2 2 3 3 3 3 3 3 
T 0 1 1 1 2 2 2 3 4 4 4 4 4 
A 0 1 1 2 2 3 3 3 4 4 5 5 5 
A 0 1 1 2 2 3 4 4 4 4 5 5 6 
T 0 1 1 2 3 3 4 5 5 5 5 5 6 
A 0 1 1 2 3 4 4 5 5 5 6 6 6

 

 

 

红色数字的位置就揭示了最长公共子序列: CTATTA。也就是说分析这张表就可以得到最长公共子序列字符串。

 

为什么呢?因为通过表的回溯过程,从后向前重构了一个最长公共子序列。对于任何位置lcs[i][j],确定是否X[i]=Y[j]。如果是,那么X[i]必是最长公共子序列的一个字符。如果否,那么移动到lcs[i,j-1]和lcs[i-1, j]之间的较大者。

 

 

动态规划方法LCS效率:

 

动态规划方法构造最长公共子序列需要O(m*n)的代价,另外,如果想要得到最长公共子序列,又需要O(m+n)的时间来读取csl[][]数组。尽管如此,其时间复杂度仍然比蛮力穷举的指数级别要强的多。

 

 

 

问题拓展:设A,B,C是三个长为n的字符串,它们取自同一常数大小的字母表。设计一个找出三个串的最长公共子串的O(n^3)的时间算法。 (来自《Algorithm Design》(中文版:算法分析与设计) - Chapter9 - 文本处理 - 创新题C-9.18)

 

分析:可以通过《LCS 最长公共子序列 》的动态规划算法,设计Java源代码如下:

Java代码  收藏代码
    1. public class TriLCS{  
    2.       
    3.     char[] charA=null;  
    4.     char[] charB=null;  
    5.     char[] charC=null;  
    6.       
    7.       
    8.     public TriLCS(String sa,String sb,String sc){  
    9.         charA=new char[sa.length()+1];  
    10.         System.arraycopy(sa.toCharArray(),0,charA,1,sa.length());  
    11.         charB=new char[sb.length()+1];  
    12.         System.arraycopy(sb.toCharArray(),0,charB,1,sb.length());  
    13.         charC=new char[sc.length()+1];  
    14.         System.arraycopy(sc.toCharArray(),0,charC,1,sc.length());  
    15.     }  
    16.       
    17.     public void getTriLCS(){  
    18.           
    19.         int[][][] length=new int[charA.length][charB.length][charC.length];  
    20.           
    21.         for(int a=1;a<charA.length;a++)  
    22.             for(int b=1;b<charB.length;b++)  
    23.                 for(int c=1;c<charC.length;c++){  
    24.                       
    25.                     if(charA[a]==charB[b]&&charA[a]==charC[c]){  
    26.                         length[a][b][c]=length[a-1][b-1][c-1]+1;  
    27.                     }  
    28.                     else if(charA[a]==charB[b]&&charA[a]!=charC[c]){  
    29.                         length[a][b][c]=max(length[a-1][b-1][c],length[a][b][c-1]);  
    30.                     }  
    31.                     else if(charA[a]==charC[c]&&charA[a]!=charB[b]){  
    32.                         length[a][b][c]=max(length[a-1][b][c-1],length[a][b-1][c]);  
    33.                     }  
    34.                     else if(charB[b]==charC[c]&&charA[a]!=charB[b]){  
    35.                         length[a][b][c]=max(length[a][b-1][c-1],length[a-1][b][c]);  
    36.                     }  
    37.                     else{  
    38.                         length[a][b][c]=max(length[a-1][b][c],length[a][b-1][c],length[a][b][c-1],length[a-1][b-1][c],length[a-1][b][c-1],length[a][b-1][c-1]);  
    39.                     }                     
    40.                 }  
    41.           
    42.           
    43.           
    44.         //打印最长公共子序列  
    45.         String lcstr="";      
    46.         int a=charA.length-1;  
    47.         int b=charB.length-1;  
    48.         int c=charC.length-1;  
    49.         while(a>=1&&b>=1&&c>=1){  
    50.             if(charA[a]==charB[b]&&charA[a]==charC[c]){  
    51.                 lcstr=charA[a]+lcstr;  
    52.                 a--;  
    53.                 b--;  
    54.                 c--;  
    55.             }  
    56.             else if(charA[a]==charB[b]&&charA[a]!=charC[c]){  
    57.                 if(length[a-1][b-1][c]<=length[a][b][c-1])  
    58.                     c--;  
    59.                 else{  
    60.                     a--;  
    61.                     b--;  
    62.                 }  
    63.             }  
    64.             else if(charA[a]==charC[c]&&charA[a]!=charB[b]){  
    65.                 if(length[a-1][b][c-1]<=length[a][b-1][c])  
    66.                     b--;  
    67.                 else{  
    68.                     a--;  
    69.                     c--;  
    70.                 }  
    71.             }  
    72.             else if(charB[b]==charC[c]&&charA[a]!=charB[b]){  
    73.                 if(length[a][b-1][c-1]<=length[a-1][b][c])  
    74.                     a--;  
    75.                 else{  
    76.                     b--;  
    77.                     c--;  
    78.                 }  
    79.             }  
    80.             else{  
    81.                 int maxize=max(length[a-1][b][c],length[a][b-1][c],length[a][b][c-1],length[a-1][b-1][c],length[a-1][b][c-1],length[a][b-1][c-1]);  
    82.                 if(maxize==length[a-1][b][c])  
    83.                     a--;  
    84.                 if(maxize==length[a][b-1][c])  
    85.                     b--;  
    86.                 if(maxize==length[a][b][c-1])  
    87.                     c--;  
    88.                 if(maxize==length[a-1][b-1][c]){  
    89.                     a--;  
    90.                     b--;  
    91.                 }  
    92.                 if(maxize==length[a-1][b][c-1]){  
    93.                     a--;  
    94.                     c--;  
    95.                 }  
    96.                 if(maxize==length[a][b-1][c-1]){  
    97.                     b--;  
    98.                     c--;  
    99.                 }     
    100.             }  
    101.         }  
    102.           
    103.         System.out.println("最长子串为:"+lcstr+"(length="+length[charA.length-1][charB.length-1][charC.length-1]+")");  
    104.     }  
    105.       
    106.     /** 
    107.      * 取最大值 
    108.      */  
    109.     private int max(int m,int n){  
    110.         return m>n?m:n;  
    111.     }  
    112.     /** 
    113.      * 取最大值 
    114.      */  
    115.     private int max(int x,int y,int z,int k,int m,int n){  
    116.         int maxizen=0;  
    117.         if(maxizen<x) maxizen=x;  
    118.         if(maxizen<y) maxizen=y;  
    119.         if(maxizen<z) maxizen=z;  
    120.         if(maxizen<k) maxizen=k;  
    121.         if(maxizen<m) maxizen=m;  
    122.         if(maxizen<n) maxizen=n;  
    123.         return maxizen;  
    124.     }  
    125.       
    126.     public static void main(String[] args){  
    127.         TriLCS tri=new TriLCS("aadsbbbcs","adsabcs","adbsbsdcs");  
    128.         tri.getTriLCS();  
    129.     }     
    130. }  
posted @ 2012-07-22 21:44  springbarley  阅读(255)  评论(0)    收藏  举报