最长公共子序列问题(最新) 联系客服

发布时间 : 星期一 文章最长公共子序列问题(最新)更新完毕开始阅读e29ba105bed5b9f3f90f1c83

算法作业:

LCS 问 题

作业要求:设计一个算法求出两个序列的所有LCS,分析最坏情况,用“会计方法”证明利用b[i][j]求出所有LCS的算法在最坏情况下时间复杂度为O(Cnm?m)

1、 算法思路:

根据最长公共子序列问题的性质,即经过分解后的子问题具有高度重复性,并且具有最优子结构

性质,采用动态规划法求解问题。设X={x1, x2, … , xn}, Y={y1, y2, … , ym}, 首先引入二维数组C[i][j]记录Xi和Yj的LCS的长度,定义C[i][j]如下:

C[i][j]??0,i?0或j?0 C[i?1][j?1]?1,i,j?0且xi?yjmax{C[i?1][j],C[i][j?1]},i,j?0且xi?yj

为了构造出LCS,还需要使用一个二维数组b[m][n],b[i][j]记录C[i][j]是通过哪个子问题的值求得的,以决定搜索的方向,欲求出所有的LCS,定义数组b如下: 设1-对角线方向;2-向上;3-向左;4-向上或向左 若X[i]=Y[j],b[i][j] = 1,

若C[i-1][j][i][j-1], 则b[i][j] = 3, 若C[i-1][j]=[i][j-1], 则b[i][j] = 4,

根据以上辅助数组C和b的定义,算法首先需要求出这两个数组, C[m][n]中记录的最长公共子序列的长度,b中记录了查找子序列元素的搜索方向。

利用C和b的信息,Find_All_LCS可以采用回溯法求出所有的LCS。基本思路如下:使用一个辅助数组记录每次调用Find_All_LCS得到的LCS中的元素,每次递归调用一次Find_All_LCS,进入一个新的执行层,首先要判断当前处理的两个子序列长度是否大于等于0 ,若不满足,则该层的递归结束,返回上一层;然后再判断当前得到的子序列是否等于数组C中求出的最长公共子序列长度,若等于,则说明算法执行到此已经得到一个LCS,按序输出;若不等于,此时根据数组b中记录的搜索方向继续搜索,特别要说明的是,当b[i][j]=4时,即要向上或向左,需要对这两个方向分别调用Find_All_LCS,保证沿着这两个方向上LCS元素不被漏掉,都可以搜索到;若b[i][j]=1,即沿对角线方向搜索前进时,此时元素X[i]为LCS中的元素,存放至辅助数组中去,同时将当前已经求得的LCS长度增1,当递归调用Find_All_LCS从b[i][j]=1处时,需要回溯一步,搜索其它路径上可能为LCS中的元素。当所有的可能路径都已经搜索完,算法结束。 对于某些情况会输出重复的LCS,这是因为算法在沿不同路径搜索时可能会出现相同的LCS序列。 2、 时间复杂度分析

由上述对Find_All_LCS 算法的分析可知,求出所有的LCS实际上是根据搜索的方向信息遍历所

有的路径找出满足条件的元素集合。因此,除求解辅助数组C和b所用的O(mn+m+n)的执行时间外,Find_All_LCS的时间复杂度取决于所遍历路径数。而路径数是由搜索方向决定的。显然算法在最好的情况下,即m=n并且b中所有的值都指示沿着对角线方向搜索,时间复杂度为O(n). 相反,当X和Y序列不存在公共子序列时为算法的最坏情况,此时C中所有值都等于0,数组b中所有的值都指示要分别沿两个不同的方向(向左或向上)搜索,这种情况下每处理一次X[i],Y[j]时总是要沿两个方向分别调用Find_All_LCS,遇到i=0或j=0时返回,直到搜索完所有的可能路径才结束,最坏情况下的搜索矩阵如下图所示:

由此可知,要确定最坏情况的时间复杂度,就是要计算出从点(m,n)到i=0或j=0(除(i,j)=(0,0)外)的所有路径。求解转化为组合数学中的路径数问题。建立一个直角坐标系如上图中所示,对于坐标系中的点可以沿向上或向左两个方向前进,设点S(m,n),x轴上的坐标点

P1(1,0),P2(2,0),...,Pi(i,0),...,Pm(m,0),y轴上的系列坐标点Q1(0,1),Q2(0,2),...,Qj(0,j),...,Qn(0,n),其

中1?i?n,1?j?m.

因为Pi和Qj是搜索路径的边界上的点,点Pi?1不能直接到达点Pi,点Qj?1也不能直接到达Qj,所以点S(m,n)到P1,P2,...,Pi,...,Pm和Q1,Q2,...,Qj,...,Qn的路径数等价于S(m,n)到点

