1. 程式人生 > >使用動態規劃解決最長公共子序列問題

使用動態規劃解決最長公共子序列問題

一、定義:

給定兩個序列X和Y,如果Z既是X的子序列也是Y的子序列,那麼我們稱Z是X和Y的公共子序列。例如:X={a,b,c,e,d,g,f},Y={b,e,f,g},那麼Z={b}、Z={b,e}、Z={b,e,f}都是X和Y的公共子序列,其中Z={b,e,f}是X和Y的最長公共子序列。求解X和Y的最長公共子序列就是LCS問題。最長公共子序列不唯一,但是其長度是唯一的。

二、定理:

令X={x1,x2,...,xm}和Y={y1,y2,....yn}為兩個序列,Z={z1,z2,...zk}為這兩個序列的最長公共子序列,則:

①如果xm=yn,那麼zk = xm = yn,且Z(k-1)是X(m-1)和Y(n-1)的一個最長公共子序列;

②如果xm != yn,zk != xm,那麼Z(k-1)是X(m-1)和Yn的一個最長公共子序列;

③如果xm != yn,zk != yn,那麼Z(k-1)是Xm和Y(n-1)的一個最長公共子序列;

這樣我們在求解X和Y的LCS時,可以分解為求解一個或兩個這樣的子問題。當xm = yn,我們需要求解X(m-1)和Y(n-1)的LCS;當xm != yn時,我們需要求解X(m-1)和Yn的一個LCS,以及求解Xm和Y(n-1)的一個LCS,兩個較長者既為X和Y的LCS。

三、求解步驟:

如果用暴力搜尋法求解LCS問題,就要窮舉X的所有子序列,對於每個子序列都要檢查它是否也是Y的子序列,從而找到最長子序列。X的每個子序列對應下表集合{1,2,3,....,m},所以X有2^m個子序列,因此暴力搜尋法的執行時間為指數階,對較長的序列是不適用的,但是根據以上定理,LCS問題具有最優子結構性質。

我們定義一個二維陣列c[i][j]用來記錄Xi和Yj的LCS的長度。如果i=0,或者j=0,既一個序列長度為0,那麼LCS的長度為0。根據最優子結構性質,可得如下公式

  

上述公式適用於字串下標為0時儲存的是字串的長度,然而我們常見的字串是'\0'為結尾的,下標為0的內容儲存的並不是字串的長度,因此我們把公式稍作修改以適應解決我們常見的字串


根據以上公式我們,可以用動態規劃方法自頂向上地計算c[i][j]的值。

以下為計算c[i][j]的值,並給出測試程式:

#include <iostream>
#define MAX_LEN 100
using namespace std;
/*給定兩個字串X和Y,求出Xi和Yj的最長公共子序列的長度,記錄在c[i][j]陣列中*/
void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN]);

int main()
{
	
	char X[] = "abaddc";
	char Y[] = "badcaef";

	int m = strlen(X);
	int n = strlen(Y);
	int c[MAX_LEN][MAX_LEN];

	get_lcslength(X, m, Y, n, c);
	for (int i = 0; i < m;i++)
	{
		for (int j = 0; j < n;j++)
		{
			cout << c[i][j] <<"	";
		}
		cout << endl;
	}
	return 0;
}
<pre name="code" class="cpp">void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN])
{
	int i = 0, j = 0;
	/*初始化第0行,如果X[0]=Y[i],則說明LCS的長度為1*/
	while (i < Ylen && X[0] != Y[i])
	{
		c[0][i] = 0;
		i++;
	}

	for (; i < Ylen; i++)
		c[0][i] = 1;
	/*同理,初始化第0列*/
	while (j < Xlen && Y[0] != X[j])
	{
		c[j][0] = 0;
		j++;
	}

	for (; j < Xlen; j++)
		c[j][0] = 1;

	for (i = 1; i < Xlen;i++)
	{
		for (j = 1; j < Ylen;j++)
		{
			if (X[i] == Y[j])
				c[i][j] = c[i - 1][j - 1] + 1;
			else if (c[i - 1][j] >= c[i][j - 1])
				c[i][j] = c[i - 1][j];
			else
				c[i][j] = c[i][j - 1];
		}
	}
}

程式的執行結果如下:

我們一張表格來解釋上面的程式:

第i行和第j列記錄了c[i][j]的值,對於第0行第0列X[0] != Y[0],故c[0][0]=0,對於第0行第1列,X[0]=Y[1],因此c[0][1]=1,那麼第0行從第1列開始及以後的列都為1;同理可求出第0列的所有c[i][0]的值。對於i>0,j>0的,標項c[i][j]的值取決於X[i]是否等於Y[j]及c[i-1][j-1]、c[i-1][j]、c[i][j-1]的值,這些值都會在計算c[i][j]的值之前計算出來。c[5][6]記錄了X和Y的LCS的長度。

