LCS 알고리즘은 주로 최장 공통 부분수열(Longest Common Subsequence)을 말하지만, 최장 공통 문자열(Longest Common Substring)을 말하기도 한다.
예시의 이미지를 통해 살펴볼 수 있는 최장 공통 부분수열과 최장 공통 문자열의 가장 큰 차이는 연속성의 차이이다.
최장 공통 부분수열: 말그대로 부분수열이기에 문자 사이를 건너뛰어 공통되는 가장 긴 부분
최장 공통 문자열: 부분문자열이 아니기 때문에 한번에 이어져있는 공통되는 가장 긴 부분
최장 공통 부분수열 알고리즘을 구하기전에 최장 공통 문자열 알고리즘을 먼저 알아보자.
최장 공통 문자열 알고리즘이 좀 더 쉽고, 최장 공통 부분수열 알고리즘에 사용이 되기 때문이다.
if i == 0 or j == 0: # 마진 설정
LCS[i][j] = 0
elif string_A[i] == string_B[j]:
LCS[i][j] = LCS[i - 1][j - 1] + 1
else:
LCS[i][j] = 0
LCS라는 2차원 배열을 활용하여 두 문자열을 비교한다.
LCS[i][j]
에 0
을 표시합니다.LCS[i - 1][j - 1]
에 1
을 표시합니다.const A = "ABCDEF";
const B = "GBCDFE";
const LCS = Array.from(new Array(A.length + 1), () =>
new Array(B.length + 1).fill(0)
);
let maxLCSLength = 0;
let maxLCSIndex = 0;
for (let i = 1; i <= A.length; i++) {
for (let j = 1; j <= B.length; j++) {
if (A[i - 1] === B[j - 1]) {
LCS[i][j] = LCS[i - 1][j - 1] + 1;
}
if (LCS[i][j] > maxLCSLength) {
maxLCSLength = LCS[i][j];
maxLCSIndex = i;
}
}
}
console.log(LCS); // LCS 2차원 배열
console.log(maxLCSLength); // 최장 공통 문자열 길이
console.log(A.slice(maxLCSIndex - maxLCSLength, maxLCSIndex)); // 최장 공통 문자열
이번에는 최장 공통 부분 수열에 대해 알아보자.
if i == 0 or j == 0: # 마진 설정
LCS[i][j] = 0
elif string_A[i] == string_B[j]:
LCS[i][j] = LCS[i - 1][j - 1] + 1
else:
LCS[i][j] = max(LCS[i - 1][j], LCS[i][j - 1])
위와 마찬가지로 LCS라는 2차원배열을 활용하여 비교한다.
LCS[i-1][j]
와 LCS[i][j-1]
중에 큰값을 표시합니다.LCS[i - 1][j - 1]
의 값에 +1
의 값을 표시합니다.최장 공통 문자열을 구하는 과정과 다른부분은 비교하는 두 문자가 다를 때 입니다. 비교하는 두 문자가 같을 때는 같은 과정을 보여줍니다. 왜 어떤 부분은 다른 로직을, 어떤부분은 같은 로직을 사용하는지 상세히 살펴보자.
부분수열은 연속된 값이 아닙니다. 때문에 현재의 문자를 비교하는 과정 이전의 최대 공통 부분수열은 계속해서 유지됩니다. 현재의 문자를 비교하는 과정 이전의 과정이 바로 LCS[i - 1][j]
와 LCS[i][j - 1]
가 됩니다.
문자열 ABC와 GBC를 비교하는 과정에서 LCS 배열은 LCS[i - 1][j]
와 LCS[i][j - 1]
의 비교를 통해 언제나 본인까지의 최대 공통 부분수열 값을 가지고 있습니다.
문자열 AB와 GB를 비교할때와 문자열 ABC와 GBC를 비교할 때 달라진 점은 두 문자열 모두에 C가 추가된 점입니다. 때문에 기존의 최대 공통 부분수열인 B에 C를 더한 BC가 최대 공통 부분수열이 되는 것입니다.
위에서 LCS 구현과정을 통해 LCS 배열을 만들며 LCS의 길이(LCS[i][j]
)를 구할 수 있었다. 이제 만든 LCS 배열을 이용해 최장 공통 부분수열의 값을 찾아보자. 경우에 따라 여러가지 답이 나올 수 있다.
LCS[i - 1][j]
와 LCS[i][j - 1]
중 현재 값과 같은 값을 찾습니다.LCS[i -1][j - 1]
로 이동합니다.const A = "ABCDEF";
const B = "GBCDFE";
const LCS = Array.from(new Array(A.length + 1), () =>
new Array(B.length + 1).fill(0)
);
// 최장 공통 부분 수열 구하기
for (let i = 1; i <= A.length; i++) {
for (let j = 1; j <= B.length; j++) {
if (A[i - 1] === B[j - 1]) {
LCS[i][j] = LCS[i - 1][j - 1] + 1;
} else {
LCS[i][j] = Math.max(LCS[i - 1][j], LCS[i][j - 1]);
}
}
}
// 최장 공통 부분 수열에 해당하는 문자열 찾기
let i = A.length;
let j = B.length;
let result = [];
while (i > 0 && j > 0) {
if (LCS[i][j] === LCS[i - 1][j]) {
i--;
} else if (LCS[i][j] === LCS[i][j - 1]) {
j--;
} else if (LCS[i][j] === LCS[i - 1][j - 1] + 1) {
result.push(A[i - 1]);
i--;
j--;
}
}
console.log(LCS); // LCS 2차원 배열
console.log(LCS[A.length][B.length]); // LCS 길이
console.log(result.reverse().join("")); // LCS 문자열