最长公共子序列

本文地址: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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值