最長公共子序列

最長公共子序列問題(LCS)

問題描述:

         所謂子序列是子序列是從最初序列通過去除某些元素但不破壞餘下元素的相對位置而形成的新序列。形如X=<x1, x2, x3………, xm>這個序列,對於Z=<xi1, xi2 , xi3 ………, xik>就是X的子序列。e.g. X=<A,B,C,D,E,F,G,H> Z=<A,D,F>就是X的一個子序列。

         公共子序列就是多個序列(如X,Y),現如果存在Z,且Z是X的子序列,Z又是Y的子序列,則Z就是X和Y的公共子序列。

         最長公共子序列的概念,從以上關於子序列和公共子序列的概念中,我們可以很簡單的知道,最長公共子序列就是多個序列的公共子序列中最長的一個。e.g. X=<A,B,C,B,D,A,B>, Y=<B,D,C,A,B,A>則Z1=<B,C,B,A>, Z2=<B,C,A,B>, Z3=<B,D,A,B>都是X和Y的最長公共子序列,長度爲4,而且我們從此可以得知最長公共子序列往往不是唯一的喲。

問題求解:

        對此問題用的是DP(動態規劃)的方法解決的,即使可以一眼看出來用brute-force(蠻力)依次匹配。但是這個蠻力的效率實在是不行啊。下面我們就開始對LCS問題的DP解法:

        分析:

        記Xi=﹤x1,…,xi﹥即X序列的前i個字符 (1≤i≤m)(前綴)Yj=﹤y1,…,yj﹥即Y序列的前j個字符 (1≤j≤n)(前綴)。假定Z=﹤z1,…,zk﹥∈LCS(X , Y)。

        若xm=yn(最後一個字符相同),則不難用反證法證明:該字符必是X與Y的任一最長公共子序列Z(設長度爲k)的最後一個字符,即有zk = xm = yn ,且顯然有Zk-1∈LCS(Xm-1 , Yn-1),即Z的前綴Zk-1是Xm-1與Yn-1的最長公共子序列。

        若xm≠yn,則亦不難用反證法證明:要麼Z∈LCS(Xm-1, Y),要麼Z∈LCS(X , Yn-1)。由於zk≠xm與zk≠yn其中至少有一個必成立因此:若zk≠xm則有Z∈LCS(Xm-1 , Y),若zk≠yn 則有Z∈LCS(X , Yn-1)。     

        ∴若xm=yn,則問題化歸成求Xm-1與Yn-1的LCS,即(LCS(X , Y)的長度等於LCS(Xm-1 , Yn-1)的長度加1) 

            若xm≠yn,則問題化歸成求Xm-1與Y的LCS及X與Yn-1的LCSLCS(X , Y)的長度爲:Max {LCS(Xm-1 , Y)的長度, LCS(X , Yn-1)的長度}

         這兩個問題不是相互獨立的:

         ∵兩者都需要求LCS(Xm-1,Yn-1)的長度,因而具有重疊性。此外,兩個序列的LCS中包含了兩個序列的前綴的LCS,故問題具有最優子結構性質  考慮用動態規劃法。

 

         現引進一個二維數組C,

         用C[i,j]記錄Xi與Yj的LCS的長度  如果我們是自底向上進行遞推計算,那麼在計算C[i,j]之前,

         C[i-1,j-1], C[i-1,j]與C[i,j-1]均已計算出來。此時根據X[i]=Y[j]還是X[i]Y[j],就可以計算出C[i,j]:

         若X[i]=Y[j],則執行C[i,j]←C[i-1,j-1]+1;

         若X[i]≠Y[j],進行下述判斷:

               若C[i-1,j]≥C[i,j-1]則C[i,j]取C[i-1,j];

               否則C[i,j]取C[i,j-1]。

         即有

      

        爲了構造出LCS,使用一個mn的二維數組b,b[i,j]記錄C[i,j]是通過哪一個子問題的值求得的,以決定搜索的方向:

         若X[i]=Y[j],則b[i,j]中記入“↖”;

         否則

                 若C[i-1,j]=C[i,j-1],則b[i,j]中記入“↑或←”

                 若C[i-1,j]>C[i,j-1],則b[i,j]中記入“↑”;

                 若C[i-1,j] < C[i,j-1],則b[i,j]中記入“←”;

         這樣就可以記錄下來尋找LCS的路徑了。

代碼示例:

       用C語言對LCS問題進行了實現,基本上得到了預期結果。

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

#define		MAX_LEN		512
#define		TRACE_END	0
#define		UP			1
#define		LEFT		2
#define		UP_AND_LEFT	3
#define		UP_OR_LEFT	4

typedef char **(sub_strs_ptr);//to store lcs resualts