當我們求出二維陣列c的值之後,我們可以根據這個二維陣列求解LCS,通過上述分析我們可以瞭解到:c[i][j]的值只取決於c[i-1][j-1]、c[i-1][j]、c[i][j-1]這三項,給出c[i][j]的值我們可以在O(1)時間內判斷出計算c[i][j]的值使用了這三項中的哪一項,我們配合下面的圖說明一下演算法的實現原理:

圖中的箭頭表示當計算c[i][j]時所使用的上一個元素是誰,首先c[i][j]與c[i][j-1]作比較,如果相等則表明計算c[i][j]時使用的是c[i][j-1],此時令j=j-1;否則,c[i][j]與c[i-1][j]作比較,如果相等則表明計算c[i][j]時使用的是c[i-1][j],此時令i=i-1;否則表明計算c[i][j]時使用的是c[i-1][j-1],那麼這時X[i] == Y[j],將這個相等的字元記錄到str陣列中,並令j=j-1,i=i-1。進行迴圈比較直到i、j有一個出現0時為止。程式碼如下:

#include <iostream>
#define MAX_LEN 100
using namespace std;
/*給定兩個字串X和Y,求出Xi和Yj的最長公共子序列的長度,記錄在c[i][j]陣列中*/
void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN]);
/*給定兩個字串X和Y,將最長公共子序列儲存在str陣列中*/
char *lcs(const char X[], const char Y[], char str[]);
int main()
{
	
	char X[] = "abaddc";
	char Y[] = "badcaef";
	char str[MAX_LEN];
	lcs(X, Y, str);
	cout << "最長公共子序列為:" << str << "	長度為:" << strlen(str)<<endl;
	lcs("acbecda", "abcdaef", str);
	cout << "最長公共子序列為:" << str << "	長度為:" << strlen(str) << endl;
	lcs("gcbecda", "hbcdaef", str);
	cout << "最長公共子序列為:" << str << "	長度為:" << strlen(str) << endl;
	lcs("ghijklmna", "hbcdaef", str);
	cout << "最長公共子序列為:" << str << "	長度為:" << strlen(str) << endl;
	lcs("gijklmn", "hbcdaef", str);
	cout << "最長公共子序列為:" << str << "	長度為:" << strlen(str) << endl;
	return 0;
}

void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN])
{
	int i = 0, j = 0;
	/*初始化第0行,如果X[0]=Y[i],則說明LCS的長度為1*/
	while (i < Ylen && X[0] != Y[i])
	{
		c[0][i] = 0;
		i++;
	}

	for (; i < Ylen; i++)
		c[0][i] = 1;
	/*同理,初始化第0列*/
	while (j < Xlen && Y[0] != X[j])
	{
		c[j][0] = 0;
		j++;
	}

	for (; j < Xlen; j++)
		c[j][0] = 1;

	for (i = 1; i < Xlen;i++)
	{
		for (j = 1; j < Ylen;j++)
		{
			if (X[i] == Y[j])
				c[i][j] = c[i - 1][j - 1] + 1;
			else if (c[i - 1][j] >= c[i][j - 1])
				c[i][j] = c[i - 1][j];
			else
				c[i][j] = c[i][j - 1];
		}
	}
}

char *lcs(const char X[], const char Y[], char str[])
{
	int Xlen = strlen(X);
	int Ylen = strlen(Y);
	int c[MAX_LEN][MAX_LEN];

	get_lcslength(X, Xlen, Y, Ylen, c);
	
	int i = Xlen - 1;
	int j = Ylen - 1;

	str[c[i][j]] = '\0';//此時的c[i][j]記錄著最長公共子序列的長度,讓str以'\0'結尾

	while (i > 0 && j > 0)
	{
		if (c[i][j] == c[i][j - 1])
			j = j - 1;
		else if (c[i][j] == c[i - 1][j])
			i = i - 1;
		else
		{
			str[c[i][j] - 1] = X[i];
			i = i - 1;
			j = j - 1;
		}
	}
	/*判斷第0行或者第0列的值,如果為1則表明X[i] == Y[j]*/
	if (c[i][j] == 1)
	{
		if (i == 0)
		{
			str[0] = X[0];
		}
		if (j == 0)
		{
			str[0] = Y[0];
		}
	}
		
	return str;
}

程式的執行結果如下所示: