【算法】LCS最長公共子序列

LCS—Longest Common Subsequence

最長公共子序列。一個序列,如果是兩個或多個已知序列的子序列,且是所有子序列中最長的,則爲最長公共子序列。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define SIZE 100

int main()
{
	char A[SIZE],B[SIZE];
	int i,j,n;

	printf("輸入字符串A:");
	char *p1 = &(A[1]);
	gets(p1);
	A[0] = '0';
	printf("輸入字符串B:");
	char *p2 = &(B[1]);
	gets(p2);
	B[0] = '0';

	const int len_A = strlen(A)-1;
	const int len_B = strlen(B)-1;

/*數組c用來存放LCS的長度*/
	int **c;
	c = (int **)malloc(sizeof(int*)*(len_A+1));
	for(i=0;i<len_A+1;i++)
		c[i]=(int *)malloc(sizeof(int)*(len_B+1));
/*b用來指明LCS的路徑*/
	char **b;
	b = (char **)malloc(sizeof(char*)*(len_A+1));
	for(i=0;i<len_A+1;i++)
		b[i]=(char *)malloc(sizeof(char)*(len_B+1));

	char *result;
	result = (char *)malloc(sizeof(char *)*(len_A+1));

	for(i=0;i<=len_A;i++)
	{
		c[i][0] = 0;
		b[i][0] = '0';
	}

	for(j=0;j<=len_B;j++)
	{
		c[0][j] = 0;
		b[0][j] = '0';
	}

	for(i=1;i<=len_A;i++)
		for(j=1;j<=len_B;j++)
		{
			if(A[i] == B[j])
			{
				c[i][j] = c[i-1][j-1] + 1;
				b[i][j] = 'b';
			}
			else if(c[i-1][j] >= c[i][j-1])
			{
				c[i][j] = c[i-1][j];
				b[i][j] = 'u';
			}
			else
			{
				c[i][j] = c[i][j-1];
				b[i][j] = 'l';
			}
		}

	for(i=0;i<=len_A;i++)
	{
		for(j=0;j<=len_B;j++)
			printf("%c  ",b[i][j]);
		printf("\n");
	}

	i = len_A;
	j = len_B;
	n = 0;
	while(b[i][j] != '0')
	{
		switch (b[i][j])
		{
		case 'u':
			i--;
			break;
		case 'l':
			j--;
			break;
		case 'b':
			result[n++] = A[i];
			i--;
			j--;
			break;
		}
	}

	free(c);
	free(b);
	result[n] = '\0';
	for(i=n-1;i>=0;i--)
		printf("%c",result[i]);
}

算法分析:

最優子結構:設 X = {x1,x2,……,xm}和Y = {y1,y2,……,yn}爲兩個序列,並設Z = {z1,z2,……,zk}爲X和Y的任意一個LCS。

1)如果xm=yn,那麼zk = xm = yn而且Zk-1是Xm-1和Yn-1的一個LCS。

2)如果xm≠yn,那麼zk≠xm蘊含Z是Xm-1和Y的一個LCS。

3)如果xm≠yn,那麼zk≠yn蘊含Z是X和Yn-1的一個LCS。

用c[i,j]表示序列Xi和Yj的一個LCS的長度。那麼有:

c[i,j] = 

case i = 0,j = 0 : c[i,j] = 0;

case i,j > 0;xi = yj:  c[i,j] = c[i-1,j-1] + 1;

case i,j > 0;xi ≠ yj:  c[i,j] = max(c[i,j-1],c[i-1,j])

僞代碼:

LCS_LENGTH(X,Y)
m ← length[X]
n ← length[Y]
for i ← 1 to m
        do c[i,0] ← 0
for j ← 1 to n
        do c[0,j] ← 0
for i ← 1 to m
        do for j ← 1 to n
                     do if xi = yj
                                then c[i,j] ← c[i-1,j-1] + 1
                                         b[i,j] ← "↖"
                                else if c[i-1,j] ≥ c[i,j-1]
                                            then c[i,j] ← c[i-1,j]
                                                     b[i,j] ← "↑"
                                           else c[i,j] ← c[i,j-1]
                                                    b[i,j] ← "←"
return c and b

流程圖:

《【算法】LCS最長公共子序列》

其中數組c中,c[i,j]表示Xi和Yj的一個LCS的長度,剛剛已經說過了,數組b則表示找到最優解的路徑。下面舉例,

X = {A,B,C,B,D,A,B}

Y = {B,D,C,A,B,A}

m ← length[X]
n ← length[Y]
for i ← 1 to m
        do c[i,0] ← 0
for j ← 1 to n
        do c[0,j] ← 0

對c表初始化,填0

《【算法】LCS最長公共子序列》

for i ← 1 to m
        do for j ← 1 to n
                     do if xi = yj
                                then c[i,j] ← c[i-1,j-1] + 1
                                         b[i,j] ← "↖"
                                else if c[i-1,j] ≥ c[i,j-1]
                                            then c[i,j] ← c[i-1,j]
                                                     b[i,j] ← "↑"
                                           else c[i,j] ← c[i,j-1]
                                                    b[i,j] ← "←"

這裏有兩層for循環,外層循環length_A次,內層length_B次。

逐步分析for循環:

《【算法】LCS最長公共子序列》

此時,表更新爲:

《【算法】LCS最長公共子序列》
i=2再循環

《【算法】LCS最長公共子序列》

表c變化:

《【算法】LCS最長公共子序列》

通過兩個j的循環可以看出,每次填入c[i,j]的值,都是查詢之前填入的,也就是查表,同時不修改原先的值。通過最優子結構來獲得最優值,也就是說,當子結構是最優時,那麼通過cut-and-paste,可以得出,最優的解。這就是動態規劃的思想。

通過上面兩步,我們可以驗證一下,雖然j只取了2次,但是我們可以驗證X={A,B} Y={B,D,C,A,B,A}的LCS。

《【算法】LCS最長公共子序列》

如上圖所示,LCS爲AB,雖然這個結果很特別,X就是Y 的子集,但是還是可以看出動態規劃得到最優解的思路。

点赞