longest-common-subsequence
การทำความเข้าใจเกี่ยวกับ Longest Common Subsequence (LCS)
Longest Common Subsequence (LCS) เป็นปัญหาที่ใช้ในการค้นหาลำดับที่มีความยาวยาวที่สุดที่สามารถพบได้ในสองลำดับที่ให้มา (เช่น สตริงหรืออาร์เรย์) โดยที่ลำดับย่อยนั้นต้องไม่จำเป็นต้องอยู่ติดกันในลำดับต้นฉบับ แต่ต้องอยู่ในลำดับเดียวกัน
ตัวอย่าง:
ให้สองสตริง:
X = "AGGTAB"Y = "GXTXAYB"
ลำดับที่ยาวที่สุดที่ปรากฏในทั้งสองสตริงคือ GTAB ซึ่งมีความยาว 4
1. แนวคิดพื้นฐานของ LCS
ในการหาค่าของ Longest Common Subsequence (LCS) ระหว่างสองลำดับ (เช่น สตริง) จะมีหลักการทำงานดังนี้:
- หากตัวอักษรที่ตำแหน่ง i ในสตริงแรกตรงกับตัวอักษรที่ตำแหน่ง j ในสตริงที่สอง (i.e.,
X[i] == Y[j]), เราสามารถรวมตัวอักษรนี้เข้าไปใน LCS - หากตัวอักษรที่ตำแหน่ง i และ j ไม่ตรงกัน (i.e.,
X[i] != Y[j]), LCS จะเป็นค่าที่ได้จากการเลือก LCS ที่ยาวที่สุดจากสองทางเลือก:- LCS ของสตริง
X[0..i-1]และY[0..j] - LCS ของสตริง
X[0..i]และY[0..j-1]
- LCS ของสตริง
กฎการทำงานหลัก:
- หาก
X[i] == Y[j]: [ LCS(i, j) = LCS(i-1, j-1) + 1 ] - หาก
X[i] != Y[j]: [ LCS(i, j) = \max(LCS(i-1, j), LCS(i, j-1)) ]
2. วิธีการใช้ Dynamic Programming ในการหาค่า LCS
การคำนวณ LCS สามารถทำได้อย่างมีประสิทธิภาพโดยใช้ Dynamic Programming เพื่อหลีกเลี่ยงการคำนวณซ้ำในขั้นตอนต่าง ๆ โดยการใช้ ตาราง DP เพื่อเก็บค่าผลลัพธ์ที่คำนวณแล้วของแต่ละคู่ (i, j) ของสตริงทั้งสอง
ขั้นตอนการทำงาน:
- สร้างตาราง DP ที่มีขนาด
(m+1) x (n+1)โดยที่mคือความยาวของสตริงXและnคือความยาวของสตริงY - กรอกค่าตารางโดยใช้กฎที่กล่าวถึงข้างต้น:
- เมื่อ
X[i] == Y[j], ค่าในตารางที่ตำแหน่ง(i, j)จะเป็นLCS(i-1, j-1) + 1 - เมื่อ
X[i] != Y[j], ค่าในตารางที่ตำแหน่ง(i, j)จะเป็นmax(LCS(i-1, j), LCS(i, j-1))
- เมื่อ
- ค่าในตารางที่มุมขวาล่าง
(m, n)จะเป็นความยาวของ Longest Common Subsequence
3. ตัวอย่างการคำนวณ LCS
ให้สองสตริง:
X = "AGGTAB"Y = "GXTXAYB"
ขั้นตอนการคำนวณ
-
สร้างตาราง DP ที่ขนาด
(7 x 8)เพราะความยาวของXคือ 6 และYคือ 7:ตาราง DP เริ่มต้นจะเป็น:
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 -
เติมค่าในตาราง DP ตามกฎที่กล่าวถึง:
-
สำหรับ
i = 1และj = 1ถ้าX[0] == Y[0](i.e.,A == G) จะไม่ตรงกัน ดังนั้น:DP[1][1] = max(DP[0][1], DP[1][0]) = 0 -
เมื่อ
X[1] == Y[1](i.e.,G == G), เราจะเพิ่ม 1:DP[2][2] = DP[1][1] + 1 = 1
-
-
ทำซ้ำขั้นตอนนี้จนกระทั่งตารางเต็ม
ผลลัพธ์สุดท้ายในตาราง DP คือ:
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 1
0 1 1 1 1 1 1 1
0 1 1 2 2 2 2 2
0 1 1 2 2 2 3 3
0 1 1 2 3 3 3 3
- ค่าในตำแหน่ง
(6, 7)คือLCS(6, 7) = 4ซึ่งเป็นความยาวของ Longest Common Subsequence ที่ยาวที่สุดระหว่างXและY(ในกรณีนี้คือ "GTAB")
4. วิธีการคืนค่า LCS
หลังจากที่เราได้ค่า LCS ในตาราง DP แล้ว ขั้นตอนถัดไปคือการคืนค่าลำดับของ LCS โดยการย้อนกลับจากมุมขวาล่างของตาราง
ขั้นตอนการคืนค่า LCS:
- เริ่มจากตำแหน่ง
(m, n)(มุมขวาล่างของตาราง) - หาก
X[i] == Y[j], ให้เพิ่มตัวอักษรX[i]ไปยังลำดับ LCS และลดiและjลง 1 - หาก
X[i] != Y[j], เลือกทางที่มีค่ามากกว่า (ระหว่างDP[i-1][j]หรือDP[i][j-1])
ตัวอย่างโค้ดในการคืนค่า LCS:
def get_LCS(X, Y):
m, n = len(X), len(Y)
# สร้างตาราง DP
dp = [[0] * (n + 1) for _ in range(m + 1)]
# เติมค่าตาราง DP
for i in range(1, m + 1):
for j in range(1, n + 1):
if X[i - 1] == Y[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
# คืนค่า LCS
lcs = []
i, j = m, n
while i > 0 and j > 0:
if X[i - 1] == Y[j - 1]:
lcs.append(X[i - 1])
i -= 1
j -= 1
elif dp[i - 1][j] >= dp[i][j - 1]:
i -= 1
else:
j -= 1
# กลับลำดับของ LCS
return ''.join(reversed(lcs))
# ตัวอย่างการใช้งาน
X = "AGGTAB"
Y = "GXTXAYB"
print(get_LCS(X, Y)) # ผลลัพธ์: "GTAB"
5. สรุป
- Longest Common Subsequence (LCS) คือการหาลำดับที่ยาวที่สุดที่มีอยู่ในสองลำดับที่ไม่จำเป็นต้องอยู่ติดกัน
- การคำนวณ LCS สามารถทำได้อย่างมีประสิทธิภาพโดยใช้ Dynamic Programming ด้วยการสร้างตาราง DP เพื่อเก็บผลลัพธ์ของการคำนวณ LCS ของแต่ละคู่
(i, j) - Time Complexity ของการคำนวณ LCS คือ O(m * n) ซึ่ง m และ n คือความยาวของสตริงทั้งสอง
- Space Complexity ของการใช้ DP คือ O(m * n) ในการเก็บตาราง DP