最長公共子序列問題:給定兩序列,找出最長的子序列(不要求連續)同時出現在兩者中。本文提供標準動態規劃 Ruby 實作,包含長度矩陣建構、方向追蹤矩陣、遞迴回溯列印 LCS 內容。
# encoding: utf-8
# 隨機生成測試序列
chars = ('a'..'e').to_a
x, y = [], []
(rand(5)+5).times { x << chars[rand(chars.size)] }
(rand(5)+5).times { y << chars[rand(chars.size)] }
printf("The random character arrays are: %s and %s\n", x.inspect, y.inspect)
m, n = x.size, y.size
c = Array.new(m+1) { Array.new(n+1, 0) } # 長度矩陣
b = Array.new(m+1) { Array.new(n+1) } # 方向矩陣: 0=斜上, 1=上, 2=左
# DP 填表
(1..m).each do |i|
(1..n).each do |j|
if x[i-1] == y[j-1]
c[i][j] = c[i-1][j-1] + 1
b[i][j] = 0 # 斜上 (匹配)
else
if c[i-1][j] >= c[i][j-1]
c[i][j] = c[i-1][j]
b[i][j] = 1 # 上
else
c[i][j] = c[i][j-1]
b[i][j] = 2 # 左
end
end
end
end
# 遞迴回溯列印 LCS
def print_lcs(x, b, i, j)
return if i == 0 || j == 0
if b[i][j] == 0
print_lcs(x, b, i-1, j-1)
print "#{x[i-1]} "
elsif b[i][j] == 1
print_lcs(x, b, i-1, j)
else
print_lcs(x, b, i, j-1)
end
end
print "The Longest-Common-Subsequence is: "
print_lcs(x, b, m, n)
puts
The random character arrays are: ["b", "a", "c", "a", "a", "b", "d"] and ["a", "c", "a", "c", "a", "a", "b"]
The Longest-Common-Subsequence is: a c a a b
c[i][j]:X[1..i] 與 Y[1..j] 的 LCS 長度b[i][j]:最優解來源方向
c[i-1][j-1] (斜上)c[i-1][j] (上)c[i][j-1] (左)
if X[i] == Y[j]:
c[i][j] = c[i-1][j-1] + 1
b[i][j] = 0
else:
c[i][j] = max(c[i-1][j], c[i][j-1])
b[i][j] = (c[i-1][j] >= c[i][j-1]) ? 1 : 2
c[m][n] 起始,依 b 方向遞迴(i-1, j-1)(i-1, j)(i, j-1)| 變體 | 修改點 | 應用 |
|---|---|---|
| 最長公共子串 | 要求連續,不匹配時歸零 | 相似度檢測、病毒特徵碼 |
| 編輯距離 | 允許替換操作 | 拼字檢查、DNA 比對 |
| 最長遞增子序列 (LIS) | 單序列,轉化為 LCS | 版本號排序、橋樑建設 |
| 多序列 LCS | 擴充至 k 維 DP | 多基因組比對 |
Array.new(m+1) { Array.new(n+1, 0) }:正確建立二維陣列,避免淺拷貝陷阱。x[i-1]:Ruby 陣列 0-based,DP 邏輯 1-based,統一轉換。print_lcs 遞迴深度 ≤ min(m,n),安全範圍內。(rand(5)+5).times 產生 5-9 長度序列。LCS 是動態規劃經典入門題。Ruby 程式碼清晰展現「建表 → 回溯」雙階段結構。理解 c 與 b 矩陣的語意,是掌握序列比對類演算法的關鍵。生產環境長序列建議使用 Hirschberg 算法 (O(n) 空間) 或 C 擴充庫。