1. 程式人生 > >最長公共子序列(動態規劃)

最長公共子序列(動態規劃)

目錄

2  問題描述

1 子序列概念

一個給定序列的子序列是在序列中刪除若干個元素後得到的序列。在這裡,首先說明子序列的概念(切記子序列非子集的概念),例如X={B,C,D,B}是序列Y={A,B,C,B,D,A,B}的一個子序列,則序列X在序列Y中相對應的下標為{2,3,5,7},序列XY的下標都是從1開始。

2  問題描述

如果給定兩個序列X和序列Y,當另外一個序列Z即是X的子序列又是Y的子序列,那麼稱序列Z是序列X和序列Y的公共子序列。

假如X=A,B,C,B,D,A,B,Y=B,D,C,A,B,A這兩個序列,則序列Z=B,C,AXY的一個公共子序列,但他不是一個最長的公共子序列。而序列{B,C,B,A}才是一個最長的公共子序列,他的長度為4,應為序列XY沒有長度大於四的公共子序列。

瞭解了最長公共子序列,那麼最長公共子序列問題就是在給定的兩個序列X

Y中,找出他們最長的公共子序列這樣一個問題。

2.1 問題分析:

設序列X={x_{1},x_{2}......x_{m}}和序列Y={y_{1},y_{2}.....y_{n}}的最長公共子序列是Z={z_{1},z_{2}......z_{k}}

(1)如果x_{m}=y_{n},則z_{k}=x_{m}=y_{n},並且Z_{k-1}X_{m-1}Y_{n-1}的最長公共子序列。

(2)如果x_{m}\neq y_{n}並且z_{k}\neq x_{m},那麼ZX_{m-1}Y的一個最長公共子序列。

(3)如果x_{m}\neq y_{n}並且z_{k}\neq y_{n},那麼ZXY_{n-1}的一個最長公共子序列。

在這裡,其中X_{m-1}={x_{1},x_{2}......x_{m-1}};Y_{n-1}={y_{1},y_{2}.....y_{n-1}};Z_{k-1}={z_{1},z_{2}......z_{k-1}}.

2.2 動態規劃求解公式

根據以上問題分析,我們要找出序列XY的最長公共子序列,可以按照以下遞迴進行求解:當x_{m}=y_{n}時,(即兩個序列最後一個字元相同),那我們的變為求解X_{m-1}Y_{n-1}得最長公共子序列在加上x_{m}即可,就可以得到XY的一個最長公共子序列。當x_{m}\neq y_{n}時,必須求解兩個問題,即找出X_{m-1}YXY_{n-1}兩者當中較長的一個公共子序列 ,即得到最終結果,所以我們建立了以上的遞迴公式求解。我們用c[i][j]

記錄序列XY的最長公共序列的長度,其中當i=0或者j=0時c[i][j]=0表示此時最長公共序列為。b[i][j]記錄c[i][j]的值是由哪一個子問題求解得到的。XY的最長公共子序列記錄在c[i][j]

下面我們就可以寫出求最長公共子序列的遞推公式:

c[i][j]=\left\{\begin{matrix} 0 & & & & & & & i=0||j=0& & \\ c[i-1][j-1]+1& & & & & & & i,j>0;x_{i}=y_{j}& & \\ max(c[i][j-1],c[i-1][j])& & & & & & & i,j>0;x_{i}\neq y_{j} & & \end{matrix}\right.

2.3 演算法展示

#include "pch.h"
#include <iostream>
using namespace std;
int LengthString(char *x, char *y, int m, int n, int **c, int **b)
{
	//x和y是兩個字串,m和n分別是其長度,二維陣列儲存最長公共序列長度,b陣列記錄在哪個子問題下得到的解
	for (int i = 0;i <= m;i++)
		c[i][0] = 0;
	for (int j = 0;j <= n;j++)
		c[0][j] = 0;
	for (int i = 1;i <= m;i++)
	{
		for (int j = 1;j <= n;j++)
		{
			if (x[i] == y[j])
			{
				c[i][j] = c[i - 1][j - 1] + 1;
				b[i][j] = 1;
			}
			else
				if (c[i][j - 1] > c[i - 1][j])
				{
					c[i][j] = c[i][j - 1];
					b[i][j] = 2;
				}
				else
				{
					c[i][j] = c[i - 1][j];
					b[i][j] = 3;
				}
		}
	}
	return c[m][n];
}
void LCS(int i, int j, char *x, int **b)
{
	if (i == 0 || j == 0)
		return;
	if (b[i][j] == 1)
	{
		LCS(i - 1, j - 1, x, b);
		cout << x[i-1] << " ";
	}
	else
		if (b[i][j] == 2)LCS(i - 1, j, x, b);
		else
			LCS(i, j - 1, x, b);
}
int main()
{
	int charNum_1;
	int charNum_2;
	cout << "請輸入兩個字串的長度:" << endl;
	cin >> charNum_1 >> charNum_2;
	char *x = new char[charNum_1];
	char *y=new char[charNum_2];
	int **b = new int*[charNum_1];
	int **c = new int*[charNum_1];
	for (int i = 0;i <= 7;i++)//申請空間
	{
		b[i] = new int[charNum_2];
		c[i] = new int[charNum_2];
	}
	for (int i = 0;i <= charNum_1;i++)//初始化
	{
		for (int j = 0;j <= charNum_2;j++)
		{
			b[i][j] = 0;
			c[i][j] = 0;
		}
		cout << endl;
	}
	cout << "請輸入字串 1:";
	for (int i = 0;i <charNum_1;i++)
	{
		cin >> x[i];
	}
	cout << "請輸入字串 2:";
	for (int i = 0;i <charNum_2;i++)
	{
		cin >> y[i];
	}
	int len=LengthString(x, y, charNum_1, charNum_2, c, b);
	
	
	cout << "最長公共子串長度是:" << len << endl;
	cout << "最長公共子串是:";
	LCS(charNum_1, charNum_2, x, b);
	for (int i = 0; i <= charNum_1; i++)    //釋放動態申請的二維陣列空間
		delete[] c[i];
	delete[] c;
	
}

 由於每個陣列單元計算耗費O(1)的時間,所以演算法LengthString的時間複雜度為O(n*m)

2.4 求解最長序列輸出

 由以上我們求出的只是最長公共子序列的長度,最長子序列是什麼還沒求出。所以我們根據b陣列繼續構造最長子序列,首先從b[i][j]開始,在陣列中依次搜尋,當b[i][j]\doteq 1時,表示XY的最長公共子序列是由X_{m-1}Y_{n-1}加上x_{m}得到的,當b[i][j]\doteq 2表示XY的最長公共子序列和X_{i-1}Y_{j}的最長公共子序列相同,當b[i][j]\doteq 3時,表示XY的最長公共子序列和X_{i}Y_{j-1}的子序列相同,由此我們得到遞迴求解公共子序列的演算法。

void LCS(int i, int j, char *x, int **b)
{
	//i和j分別表示序列x和y的長度
	if (i == 0 || j == 0)
		return;
	if (b[i][j] == 1)
	{
		LCS(i - 1, j - 1, x, b);
		cout << x[i-1] << " ";
	}
	else
		if (b[i][j] == 2)LCS(i - 1, j, x, b);
		else
			LCS(i, j - 1, x, b);
}

LCS演算法中,由於每次遞迴使得i和j的值每次都減小1,所以演算法的時間複雜度為O(m+n).