카테고리 없음

[Algorithm] LCS(Longest Common Subsequence, 최장 공통 부분 수열)

plzfday 2017. 11. 15. 18:06

LCS알고리즘

오늘은 LCS 알고리즘에 대해서 알아보도록 하겠다. LCS(Longest Common Subsequence, 최장 공통 부분 수열)이다.

substring과 subsequence의 차이점은 예를 들어서 'carrot'이라는 단어를 자를때, 

Substring : 연속해서 이어져 있는 것이다. (예시) carr 또는 rot 등등.

Subsequence : 순서는 바뀌지 않지만, 띄어져서도 이어지는 녀석을 칭한다. (예시) cro 또는 arrt 등등. 


LCS는 두 수열이 주어졌을 때, 공통으로 들어있는 부분 수열 중 가장 긴 것을 찾는 것이다.

orange와 neopong 이 주어졌을 때 공통으로 있는 부분 수열은 'ong'가 될 것이다..


접근

수열 A인 abcdef와 수열 B인 bce가 있다. A의 맨 마지막 단어를 지워보자. abcde가 될 것 이다. 그렇지만 A와 B의 LCS의 길이는 계속 같다. 한 번 더 A의 맨 마지막을 지워보자. abcd가 될 것이고 LCS의 길이는 하나가 줄게 된다. 

이렇듯이 수열의 맨 뒤를 제거하게 되면 ①LCS의 길이가 줄거나 ②그대로인 경우로 나눠진다.

참고로, LCS 알고리즘에서는 수열 A와 B 둘 중 하나를 기준으로 잡고 비교해야 한다.

경우

1) 두 수열의 마지막이 모두 LCS에 속할 경우

A[i] == B[j](A와 B의 문자가 같다)일때, 
A와 B의 LCS는 A에서 마지막 한 개 뺀 것과 B에서 마지막 한 개 뺀 것 간의 LCS + 1와 같다.

즉, 일반화를 하면 LCS[i][j] = LCS[i - 1][j - 1] + 1이다. 

예시) 'axpe'와 'aowpe'의 LCS는 'axp'와 'aowp'의 LCS + 1과 같다.

2) A 수열의 마지막이 LCS에 속하지 않을 경우

A[i] != B[j](A와 B의 문자가 다르다)일때,
A와 B의 LCS는 A에서 마지막 한 개 뺀 것과 B의 LCS와 같다.

즉, 일반화를 하면 LCS[i][j] = LCS[i - 1][j]이다.

예시) 'abdop'와 'ebd'의 LCS는 'abdo'와 'ebd'의 LCS와 같다.

3) B 수열의 마지막이 LCS에 속하지 않을 경우

A[i] != B[j](A와 B의 문자가 다르다)일때,
A와 B의 LCS는 B에서 마지막 한 개 뺀 것과 A의 LCS와 같다.

즉, 일반화를 하면 LCS[i][j] = LCS[i][j - 1]이다.

예시) 'abc'와 'axbe'의 LCS는 'abc'와 'axb'의 LCS와 같다.


A[i] != B[j]인 경우는 경우 2와 3이 충족한다. 그렇지만 우리는 최장 공통 부분 수열을 구해야 하기 때문에 

둘 중에 큰 값이 나온 것을 고르면 된다. -> if(A[i] != B[j]), LCS[i][j] = max(LCS[i - 1][j], LCS[i][j - 1]


행렬을 이용한 보여주기!

되게 신기하지만 이 친구는 다이나믹 프로그래밍을 통해 문제를 해결할 수 있다. 우선 행렬을 통해 어떻게 구현되는지 알아보자.
백준 문제에서도 나온 문자열 두 개를 통해 알아보자. 'ACAYKP'와 'CAPCAK'가 있다.

먼저 아래와 같이 세팅해준다.


LCS[][0] 와 LCS[0][]을 모두 0으로 초기화 해주면 된다. 저 ø은 그냥 인덱스를 1부터 시작하기 위해서라고 생각하면 될 것 같다..

우리가 아까전에 세 가지 경우에서 봤던 것 처럼 똑같이 해주면 된다.



C부터 시작해서 상단에 있는 기준이 되는 단어들과 매칭을 한다. C와 A는 다르니깐 위쪽 또는 왼쪽 중 큰 수를 가져간다. 그래서 LCS[A][C]는 0이 된다. 



이제 C와 C를 비교한다. 둘이 같으므로 LCS[i - 1][j - 1] + 1을 해주는데 행렬상으로는 좌측 상단이므로 0 + 1을 해주면 된다.

이렇게 계속 반복을 해주면


이렇게 행렬이 채워진다. 


고로 'ACAYKP'와 'CAPCAK'의 LCS의 길이는 4가 되는 것이다.(항상 LCS의 최대 길이는 행렬 맨 끝에 위치한다)


아래 코드를 참고하길 바란다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <cstdio>
#include <cstring>
#include <algorithm>
char str1[1010];
char str2[1010];
int LCS[1010][1010];
int main()
{
    scanf("%s"&str1);
    scanf("%s"&str2);
    int s1 = strlen(str1);
    int s2 = strlen(str2);
 
    for (int i = 1; i <= s1; i++) {
        for (int j = 1; j <= s2; j++) {
            if (str1[i - 1== str2[j - 1])
                LCS[i][j] = LCS[i - 1][j - 1+ 1;
            else
                LCS[i][j] = std::max(LCS[i - 1][j], LCS[i][j - 1]);
        }
    }
    printf("%d\n", LCS[s1][s2]);
    
    return 0;
}

cs