最長公共子序列(動態規劃)
目錄
1 子序列概念
一個給定序列的子序列是在序列中刪除若干個元素後得到的序列。在這裡,首先說明子序列的概念(切記子序列非子集的概念),例如是序列的一個子序列,則序列在序列中相對應的下標為,序列和的下標都是從1開始。
2 問題描述
如果給定兩個序列和序列,當另外一個序列即是的子序列又是的子序列,那麼稱序列是序列和序列的公共子序列。
假如,這兩個序列,則序列是和的一個公共子序列,但他不是一個最長的公共子序列。而序列才是一個最長的公共子序列,他的長度為4,應為序列和沒有長度大於四的公共子序列。
瞭解了最長公共子序列,那麼最長公共子序列問題就是在給定的兩個序列
2.1 問題分析:
設序列和序列的最長公共子序列是。
(1)如果,則,並且是和的最長公共子序列。
(2)如果並且,那麼是和的一個最長公共子序列。
(3)如果並且,那麼是和的一個最長公共子序列。
在這裡,其中;;.
2.2 動態規劃求解公式
根據以上問題分析,我們要找出序列和的最長公共子序列,可以按照以下遞迴進行求解:當時,(即兩個序列最後一個字元相同),那我們的變為求解和得最長公共子序列在加上即可,就可以得到和的一個最長公共子序列。當時,必須求解兩個問題,即找出和,和兩者當中較長的一個公共子序列 ,即得到最終結果,所以我們建立了以上的遞迴公式求解。我們用
下面我們就可以寫出求最長公共子序列的遞推公式:
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; }
由於每個陣列單元計算耗費的時間,所以演算法LengthString的時間複雜度為
2.4 求解最長序列輸出
由以上我們求出的只是最長公共子序列的長度,最長子序列是什麼還沒求出。所以我們根據陣列繼續構造最長子序列,首先從開始,在陣列中依次搜尋,當時,表示和的最長公共子序列是由和加上得到的,當表示和的最長公共子序列和與的最長公共子序列相同,當時,表示和的最長公共子序列和與的子序列相同,由此我們得到遞迴求解公共子序列的演算法。
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);
}
在演算法中,由於每次遞迴使得i和j的值每次都減小1,所以演算法的時間複雜度為.