int is_exited_sstrs(sub_strs_ptr sstrs,int sstrs_len,char *new_str){
	int i;
	for(i=0;i<sstrs_len;i++)
		if(!strcmp(*(sstrs+i),new_str)) return 1;
	return 0;
}

void display_lcs(int *trace_tab,char* str,int n,int i,int j,char* out,int k,sub_strs_ptr sstrs,int *sstrs_num)
{
	if(trace_tab[i*(n+1)+j] == TRACE_END) {
		if(!is_exited_sstrs(sstrs,*sstrs_num,out)){
			strcpy(*(sstrs+*sstrs_num),out);
			(*sstrs_num)++;
			printf("%d:%s\n",*sstrs_num,out);
		}
		return;
	}
	if(trace_tab[i*(n+1)+j] == UP_AND_LEFT)
	{
		out[k--]=str[i-1];//got a char
		display_lcs(trace_tab,str,n,i-1,j-1,out,k,sstrs,sstrs_num);
	}
	else if(trace_tab[i*(n+1)+j] == UP)
		display_lcs(trace_tab,str,n,i-1,j,out,k,sstrs,sstrs_num);
	else if(trace_tab[i*(n+1)+j] == LEFT)
		display_lcs(trace_tab,str,n,i,j-1,out,k,sstrs,sstrs_num);
	else
	{//UP OR LEFT
		display_lcs(trace_tab,str,n,i-1,j,out,k,sstrs,sstrs_num);
		display_lcs(trace_tab,str,n,i,j-1,out,k,sstrs,sstrs_num);
	}
}

int main()
{
	int i,j;
	int m,n,*lcs_tab,*trace_tab,lcs_len,sstrs_num;
	char str_a[MAX_LEN],str_b[MAX_LEN],out[MAX_LEN];
	sub_strs_ptr sstrs;
	
	scanf("%s%s",str_a,str_b);
	m=strlen(str_a);
	n=strlen(str_b);

	lcs_tab=(int*)malloc((m+1)*(n+1)*sizeof(int));
	trace_tab=(int*)malloc((m+1)*(n+1)*sizeof(int));/*store by row*/
	if(!lcs_tab || !trace_tab) exit(-2);

	for(i=0;i<=m;i++) { lcs_tab[(n+1)*i]=0;	trace_tab[(n+1)*i]=TRACE_END;}
	for(i=0;i<=n;i++) { lcs_tab[i]=0;	trace_tab[i]=TRACE_END;}

	for(i=1;i<=m;i++)
		for(j=1;j<=n;j++)
		{
			if(str_a[i-1] == str_b[j-1])
			{
				lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+(j-1)]+1;
				trace_tab[i*(n+1)+j]=UP_AND_LEFT;
			}
			else
			{
				if(lcs_tab[(i-1)*(n+1)+j] == lcs_tab[i*(n+1)+j-1])
				{
					lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+j];
					trace_tab[i*(n+1)+j]=UP_OR_LEFT;
				}
				else if(lcs_tab[(i-1)*(n+1)+j] > lcs_tab[i*(n+1)+j-1])
				{
					lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+j];
					trace_tab[i*(n+1)+j]=UP;
				}
				else
				{
					lcs_tab[i*(n+1)+j]=lcs_tab[i*(n+1)+j-1];
					trace_tab[i*(n+1)+j]=LEFT;
				}
			}
		}

	printf("lcs_tab:\n");
	for(i=0;i<=m;i++){
		for(j=0;j<=n;j++)
			printf("%-2d",lcs_tab[i*(n+1)+j]);
		printf("\n");
	}
	printf("\ntarce_tab:\n");
	for(i=0;i<=m;i++){
		for(j=0;j<=n;j++){
			switch(trace_tab[i*(n+1)+j]){
			case TRACE_END:printf("●   ");break;
			case UP:printf("↑   ");break;
			case LEFT:printf("←   ");break;
			case UP_AND_LEFT:printf("↖   ");break;
			case UP_OR_LEFT:printf("←↑ ");break;
			}
			//printf("%-2d",trace_tab[i*(n+1)+j]);
		}
		printf("\n");
	}

	lcs_len=lcs_tab[m*(n+1)+n];
	printf("\nlcs's len = %d\n",lcs_len);

	int min_len=m>n?n:m;
	sstrs=(char**)malloc(min_len*sizeof(char*));
	for(i=0;i<min_len;i++)
		*(sstrs+i)=(char*)malloc(MAX_LEN*sizeof(char));
	
	sstrs_num=0;
	if(lcs_len != 0){
		out[lcs_len]='\0';
		display_lcs(trace_tab,str_a,n,m,n,out,lcs_len-1,sstrs,&sstrs_num);
	}
	printf("in total %d kinds lcs!\n",sstrs_num);

	return 0;
}


運行結果示意圖爲:

       

LCS問題就到這裏啦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章