P1'(1,1),P2'(2,1),.Pi.'(i.,1),,.Pm.'(.m,,1)和点Q1'(1,1),Q2'(1,2),...,Qj'(1,j),...,Qn'(1,n)的路径数,又因为

点(m,n)到(i,j)路径数为Cm?n?i?j,设总路径数为t,则有

nmn?im?1?n?in?it??Ci?1??Cj?1m?jn?1?m?j

根据组合数学的基本知识可得:

nmn?im?1?n?it??Ci?1n?1n?1n??Cj?1m?jn?1?m?j?Cn?m?2?Cn?m?3?...?Cm?Cm?1?Cn?m?2?Cn?m?3?...?Cn?Cn?1 ?Cn?m?1?Cn?m?1?Cn?m?1?Cn?m?1?Cn?m??Cn?mmm?1n?1n?n?210??m?1m?210?[注]:参考卢开澄编著的《组合数学(第3版)》P34-P38

最坏情况下算法搜索的所有路径为从S到P1,P2,...,Pi,...,Pm和Q1,Q2,...,Qj,...,Qn的所有路径数Cn?m,因此算法在最坏情况下的时间复杂度为O(Cn?m). 3、 算法实现

mm/*文件名:FindLongestCommonSequence.cpp 说明:求出两个序列中所有的最长公共子序列 日期:2005/10/12*/ #include #include #define MAXSIZE 100

using namespace std;

int C[MAXSIZE][MAXSIZE] ; //记录序列X和Y的LCS的长度

int B[MAXSIZE][MAXSIZE] ;//记录二维数组C是通过哪一个子问题的值求得的,以决定搜索的方向:1-对角线方向;2-向上;3-向左;4-向上或向左

char LCS[MAXSIZE]; int len = 0;

/*函数名:LCS_Len

功能:自底向上进行递推计算C[i][j],并确定B中的搜索方向 若i =0 或 j =0 则C[i][j] = 0;

若i,j > 0且x[i] = y[j] 则C[i][j] = C[i-1][j-1] + 1;

若i,j > 0且x[i]!= y[j] 则C[i][j] = max{C[i-1][j],C[i][j-1]) 输入:两个序列X和Y 输出:二维数组B和C*/ void LCS_Len(char* X, char* Y) {

int m = strlen(X)-1; int n = strlen(Y)-1;

for(int i = 0; i < m; i++ ) C[i][0] = 0; for(int j = 1; j < n; j++ ) C[0][j] = 0; 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] = 1;

else if ( C[i-1][j] > C[i][j-1] ) { } { }

C[i][j] = C[i][j-1]; B[i][j] = 3; C[i][j] = C[i-1][j]; B[i][j] = 2;

else if ( C[i-1][j] < C[i][j-1] )

else /*C[i-1][j] == C[i][j-1]*/

{

C[i][j] = C[i-1][j]; B[i][j] = 4; } }

/*

函数名:Fine_All_LCS

功能:回溯法递归输出所有的LCS 参数说明:

Direction : 记录搜索方向的二维数组,1-对角线方向;2-向上;3-向左;4-向上或向左 X : 一个序列

i,j : 待搜索的下标,初始值为两个序列的长度 len: 记录当前LCS的长度 lcslen: LCS的长度

LCS:记录当前得到的LCS字符*/

void Find_All_LCS(int Direction[MAXSIZE][MAXSIZE], char* X, int i, int j, int curlen, int lcslen) {

if(i>=0 && j>=0) {

if(len == lcslen)//如果当前lcs的长度等于已知的LCS的长度则输出子序列 {

for( int k = len - 1 ; k >= 0 ; k-- ) cout << LCS[k]; cout << \

}

} else {

if(Direction[i][j] == 1) { }

else if(Direction[i][j] == 2) { }

else if(Direction[i][j] == 3) { Find_All_LCS(Direction, X, i, j-1, curlen, lcslen);

Find_All_LCS(Direction, X, i-1, j, curlen, lcslen); LCS[curlen] = X[i];

len++; //子序列长度加1

Find_All_LCS(Direction, X, i-1, j-1, curlen + 1, lcslen);//沿对角线方向搜索 len--; //回溯

}

}

}

else //递归调用Find_All_LCS分别沿两个不同的方向搜索 { }

Find_All_LCS(Direction, X, i, j-1, curlen, lcslen); Find_All_LCS(Direction, X, i-1, j, curlen, lcslen);

}

int main() { }

4、 运行效果 运行效果演示:

char *x, *y;

x = \单元未用,下标从1开始 y = \

int m = (int)strlen(x)-1; int n = (int)strlen(y)-1; int lcslen;

cout << \cout << \

cout << \ LCS_Len(x, y);

lcslen = C[m][n];

Find_All_LCS(B, x, m, n, 0, lcslen); cout << \return 0;