最长公共子序列

一: 作用

       最长公共子序列的问题常用于解决字符串的相似度,是一个非常实用的算法,作为码农,此算法是我们的必备基本功。

二:概念

     举个例子,cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如其中的cb,cgs等等都是其子序列,我们可以看出

子序列不见得一定是连续的,连续的那是子串。

     我想大家已经了解了子序列的概念,那现在可以延伸到两个字符串了,那么大家能够看出:cnblogs和belong的公共子序列吗?

在你找出的公共子序列中,你能找出最长的公共子序列吗?

从图中我们看到了最长公共子序列为blog,仔细想想我们可以发现其实最长公共子序列的个数不是唯一的,可能会有两个以上,

但是长度一定是唯一的,比如这里的最长公共子序列的长度为4。

 

三:解决方案

<1> 枚举法

       这种方法是最简单,也是最容易想到的,当然时间复杂度也是龟速的,我们可以分析一下,刚才也说过了cnblogs的子序列

个数有27个 ,延伸一下:一个长度为N的字符串,其子序列有2N个,每个子序列要在第二个长度为N的字符串中去匹配,匹配一次

需要O(N)的时间,总共也就是O(N*2N),可以看出,时间复杂度为指数级,恐怖的令人窒息。

 

<2> 动态规划

      既然是经典的题目肯定是有优化空间的,并且解题方式是有固定流程的,这里我们采用的是矩阵实现,也就是二维数组。

第一步:先计算最长公共子序列的长度。

第二步:根据长度,然后通过回溯求出最长公共子序列。

现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},

设一个C[i,j]: 保存Xi与Yj的LCS的长度。

递推方程为:

不知道大家看懂了没?动态规划的一个重要性质特点就是解决“子问题重叠”的场景,可以有效的避免重复计算,根据上面的

公式其实可以发现C[i,j]一直保存着当前(Xi,Yi)的最大子序列长度。

定理: LCS的最优子结构性质

设序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的一个最长公共子序列Z=<z1, z2, …, zk>,则:

       1. 若xm=yn,则zk=xm=yn且Zk-1是Xm-1和Yn-1的最长公共子序列;
       2. 若xm≠yn且zk≠xm ,则Z是Xm-1和Y的最长公共子序列;
       3. 若xm≠yn且zk≠yn ,则Z是X和Yn-1的最长公共子序列。

其中Xm-1=<x1, x2, …, xm-1>,Yn-1=<y1, y2, …, yn-1>,Zk-1=<z1, z2, …, zk-1>。


最长公共子序列
问题描述
最长公共子序列是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。
最长公共子序列也称作最长公共子串(不要求连续),英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。
例如,若x=和y=,则序列是x和y的一个公共子序列,序列<B,C,B,A>也是x和y的一个公 共子序列。而且,后者是x和y的一个最长公共子序列,因为x和y没有长度大于4的公共子序列。
输入
输入数据有T组测试数据。测试数据的数目 (T)在输入的第一行给出。每组测试数据有两行:每行有一个字符串,每个字符串的长度都不超过20。
输出
每个用例,用一行输出其最大公共子序列的长度,如果没有公共子序列,则输出0。
样例输入
2
abceef
1235896
ABCBDAB
BDCABA
样例输出
0
4

#include<iostream>
using namespace std;
int M[101][101];    //公共子序列数组
char a[101];        //第一个字符串,下标从1开始
char b[101];        //第二个字符串,下标从1开始
//n,m是两个字符串的长度
void work(int n,int m)
{
int i,j;
    //将第一行清零
    for(i=1;i<=n;i++)
M[i][0]=0;
//将第一列清零
    for(j=1;j<=m;j++)
M[0][i]=0;
//填充其它格子内容
//方法是
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
if(a[i-1]==b[j-1]) //i,j从1开始,但字符串是从0开始 
M[i][j]=M[i-1][j-1]+1;
//本来有三种情况,但是M[i][j]=M[i-1][j-1]可以不要添加进来
//就如下两种情况就可以啦
else 
                M[i][j]=M[i][j-1];
}
}
}


int main()
{
int k;
cin>>k;  //输入用例个数
getchar(); //getchar()是stdio.h中的库函数,它的作用是从stdin流中读入一个字符也就是说,
          //如果stdin有数据的话不用输入它就可以直接读取了,第一次getchar()时,确实需要人工的输入,
          //但是如果你输了多个字符,以后的getchar()再执行时就会直接从缓冲区中读取了。
while(k--)
{
gets(a); //表示从键盘接收一个字符串,并放到数组a中,
gets(b); //表示从键盘接收一个字符串,并放到数组b中,
work(strlen(a),strlen(b)); //strlen返回字符个数.
cout<<M[strlen(a)][strlen(b)]<<endl;
}
return 0;
}


ABCBDAB

BDCABA

 

第一个串长为7

第二个串长为6

思路如下:

各串都删除最后一个字符,则得到如下:

ABCBDA   B

BDCAB   A

显然删除的最后字符不相同,则原串的最长公共子序列应为:

ABCBDA    B

BDCABA

ABCBDAB

BDCAB     A

中的最长公共子序列中的某个值。

用C[n][m]表示最长公共子序列长度,则如果后两个字符不相同,C[n][m]的值应为C[n-1][m]和C[n][m-1]中最大的一个。

如果相同,例如两个原串为:

ABCBDA

BDCA

最后都为A,则原串的最长公共子序为原串都删除最后一个字符得到的最长值+1,即C[n][m]=C[n-1][m-1]+1。

由此可知,即求C[i][j],需要先求C[i-1][j-1],C[i-1][j]和C[i][j-1],即其左上角,上面和左边的值。

结合经验,显然可以对C[][]先按行,后按列求出其值,得到[n][m]即为结果。

需要注意的是C[i][j]表示只取第一个串的前i个字符和第二个串的前j个字符,即忽略后面的字符。

例:

ABCBDAB

BDCABA

得到C[][]矩阵如下:

 

 

 

B

D

C

A

B

A

 

i\j

0

1

2

3

4

5

6

 

0

0

0

0

0

0

0

0

A

1

0

0

0

0

1

1

1

B

2

0

1

1

1

1

2

2

C

3

0

1

1

2

2

2

2

B

4

0

1

1

2

2

3

3

D

5

0

1

2

2

2

3

3

A

6

0

1

2

2

3

3

4

B

7

0

1

2

2

3

4

4

行列对比,不同为0,相同则进1,再不同则,则仍为1,再相同则再进1

 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值