粗解_最长公共子序列_动态规划

题目描述

最长公共子序列是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。

最长公共子序列也称作最长公共子串(不要求连续),英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。

输入

输入数据有T组测试数据。测试数据的数目 (T)在输入的第一行给出。每组测试数据有两行:每行有一个字符串,每个字符串的长度都不超过20。

输出

每个用例,用一行输出其最大公共子序列的长度,如果没有公共子序列,则输出0。

样例输入

2
abceef
1235896
ABCBDAB
BDCABA

样例输出

0
4

分析:

弄懂了最长子序列问题,对于这个问题就容易多了。该问题与最长子序列的不同在于这里要找的最长子序列必须存在于所给的两个序列中。将原来的从自己本身找(n*n遍历)改成 从对双方序列中找(n*m)遍历。

解答:设d [ i ][ j ]  : 为序列A( 1-i )和序列B( 1-j )的最长子序列长度。递推过程中存在3种情况:1.当i==0或者j==0时表示,两个序列中存在一个序列长度为0,显然则此时的d[i][j] = 0;   2.当遍历到a[i] = b[j]时,此时公共序列长度应加1,并且两个序列都要从下一个元素继续找。3.遍历时如果a[i]!=b[i]则应该让某一个序列往下一个元素移动,看是否能匹配另一个元素,那么让哪一个序列的状态往后移呢?答案是使得公共子序列长度长的那个,由于不知道到底哪一个会使得公共子序列长度更长,所以我们将两种情况都列出来,取其大者。

扫描二维码关注公众号,回复: 3015949 查看本文章

代码:

#include<bits/stdc++.h>
#define MAX 100
using namespace std;

int main()
{
    int t;
    char a[MAX];
    char b[MAX];
    int d[MAX][MAX];
    cin>>t;
    while(t--)
    {
        memset(a,-1,sizeof(a));
        memset(b,-1,sizeof(b));
        memset(d,0,sizeof(d));//这里直接将全部赋为0(自然包括i=0||j=0)

        cin>>a;
        cin>>b;
        int n = strlen(a);
        int m = strlen(b);

        for(int i = 1 ; i<=n ; i++)
            for(int j = 1 ; j<=m ; j++)
            {
                    if(a[i-1]==b[j-1])//由于i从1开始到n而元素存储从0到n-1,j同理所以减1
                        d[i][j] = d[i-1][j-1]+1;
                    else
                        d[i][j] = max(d[i-1][j],d[i][j-1]);
            }
        cout<<d[n][m]<<endl;
    }
    return 0;
}

那么如何得到最长序列而不仅仅是长度呢?

由于dp[][]的获取方法我们是知道的,所以我们可以根据获取的方法进行逆推,从二维数组中逆推出元素内容。

只有当a[i] = b[j] 时,这个元素是我们所需要的公共子序列中的元素,而当a[i] = b[j]时,dp[i][j] = d[i-1][j-1]+1,也就是说,在获取元素时我们可以借助dp[][]数组来判断,即将dp[i][j] 与dp[i-1][j-1]+1,dp[i-1][j]和dp[i][j-1]之间的关系作为条件。

此时有三种情况:

注意:上述3种情况的顺序在最长公共子序列不止一个的情况下会导致输出不同,原因在于,在判断时是往a序列方向遍历还是往b序列遍历,产生的结果对应为,按a序列中元素出现顺序的顺序和按b序列中元素出现顺序的顺序出现。

以a方向遍历为例:

代码:直接加到上面输出长度代码后即可

char z[MAX];
int t = 0;
for(int i = n,int j = m ; i&&j ; )
{
if(dp[i][j]==dp[i-1][j])//往a方向遍历
i--;
else if(dp[i][j] = dp[i][j-1])
j--;
else if(dp[i][j] = dp[i-1][j-1])
{
z[t++]+=a[i]//z[t++]+=b[j]
i--;
j--;
}
}
for(int i = t-1 ; i>=0 ; i--)
cout<<z[i];

过程图解:从右下角开始,每次先判断左方。

红色部分为路径,结果为:浅红色部分对应的纵行元素。注意:由于是逆向枚举的所以的到的结果为逆向的,所以输出时需逆向输出。结果为:BCBA

猜你喜欢

转载自blog.csdn.net/SWEENEY_HE/article/details/82261865