最长公共子序列(LCS)问题

寻找两个序列的最长公共子序列

参考链接:https://www.geeksforgeeks.org/longest-common-subsequence-dp-4/

子序列特点:子序列中的元素与原序列中的元素以相同的相对顺序出现,子序列中的元素不一定连续出现在原序列中。

例如:“ABCDGH"和"AEDFHR"的最长公共子序列为"ADH”,其长度为3。“AGGTAB"和"GXTXAYB"的最长公共子序列为"GTAB”,其长度为4。

下面说明具体求解过程。

最优解的结构
假设输入的长度为 n n n m m m的序列分别为 X [ 0 , … , n − 1 ] X[0,\dots,n-1] X[0,,n1] Y [ 0 , … , m − 1 ] Y[0,\dots,m-1] Y[0,,m1]。记 X X X Y Y Y的最长公共子序列的长度为 L ( X [ 0 , … , n − 1 ] , Y [ 0 , … , m − 1 ] ) L(X[0,\dots,n-1],Y[0,\dots,m-1]) L(X[0,,n1],Y[0,,m1])

  • X X X Y Y Y的最后一个字符匹配,即 X [ n − 1 ] = Y [ m − 1 ] X[n-1]=Y[m-1] X[n1]=Y[m1],则
    L ( X [ 0 , … , n − 1 ] , Y [ 0 , … , m − 1 ] ) = 1 + L ( X [ 0 , … , n − 2 ] , Y [ 0 , … , m − 2 ] ) 。 L(X[0,\dots,n-1],Y[0,\dots,m-1]) \\= 1 + L(X[0,\dots,n-2],Y[0,\dots,m-2])。 L(X[0,,n1],Y[0,,m1])=1+L(X[0,,n2],Y[0,,m2])
  • X X X Y Y Y的最后一个字符不匹配,即 X [ n − 1 ] ≠ Y [ m − 1 ] X[n-1] \ne Y[m-1] X[n1]=Y[m1],则
    L ( X [ 0 , … , n − 1 ] , Y [ 0 , … , m − 1 ] ) = max ⁡ { L ( X [ 0 , … , n − 1 ] , Y [ 0 , … , m − 2 ] ) , L ( X [ 0 , … , n − 2 ] , Y [ 0 , … , m − 1 ] ) } 。 L(X[0,\dots,n-1],Y[0,\dots,m-1])\\ = \max \left\{ L(X[0,\dots,n-1],Y[0,\dots,m-2]), L(X[0,\dots,n-2],Y[0,\dots,m-1]) \right\}。 L(X[0,,n1],Y[0,,m1])=max{L(X[0,,n1],Y[0,,m2]),L(X[0,,n2],Y[0,,m1])}

递归实现方法
利用上述的递归表达式,我们可以写出最长公共子序列问题的递归实现C代码:

int findmax(int a, int b){
    return (a>=b) ? a : b;
}

int LCS(char *X,char *Y,int m, int n){
    int L;

    if ((m == 0) || (n == 0))
        return 0;

    if (X[m-1] == Y[n-1]){
        L = LCS(X,Y,m-1,n-1) + 1;
    }else{
        L = findmax(LCS(X,Y,m-1,n),LCS(X,Y,m,n-1));
    }
    return L;
}

该方法的复杂度为 O ( 2 n ) O(2^n) O(2n),复杂度较高,原因是出现了重复计算的情况。

利用表格的实现方法
为了降低复杂度,我们利用表格记录已经计算的结果。假设 X = ‘ ‘ G X T X A Y B " X=``GXTXAYB" X=GXTXAYB" Y = ‘ ‘ A G G T A B " Y=``AGGTAB" Y=AGGTAB",其中 n = 7 n=7 n=7 m = 6 m=6 m=6。构造一个 ( n + 1 ) × ( m + 1 ) (n+1)\times(m+1) (n+1)×(m+1)的表格 L L L如下。

j j j0123456
i i i ∅ \empty AGGTAB
0 ∅ \empty 0000000
1G00 1 \blue 1 1 1 \red 1 1111
2X00 1 \blue 1 1 1 \red 1 1111
3T0011 2 \red 2 222
4X0011 2 \red 2 222
5A01112 3 \red 3 33
6Y01112 3 \red 3 33
7B011123 4 \red 4 4

实现C代码如下。

int LCS_table(char* X, char* Y,int m,int n){
    int L[m+1][n+1];

    int i,j;
    for(i=0;i<=m;i++){
        for(j=0;j<=n;j++){
            if ((i==0)||(j==0)){
                L[i][j] = 0;
            }else{
                if (X[i-1] == Y[j-1]){
                    L[i][j] = L[i-1][j-1] + 1;
                }else{
                    L[i][j] = findmax(L[i-1][j],L[i][j-1]);
                }
            }
        }
    }

    int k = L[m][n];
    char lcsstr[N] = {'\0'};
    i = m;
    j = n;
    while((i>0)&&(j>0)){
        if(X[i-1] == Y[j-1]){
            lcsstr[k-1] = X[i-1];
            k--;
            i--;
            j--;
        }else{
            if(L[i-1][j] > L[i][j-1]){
                i--;
            }else{
                j--;
            }
        }
    }
    printf("The longest common string is %s\n",lcsstr);

    return L[m][n];
}

那么打印出来最长公共子序列呢?

首先,将子序列初始化为空字符串。然后,从表格的右下角元素 L [ i ] [ j ] , i = n , j = m L[i][j],i=n,j=m L[i][j],i=n,j=m出发,

  • X [ i ] = Y [ j ] X[i] = Y[j] X[i]=Y[j],则将当前字符合并到之前子序列的前面,执行i--; j--;
  • X [ [ i ] ≠ Y [ j ] X[[i] \ne Y[j] X[[i]=Y[j]
    • 如果 L [ i − 1 ] [ j ] > L [ i ] [ j − 1 ] L[i-1][j] > L[i][j-1] L[i1][j]>L[i][j1],执行i--;
    • 如果 L [ i − 1 ] [ j ] ≤ L [ i ] [ j − 1 ] L[i-1][j] \le L[i][j-1] L[i1][j]L[i][j1],执行j--;

C代码已在上一段给出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值