LCS vs. Minimum Edit Distance
LCS vs. Minimum Edit Distance的重點整理。
核心差異總覽 (Core Differences)
| 特性 | 最長公共子序列 (LCS) | Minimum Edit Distance |
|---|---|---|
| 目的 | 找出 \(S_1\) 與 \(S_2\) 的最長共同子序列 | 將 \(S_1\) 轉換為 \(S_2\) |
| 提問 | 我們有多像? | 變成相同要多少工? |
| 目標 | 最大化 共同長度 | 最小化 操作成本 |
| 關鍵操作 | 匹配、跳過 | 匹配、插入、刪除、替換 |
| 公式核心 | max() |
min() |
1. 最長公共子序列 (LCS)
A. 問題定義
給定兩序列 \(X=\langle x_1,\dots,x_m\rangle\) 與 \(Y=\langle y_1,\dots,y_n\rangle\),找 \(X\) 與 \(Y\) 的最長共同子序列。
子序列:刪除若干元素且不改變相對順序所得,如 “ace” 為 “abcde” 之子序列,“aec” 不是。
B. 結構、狀態與轉移
1. 最優解的結構 (Optimal Substructure)
這是推導 DP 公式最關鍵的一步。我們想找出 \(X_m\) ( \(X\) 的所有字元) 和 \(Y_n\) ( \(Y\) 的所有字元) 之間的 LCS,我們只需要比較最後一個字元:\(x_m\) 和 \(y_n\)。
-
Case 1: \(x_m = y_n\) (最後一個字元相同)
-
結論: 這個字元 (\(x_m = y_n\)) 必定是 LCS 的最後一個字元。我們剩下的任務就是去找出 \(X\) 的前面 \(m-1\) 個字元 (\(X_{m-1}\)) 和 \(Y\) 的前面 \(n-1\) 個字元 (\(Y_{n-1}\)) 之間的 LCS。
-
LCS(\(X_m, Y_n\)) = LCS(\(X_{m-1}, Y_{n-1}\)) + \(x_m\)
-
-
Case 2: \(x_m \ne y_n\) (最後一個字元不同)
-
結論: \(x_m\) 和 \(y_n\) 不可能同時是 LCS 的最後一個字元。LCS 必定藏在以下兩種可能之中:
-
LCS(\(X_{m-1}, Y_n\)) (把 \(x_m\) 丟掉)
-
LCS(\(X_m, Y_{n-1}\)) (把 \(y_n\) 丟掉)
-
-
我們取兩者中較長 (Max) 的那個。
-
LCS(\(X_m, Y_n\)) = Max( LCS(\(X_{m-1}, Y_n\)), LCS(\(X_m, Y_{n-1}\)) )
-
2. 狀態與轉移方程
基於上述結構,我們定義狀態並建立遞迴解:
狀態: c[i, j] 為 \(X\) 前 \(i\) 與 \(Y\) 前 \(j\) 的 LCS 長度。
轉移:
\[c[i,j]= \begin{cases} 0 & i=0 \text{ 或 } j=0\\ c[i-1,j-1]+1 & X_i=Y_j \text{ (對應 Case 1)}\\ \max{(c[i-1,j],c[i,j-1])} & X_i\ne Y_j \text{ (對應 Case 2)} \end{cases}\]
邊界: \(c[i,0]=0,c[0,j]=0\)。
C. 演算法 (Bottom-Up)
如果直接用遞迴公式,會因為「重疊子問題」導致效率極低。因此我們用 DP (Bottom-Up),開一個 c[0..m, 0..n] 表格,從 c[0, 0] 開始,一格一格把答案算出來,直到 c[m, n]。
LCS-LENGTH 產生 c(長度)與 b(方向)自上而下、左到右填表。
D. 範例
口訣:一樣斜上,大看上,小看左,記得要加 1。
\(X=\langle A,B,C,B,D,A,B\rangle;(m=7)\)
\(Y=\langle B,D,C,A,B,A\rangle;(n=6)\)
最終長度:c[7,6]=4;其中一個 LCS:“BCBA”。
E. 回溯 (Reconstruction)
c 表格只告訴我們「長度」,b 表格 (存箭頭 ↖, ↑, ←) 才是用來回溯找出「LCS 到底長怎樣」的。PRINT-LCS 自右下 b[m,n] 依箭頭回溯。
-
如果箭頭是
↖:代表 \(X_i\) 是一個匹配,它是 LCS 的一部分。我們把它印出來 (或記錄下來),然後跳到b[i-1, j-1]繼續找。 -
如果箭頭是
↑:代表 \(X_i\) 被跳過了。我們不印東西,跳到b[i-1, j]繼續找。 -
如果箭頭是
←:代表 \(Y_j\) 被跳過了。我們不印東西,跳到b[i, j-1]繼續找。 -
直到
i=0或j=0為止。
F. 複雜度分析 (Complexity)
-
時間複雜度 (Time):
-
LCS-LENGTH演算法的核心是兩個for迴圈 (一個i從 1 到 \(m\),一個j從 1 到 \(n\))。 -
迴圈中的每一步(填
c[i, j]和b[i, j])都只花了 \(O(1)\) 常數時間。 -
總時間複雜度:\(\Theta(mn)\)
-
-
空間複雜度 (Space):
-
我們需要儲存
c表格 (大小 \((m+1) \times (n+1)\)) 和b表格 (大小 \(m \times n\))。 -
總空間複雜度:\(\Theta(mn)\)
-
(優化:如果「不」需要
b表格來回溯,只要求「長度」,空間可以優化到 \(\Theta(\min(m, n))\) )
-
-
回溯時間 (Reconstruction Time):
-
PRINT-LCS函式從(m, n)開始,每一步遞迴i或j(或兩者) 都會減 1。 -
路徑的總長度最多是 \(m + n\)。
-
回溯時間複雜度:\(O(m+n)\)
-
2. Minimum Edit Distance
A. 定義
給定 \(S_1\)(長度 \(m\))與 \(S_2\)(長度 \(n\)),求將 \(S_1\) 轉換為 \(S_2\) 的最小操作數。允許操作成本皆為 \(1\):Insert、Delete、Replace。
B. 狀態與轉移
狀態: dp[i, j] 為將 \(S_1[1..i]\) 轉為 \(S_2[1..j]\) 的最小成本。
轉移:
\[dp[i, j] = \min \begin{cases} dp[i-1, j] + 1 & \text{(刪除 $S_1[i]$)} \\ dp[i, j-1] + 1 & \text{(插入 $S_2[j]$)} \\ dp[i-1, j-1] + \text{cost} & \text{(匹配/替換)} \end{cases}\]
其中 \(\text{cost}=0\) 若 \(S_1[i]=S_2[j]\),否則 \(\text{cost}=1\)。
邊界: \(dp[0,0]=0,;dp[i,0]=i,;dp[0,j]=j\)。
C. 演算法
-
建立
dp[0..m,0..n]。 -
填第一列與第一行。
-
雙迴圈計算三方向成本取最小。
-
回傳
dp[m,n]。
程式碼片段
EDIT-DISTANCE(S1, S2)
m = length(S1)
n = length(S2)
let dp[0..m, 0..n] be a new table
// 1. Initialize Base Cases (Boundaries)
for i = 0 to m
dp[i, 0] = i // Deletion cost
for j = 1 to n
dp[0, j] = j // Insertion cost
// 2. Fill the table
for i = 1 to m
for j = 1 to n
// Calculate substitution/match cost
if S1[i] == S2[j]
sub_cost = 0
else
sub_cost = 1
// 3. Calculate costs from three directions
cost_del = dp[i - 1, j] + 1
cost_ins = dp[i, j - 1] + 1
cost_match_replace = dp[i - 1, j - 1] + sub_cost
// 4. Take the minimum
dp[i, j] = min(cost_del, cost_ins, cost_match_replace)持ㄕ
// 5. Return final cost
return dp[m, n]
D. 範例
\(S_1=\text{"SAT"}\), \(S_2=\text{"CAT"}\)
j 0 ("") 1 ("C") 2 ("A") 3 ("T")
i
0 ("") ( 0, - ) ( 1, ← ) ( 2, ← ) ( 3, ← )
1 ("S") ( 1, ↑ ) ( 1, ↖ ) ( 2, ← ) ( 3, ← )
2 ("A") ( 2, ↑ ) ( 2, ↑ ) ( 1, ↖ ) ( 2, ← )
3 ("T") ( 3, ↑ ) ( 3, ↑ ) ( 2, ↑ ) ( 1, ↖ )
最終答案:dp[3,3]=1。
E. 回溯 (Reconstruction)
dp 表格和箭頭不僅能給出最小成本,還能回溯出具體的操作步驟。
從 \(dp[m, n]\) (右下角) 開始,跟隨箭頭回溯到 \(dp[0, 0]\) (左上角):
-
↖(來自左上):-
如果
cost = 0(即 \(S_1[i] = S_2[j]\)),代表 匹配 (Match)。 -
如果
cost = 1(即 \(S_1[i] \ne S_2[j]\)),代表 替換 (Replace)。
-
-
↑(來自上面):代表 刪除 (Delete) \(S_1[i]\)。 -
←(來自左邊):代表 插入 (Insert) \(S_2[j]\)。
範例 “SAT” \(\to\) “CAT” 回溯:
-
dp[3, 3](1,↖):來自dp[2, 2],‘T’ == ‘T’,匹配 ‘T’。 -
dp[2, 2](1,↖):來自dp[1, 1],‘A’ == ‘A’,匹配 ‘A’。 -
dp[1, 1](1,↖):來自dp[0, 0],‘S’ != ‘C’,替換 ‘S’ \(\to\) ‘C’。 -
dp[0, 0]:到達起點,結束。
總操作: 1 次「替換」。
F. 複雜度分析 (Complexity)
-
時間複雜度 (Time):
-
演算法的核心是兩個
for迴圈 ( \(i\) from 1 to \(m\), \(j\) from 1 to \(n\))。 -
在迴圈中,我們只執行 \(O(1)\) 的常數時間操作 (三次查表、一次
min運算)。 -
總時間複雜度:\(\Theta(mn)\)
-
-
空間複雜度 (Space):
-
我們需要儲存
dp表格,其大小為 \((m+1) \times (n+1)\)。 -
總空間複雜度:\(\Theta(mn)\)
-
(優化:如果只要求「最小成本」而不需回溯「操作路徑」,空間可以優化到 \(\Theta(\min(m, n))\),因為計算第 \(i\) 列時,我們只需要第 \(i-1\) 列的資訊。)
-
-
回溯時間 (Reconstruction Time):
-
回溯是從
(m, n)走回(0, 0)。 -
每一步 \(i\) 或 \(j\) (或兩者) 都會減 1。
-
路徑的總長度最多是 \(m + n\)。
-
回溯時間複雜度:\(O(m+n)\)
-