算法導論讀書筆記(18)

http://www.cnblogs.com/sungoshawk/p/3779740.html

算法導論讀書筆記(18)

最長公共子序列

某給定序列的子序列,就是將給定序列中零個或多個元素去掉後得到的結果。其形式化定義如下:給定一個序列 X = < x1 , x2 , … , xm >,另一個序列 Z = < z1 , z2 , … , zk >,如果 Z 滿足如下條件則稱 Z 爲 X 的 子序列 (subsequence),即存在一個嚴格遞增的 X 的下標序列 < i1 , i2 , … , ik >,對所有 j = 1,2,…, k ,滿足 xij = zj 。給定兩個序列 X 和 Y ,如果 Z 既是 X 的子序列,也是 Y 的子序列,則稱它是 X 和 Y 的 公共子序列 。

最長公共子序列問題 (longest-common-subsequence problem)就是給定兩個序列 X = < x1 , x2 , … , xm >和 Y = < y1 , y2 , … , yn >,求 X 和 Y 長度最長的公共子序列。簡稱LCS問題。下面將展示如何用動態規劃方法高效求解LCS問題。

步驟1:描述最長公共子序列的特徵

LCS問題符合最優子結構的性質。可以看到,子問題的自然分類對應兩個輸入序列的“前綴”對。前綴的嚴格定義如下:給定一個序列 X = < x1 , x2 , … , xm>,對 i = 0,1,…, m ,定義 X 的第 i 前綴爲 Xi = < x1 , x2 , … , xi >, X0 爲空串。

定理 (LCS的最優子結構)

令 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。

上面的定理說明兩個序列的LCS包含兩個序列的前綴的LCS。因此,LCS問題滿足最優子結構性質。

步驟2:一個遞歸解

由定理可知,在求 X = < x1 , x2 , … , xm >和 Y = < y1 , y2 , … , yn >的一個LCS時,我們需要求解一個或兩個子問題。如果 xm = yn ,我們應該求解 Xm-1和 Yn-1 的一個LCS。然後將 xm = yn 追加到這個LCS的末尾,就得到 X 和 Y 的一個LCS。如果 xm ≠ yn ,我們必須求解兩個子問題:求 Xm-1 和 Y 的一個LCS與 X 和 Yn-1 的一個LCS。兩個LCS中長的那個即爲 X 和 Y 的一個LCS。

可以很容易看出LCS中的重疊子問題。爲了求 X 和 Y 的一個LCS,我們可能需要求 X 和 Yn-1 的一個LCS以及 Xm-1 和 Y 的一個LCS。這幾個子問題都包含求解 Xm-1 和 Yn-1 的LCS的子子問題。

設計LCS問題的遞歸算法還要建立最優解的遞歸式。令 c [ i , j ]表示 Xi 和 Yj 的LCS的長度。如果 i = 0或 j = 0,即一個序列長度爲0,那麼LCS的長度爲0。根據LCS問題的最優子結構性質,可知:


步驟3:計算LCS的長度

過程 LCS-LENGTH 接受兩個序列 X = < x1 , x2 , … , xm >和 Y = < y1 , y2 , … , yn >爲輸入。它將 c [ i , j ]的值保存在表 c [ 0 .. m , 0 .. n ],並按 行主次序(row-major order)計算表項(即首先由左至右計算 c 的第一行,然後第二行,依此類推)。過程還維護一個表 b [ 1 .. m , 1 .. n ]幫助構造最優解。 b [ i ,j ]指向的表項對應計算 c [ i , j ]時所選擇的子問題的最優解。過程返回表 b 和表 c , c [ m , n ]保存了 X 和 Y 的LCS的長度。

LCS-LENGTH(X, Y)
1  m = X.length
2  n = Y.length
3  let b[1..m, 1..n] and c[0..m, 0..n] be new tables
4  for i = 1 to n
5      c[i, 0] = 0
6  for j = 0 to n
7      c[0, j] = 0
8  for i = 1 to m
9      for j = 1 to n
10         if x_i == y_j
11             c[i, j] = c[i - 1, j - 1] + 1
12             b[i, j] = "↖"
13         elseif c[i - 1, j] >= c[i, j - 1]
14             c[i, j] = c[i - 1, j]
15             b[i, j] = "↑"
16         else
17             c[i, j] = c[i, j - 1]
18             b[i, j] = "←"
19 return c and b

下圖顯示了 LCS-LENGTH 對輸入序列 X = < A , B , C , B , D , A , B >和 Y = < B , D , C , A , B , A >生成的結果。過程的運行時間爲 Θ ( mn ),因爲每個表項的計算時間爲 Θ ( 1 )。

步驟4:構造LCS

現在可以用 LCS-LENGTH 返回的表 b 快速構造 X = < x1 , x2 , … , xm >和 Y = < y1 , y2 , … , yn >的LCS。

PRINT-LCS(b, X, i, j)
1 if i == 0 or j == 0
2     return
3 if b[i, j] == "↖"
4     PRINT-LCS(b, X, i - 1, j - 1)
5     print x_i
6 elseif b[i, j] == "↑"
7     PRINT-LCS(b, X, i - 1, j)
8 else
9     PRINT-LCS(b, X, i, j - 1)

LCS問題的簡單Java實現

參考自http://www.cs.cityu.edu.hk/~lwang/cs5302/LCS.java

private static int[][] lcsLength(String x, String y) {
    int m = x.length();
    int n = y.length();
    int[][] b = new int[m + 1][n + 1];
    int[][] c = new int[m + 1][n + 1];
    for (int i = 0; i < n; i++)
        c[i][0] = 0;
    for (int j = 0; j < m; j++)
        c[0][j] = 0;
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (x.charAt(i - 1) == y.charAt(j - 1)) {
                c[i][j] = c[i - 1][j - 1] + 1;
                b[i][j] = DIAGONAL;
            } else if (c[i - 1][j] >= c[i][j - 1]) {
                c[i][j] = c[i - 1][j];
                b[i][j] = UP;
            } else {
                c[i][j] = c[i][j - 1];
                b[i][j] = FORWARD;
            }
        }
    }
    return b;
}

public static String getLCS(String x, String y) {
    int[][] b = lcsLength(x, y);
    String lcs = "";
    int i = x.length();
    int j = y.length();
    while (i != 0 && j != 0) {
        if (b[i][j] == DIAGONAL) {
            lcs = x.charAt(i - 1) + lcs;
            i = i - 1;
            j = j - 1;
        }
        if (b[i][j] == UP) {
            i = i - 1;
        }
        if (b[i][j] == FORWARD) {
            j = j - 1;
        }
    }
    return lcs;
}

private static final int DIAGONAL = 1;
private static final int UP = 2;
private static final int FORWARD = 3;

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