动态规划算法之最长公共子序列问题

本文转载自https://www.cnblogs.com/Jason-Damon/p/3245443.html

一、问题

某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置而形成的新序列。比如,BCAB是序列ABCBDAB的一个子序列。而最长公共子序列问题是寻找两个或多个已知序列它们最长的子序列。比如我们要寻找序列ACBDAW和序列DBAW的最长公共子序列,应该是BAW,长度为3。

二、解题思路

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

  (1)若xm=yn,则zk=xm=yn,且{z1...zk-1}是{x1...xm-1}和{y1...yn-1 }的最长公共子序列。
  (2)若xm≠yn且zk≠xm,则{z1....zk } 是{x1...xm-1}和{y1....yn}最长公共子序列。
  (3)若xm≠yn且zk≠yn,则{z1...zk}是{x1...xm}和{y1...yn-1}的最长公共子序列。

  则可以有如下的递归关系:
  c[i][j]表示x的第i位和y的第j位之前(包括i和j)的最长公共子序列的个数
     

   因此在求解的时候也分为这三种情况来考虑即可。最后还要用回溯的方法把最长子串输出出来。

三、源码

//LCS  longest common subsequence

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100

void LCS(char *x, char *y,int x_len, int y_len, int common_len[][MAX], int b[][MAX])
{
    //common_len[i][j]存储的是x的第i位与Y的第j位的公共子序列的长度
    //b[i][j] 记录获得common_len[i][j]的路径:分别为0 -1 1,便于回溯输出公共子串

    int i,j;
    
    for (i = 0; i < x_len; i++)
        common_len[i][0] = 0;
    for (j = 0; j < y_len; j++)
        common_len[0][j] =0;

    for (i = 1; i <= x_len; i++)
    {
        for (j = 1; j <= y_len; j++)
        {
            if (x[i-1] == y[j-1])  //从零开始存储,所以第i位为x[i-1]
            {
                common_len[i][j] = common_len[i-1][j-1] + 1;
                b[i][j] = 0;
            }
            else if (common_len[i-1][j] >= common_len[i][j-1])
            {
                common_len[i][j] = common_len[i-1][j];
                b[i][j] = -1;
            }
            else
            {
                common_len[i][j] = common_len[i][j-1];
                b[i][j] = 1;
            }
        }
    }
}

void backtrack(int i, int j,int b[][MAX], char *x)
{
    if (0 == i || 0 == j) 
        return;
    else if (0 == b[i][j])
    {
        backtrack(i-1,j-1,b,x);
        printf("%c",x[i-1]);
    }
    else if(-1 == b[i][j])
    {
        backtrack(i-1,j,b,x);
    }
    else
    {
        backtrack(i,j-1,b,x);
    }
}

int main()
{
    int x_len,y_len;
    char x[MAX] = "ABCBDAB";
    char y[MAX] = "BDCABA";
    int common_len[MAX][MAX];
    int b[MAX][MAX];

    x_len = strlen(x);
    y_len = strlen(y);

    LCS(x,y,x_len,y_len,common_len,b);
    backtrack(x_len,y_len,b,x);
    
    printf("\n");
    
    
    return 0;
}

比如:

A序列: VAZDBEG(长度为7)        B序列: GVMZDECG (长度为8)       那么它们的LCS:VZDEG   len=5 

填表table:我们需要一个8x9的表,将0行和0列全部初始化为0,然后从第1行(i=1)开始填,每次循环从第1列(j=1)开始,检查序列A[i-1]

和B[j-1]的字符是否相同,若相同,则table[i][j]=table[i-1][j-1]+1;否则有两种情况,所以要取当前位置table[i][j]左边的值和它上边的值中更大的一个,即 table[i][j]=max{table[i-1][j] , table[i][j-1]}。可以发现最终表格table最右下角的数字5就是最长公共子序列的长度。

算法时间复杂度为O(m*n)。(m、n分别为两个序列的长度)

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页