算法导论-第15章-动态规划-15.3 最长公共子序列

来源:互联网 发布:enum class java 编辑:程序博客网 时间:2024/04/30 17:15

一: 作用

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

二:概念

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

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

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

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

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

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


三:解决方案

 动态规划

现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},二维数组C[i,j]: 保存Xi与Yj的LCS的长度。

递推方程为:

recursive formula

回溯输出最长公共子序列过程:

X=[ A,B,C,B,D,A,B ]

Y=[ B,D,C,A,B,A ]

flow

代码:

#include <iostream>using namespace std;#define M 8#define N 9//b记录的是,箭头的方向,2代表向左上角,1代表向上,3代表向左,后面根据箭头的方向确定公共序列(此数组也可以省略)//c记录的是LCS的长度int b[M+1][N+1] = {0}, c[M+1][N+1] = {0};int c2[2][M+1] = {0};/********书上的伪代码*******************************************/void Lcs_Length(int *x, int *y){int i, j;//初始化for(i = 1; i <= M; i++)c[i][0] = 0;for(j = 1; j <= N; j++)c[0][j] = 0;//根据公式15.14计算,算法复杂度为:O(mn)for(i = 1; i <= M; i++){for(j = 1; j <= N; j++){//记录计算结果if(x[i] == y[j]){c[i][j] = c[i-1][j-1] + 1;b[i][j] = 2;}else{if(c[i-1][j] >= c[i][j-1]){c[i][j] = c[i-1][j];b[i][j] = 1;}else{c[i][j] = c[i][j-1];b[i][j] = 3;}}}}}//根据 b 确定输出,算法复杂度为:O(m+n)void Print_Lcs(int *x, int i, int j){if(i == 0 || j == 0)return;if(b[i][j] == 2){Print_Lcs(x, i-1, j-1);cout<<x[i]<<' ';//输出x[i],而不是c中的数}else if(b[i][j] == 1)Print_Lcs(x, i-1, j);elsePrint_Lcs(x, i, j-1);}//15.4-2 不使用表b的情况下计算LCS并输出void Lcs_Length2(int *x, int *y){int i, j;//初始化for(i = 1; i <= M; i++)c[i][0] = 0;for(j = 1; j <= N; j++)c[0][j] = 0;//求LCS的时间没有什么区别,只要把与b有关的去掉就可以了for(i = 1; i <= M; i++){for(j = 1; j <= N; j++){//第一种情况if(x[i] == y[j])c[i][j] = c[i-1][j-1] + 1;else{//第二种情况if(c[i-1][j] >= c[i][j-1])c[i][j] = c[i-1][j];//第三种情况elsec[i][j] = c[i][j-1];}}}}//区别在于输出,根据计算反推出前一个数据,而不是通过查找获得void Print_Lcs2(int *x, int i, int j){//递归到初始位置了if(i == 0 || j == 0)return;//三种情况,刚好与Lcs_Length2中的三种情况相对应(不是按顺序对应)//第二种情况if(c[i][j] == c[i-1][j])Print_Lcs2(x, i-1, j);//第三种情况else if(c[i][j] == c[i][j-1])Print_Lcs2(x, i, j-1);//第一种情况else{//匹配位置Print_Lcs2(x, i-1, j-1);//属于最长子序列的字符肯定不与左和上相等cout<<x[i]<<' ';}}//15.4-3备忘录版本,类似于递归,只是对做过的计算记录下来,不重复计算//每一次迭代是x[1..m]和y[1..n]的匹配int Lcs_Length3(int *x, int *y, int m, int n){//递归结束条件,长度为0,肯定匹配为0if(m == 0|| n == 0)return 0;//若已经计算,直接返回结果if(c[m][n] != 0)return c[m][n];//公式15.14的对应if(x[m] == y[n])c[m][n] = Lcs_Length3(x, y, m-1, n-1) + 1;else{int a = Lcs_Length3(x, y, m-1, n);int b = Lcs_Length3(x, y, m, n-1);c[m][n] = a > b ? a : b;}return c[m][n];}//15.4-4(1)使用2*min(m,n)及O(1)的额外空间来计算LCS的长度void Lcs_Length4(int *x, int *y){int i, j;//c2是2*min(M,N)的矩阵,初始化memset(c2, 0 ,sizeof(c2));//类似于上文的循环,只是i%2代表当前行,(i-1)%2代表上一行,其余内容相似for(i = 1; i <= N; i++){for(j = 1; j <= M; j++){if(y[i] == x[j])c2[i%2][j] = c2[(i-1)%2][j-1] + 1;else{if(c2[(i-1)%2][j] >= c2[i%2][j-1])c2[i%2][j] = c2[(i-1)%2][j];elsec2[i%2][j] = c2[i%2][j-1];}}}//输出结果cout<<c2[N%2][M]<<endl;}void Lcs_Length5(int *x, int *y){int i, j, temp = 0;memset(c2, 0 ,sizeof(c2));for(i = 1; i <= N; i++){for(j = 1; j <= M; j++){if(y[i] == x[j])c2[i%2][j] = c2[(i-1)%2][j-1] + 1;else{if(c2[(i-1)%2][j] >= c2[i%2][j-1])c2[i%2][j] = c2[(i-1)%2][j];elsec2[i%2][j] = c2[i%2][j-1];}}}cout<<c2[N%2][M]<<endl;}void Print(){int i, j;for(i = 1; i <= M; i++){for(j = 1; j <= N; j++)cout<<c[i][j]<<' ';cout<<endl;}}int main(){int x[M+1] = {0,1,0,0,1,0,1,0,1};int y[N+1] = {0,0,1,0,1,1,0,1,1,0};Lcs_Length(x, y);//Print();Print_Lcs(x, M, N);//Lcs_Length2(x, y);//Lcs_Length3(x, y, M, N);//Print();//Print_Lcs2(x, M, N);//Lcs_Length4(x, y);return 0;}



0 0
原创粉丝点击