本文地址:http://blog.youkuaiyun.com/spch2008/article/details/9338939
算法课考了一个最长公共子序列,考傻了。原题如下:
一个序列S,若分别是两个或多个已经序列的子序列,且是所有符合条件的序列中最长的,则S称为已知序列的最长公共子序列。
比如{1,2,3,4,5,6}和序列{1,3,4,5,8,9}的最长公共子序列是{1,3,4,5}。利用动态规划法设计算法求最长公共子序列问题。
当时很纳闷,公共子序列为什么不是连续的,即{1,3,4,5}在{1,2,3,4,5,6}中并不连续啊,琢磨了半天还是没有解出。
后来经查询:连续的是公共子串,而公共子序列只要保证顺序不变即可,不要求连续。
假设有A,B两个序列,lenA = len(A),lenB = len(B);一个辅助矩阵c,大小c[lenA + 1][lenB + 1]。
上面的递推公式很容易理解:当xi = yj 时,公共子序列长度加1。如果xi != yj,这时,自然而然我们想到公共子序列长度
不变,等于前一个的长度。
假设xi与yj处于当前位置,xi != yj,那么可以认为序列{A, B, C}和序列{B,D,C,A}的最长公共子序列为序列{A,B,C}与序列{B,D,C}的
最长公共子序列;当然,也可以认为是{A,B}与{B,D,C,A}的最长公共子序列。那么我们求一个最长的,认为是{A,B,C}与{B,D,C,A}最长
公共子序列。这样,c[lenA][lenB]即为二个序列的最长公共子序列的长度值。
现在需要另外一个数组,我把它称作路径数组,记录最长公共子序列的产生过程。便于取得最长公共子序列。
当遇到左上箭头时,说明c[i][j] = c[i-1][j-1] + 1获得的,即若此时最长公共子序列为Ai、 Bj,则下一个公共子序列从Ai-1, Bj-1中取得。
当遇到上箭头,对比于c[i][j] = c[i-1][j-1] + 1,说明c[i][j] = c[i-1][j],说明最长公共子序列Ai、Bj是通过Ai-1,Bj获得的。
当遇到左箭头,说明c[i][j] = c[i][j-1],最长公共子序列Ai,Bj是通过Ai,Bj-1获得的。
其实很好理解,左上箭头,i与j同时减1;左箭头,在上图中,即i不变,j减1;而上箭头,j不变,i减1。
代码:
const int LeftUP = 1;
const int UP = 2;
const int Left = 3;
void Print(char *X, int **path, int i, int j)
{
if ( i == 0 || j == 0)
return;
//只有xi == yj,才说明该元素是最长公共子序列中一个元素。
if(path[i][j] == LeftUP)
{
Print(X, path, i-1, j-1);
cout << X[i-1] << " ";
}
else if(path[i][j] == UP)
{
Print(X, path, i - 1, j);
}
else
{
Print(X, path, i, j-1);
}
}
void LCS_IMP(char *X, char *Y, int lenX, int lenY, int **c, int **path)
{
//初始化
for(int i = 0; i < lenX + 1; i++)
{
c[i][0] = 0;
path[i][0] = 0;
}
for(int j = 0; j < lenY + 1; j++)
{
c[0][j] = 0;
path[0][j] = 0;
}
for(int i = 1; i < lenX + 1; i++)
{
for(int j = 1; j < lenY + 1; j++)
{
if(X[i-1] == Y[j-1])
{
c[i][j] = c[i-1][j-1] + 1;
path[i][j] = LeftUP;
}
else
{
if(c[i-1][j] >= c[i][j-1])
{
c[i][j] = c[i-1][j];
path[i][j] = UP;
}
else
{
c[i][j] = c[i][j-1];
path[i][j] = Left;
}
}
}
}
}
int LCS(char *X, char *Y, int lenX, int lenY)
{
//开辟数组空间
int **c = new int*[lenX + 1];
for(int i = 0; i < lenX + 1; i++)
c[i] = new int[lenY + 1];
int **path = new int*[lenX + 1];
for(int i = 0; i < lenX + 1; i++)
path[i] = new int[lenY + 1];
LCS_IMP(X, Y, lenX, lenY, c, path);
int len = c[lenX][lenY];
//打印序列
Print(X, path, lenX, lenY);
cout << endl;
//释放数组空间
for(int i = 0; i < lenX + 1; i++)
{
delete [] c[i];
delete [] path[i];
}
delete [] c;
delete [] path;
return len;
}
代码调优:
可以进一步优化,省略路径数组。二维数组c中的递推关系很容易推得最长公共序列的产生过程。
最长公共子序列Ai,Bj,如果c[i][j] = c[i-1][j-1] + 1,则说明Ai,Bj是通过Ai-1,Bj-1获得的。
只需数组c,即可推断产生序列。
void PrintLCS(char *X, char *Y, int **c, int i, int j)
{
if( i == 0 || j == 0)
return;
if(X[i] == Y[j])
{
PrintLCS(X, Y, c, i-1, j-1);
cout << X[i-1] << " ";
}
else if(c[i-1][j] > c[i][j-1])
{
PrintLCS(X, c, i-1, j);
}
else
{
PrintLCS(X, c, i, j-1);
}
}
//使用
//PrintLCS(X, Y, c, lenX, lenY)