0
点赞
收藏
分享

微信扫一扫

经典算法之LCS最长公共子序列算法


一、    介绍

LCS是Longest Common Subsequence的缩写,即最长公共子序列。一个序列,如果是两个或多个已知序列的子序列,且是所有子序列中最长的,则为最长公共子序列。

例如:X=<A,B,C,E,A,C>和Y=<B,F,C,A,C,D>两个序列的最长公共子序列为Z=<B,C,A>

二、    动态规划算法思想

1)    最优子结构

---------------------图来自《算法导论P209页》

其实动态规划算法的思想最重要的一步也就是这一步,简单来说就是要找到构成最优解的一个子结构。假如这个子结构满足最优性,则可以采用动态规划自底向上来求解整体最优解。

本题中,我们假设序列X=<x1,x2,x3,x4……xm>和Y=<y1,y2,y3……yn>是需要求解最长公共子序列的两个串。我们假设X和Y存在一个最长公共子序列Z=<z1,z2,z3….zk>。

则Z和X、Y序列满足下列的关系式:

假如xm=yn,则我们可以取zk=xm,zk-1是xm-1和yn-1的最长公共子序列。假如X和Y存在某个最长公共子序列,且xm=yn,那么我们可以知道,至少xm或者yn两者之一是存在这个最长公共子序列中,否则两个都不存在的话,我们只要将xm和yn拼接上去,就得到一个更长的公共子序列,与假设不成立。假如只有xm在公共子序列中,而yn不在公共子序列中的时候,我们可以yn替换成Y的最后一个字符。同理如果xm不在公共子序列中,我们可以把xm和最后一位替换一下。那么可以保证取zk=xm都可以得到一个最长公共子序列。

如上图中,假如某个最长公共子序列的X是到k终止,Y序列是到n终止(不可能最长子序列X在m之前且Y序列在n之前,容易理解)。在这个情况下,我们只要将k直接移动到m上,即取m中的A为X序列中的最后一个字符即可。

假如xm≠yn,那么zk则需要从Xm-1和Yn或者Xm和Yn-1的最长公共子序列中取最大值。

2)    递归解

根据上述说明,可以得到下述递归解。其中c[i,j]表示序列Xi和Yj的最长公共子序列的长度。

---------------------图来自《算法导论P210页》

3)    自底向上求解

具体代码如下:

#include <iostream>
 #include <stack>
 using namespace std;
 //预定义
 #define m 7
 #define n 6
 //最长公共子序列函数
 void LCS(char* x,char* y)
 {
 //输出序列
 int out[n]={0};
 //要生成的矩阵
 int c[m+1][n+1];
 char b[m+1][n+1];
 //初始化
 for(int i=0;i<=m;i++)
 {
 c[i][0]=0;
 b[i][0]='-';
 }
 for(int j=0;j<=n;j++)
 {
 c[0][j]=0;
 b[0][j]='|';
 }
 for(i=0;i<m;i++)
 {
 for(j=0;j<n;j++)
 {
 if(x[i]==y[j])
 {
 //若两个元素相等,则加1
 c[i+1][j+1]=c[i][j]+1;
 b[i+1][j+1]='\\';
 }
 else
 {
 //若不相等,从最大值中传递
 if(c[i][j+1]>=c[i+1][j])
 {
 c[i+1][j+1]=c[i][j+1];
 b[i+1][j+1]='|';
 }
 else
 {
 c[i+1][j+1]=c[i+1][j];
 b[i+1][j+1]='-';
 }
 }
 }
 }
 //输出矩阵
 for(i=0;i<=m;i++)
 {
 for(j=0;j<=n;j++)
 {
 cout<<c[i][j]<<" ";
 }
 cout<<endl;
 }
 //输出方向标
 cout<<endl;
 for(i=0;i<=m;i++)
 {
 for(j=0;j<=n;j++)
 {
 cout<<b[i][j]<<" ";
 }
 cout<<endl;
 }
 cout<<endl;
 //最长公共子序列进栈
 i=m;j=n;
 stack<char> myStack;
 while(i>0 && j>0)
 {
 if(b[i][j]=='\\')
 {
 myStack.push(x[i-1]);
 }
 if(b[i][j]=='|')
 i--;
 else
 j--;
 }
 //出栈输出
 cout<<"最长公共子序列为:";
 while(!myStack.empty())
 {
 cout<<myStack.top();
 myStack.pop();
 }
 cout<<endl;
 }
 int main()
 {
 //调用函数
 LCS("ABCBDAB","BDCABA");
 return 0;
 }

4)    由解信息构造出最优解

输出结果和教材给出的示例图相同。为了方便实现,本算法没有动态构造二维数组,而是直接定义好了两个字符串的长度,直接作为已知参数来构造二维数组,这里也是为了更方便说明算法,而不想把时间花在C语言或者C++语言的具体细节上,最后构造最优解,书中是采用了递归的方法,这里直接在一个函数里,因此直接采用数据结构栈来实现相同的功能。

举报

相关推荐

0 条评论