LCP (Longest Common Prefix) 알고리즘이란?
LCP는 두 접미사의 최대 공통 접두사의 길이를 의미한다.
LCP를 이해하기 위해서는 아래 링크의 내용을 이해하여야 한다.
물론 LCP를 공부하기 위해 찾아온 사람들은 아래 내용을 알고 왔으리라 생각한다.
Suffix Array를 구하는 게시물 :: http://programbasic.tistory.com/613
위의 게시물에서 banana로 구한 테이블을 정리해보자.
i |
SA[i] |
Suffix |
LCP[i] |
0 |
5 |
a |
|
1 |
3 |
ana |
|
2 |
1 |
anana |
|
3 |
0 |
banana |
|
4 |
4 |
na |
|
5 |
2 |
nana |
여기서 두 접미사의 최대 공통 접두사의 길이를 직접 구해보면
i = 0일때 a 이전의 값은 없으므로 x가 된다.
i = 1일때 a와 ana의 최대 공통 접두사 길이(LCP)는 1이다
i = 2일때 ana와 anana의 LCP는 3이다.
i = 3일때 anana와 banana의 LCP는 0이다.
i = 4일때 banana와 na의 LCP는 0이다.
i = 5일때 na와 nana의 LCP는 2이다.
따라서 LCP 테이블은 다음과 같이 완성된다.
i | SA[i] (= Pos[i]) | Suffix | LCP[i] |
0 | 5 | a | x |
1 | 3 | ana | 1 |
2 | 1 | anana | 3 |
3 | 0 | banana | 0 |
4 | 4 | na | 0 |
5 | 2 | nana | 2 |
이렇게 두 단어를 가지고 직접 비교하면 의 시간이 걸린다.
따라서 아래의 사실과 명제를 통해 O(N)에 해결하는 알고리즘을 알아보고자 한다.
우선 아래 나와있는 단어에 대해서 설명을 하자면
lcp(x,y)는 문자열 S에 대해 S[x...]와 S[y...]의 최대 공통 접두사의 길이로 정의한다.
Pos[]는 문자열 S의 접미사 배열로 정의한다.(위의 SA[]와 동일하다.)
Rank[i]는 S[i...]가 사전순으로 몇 번째 접미사인지 들어있다. 결국 Rank[pos[i]] = i이다.
다시말해 Pos[1] = 3인데 Rank[3] = 1인 관계이다. (역함수 관계)
햇갈리지 않게 미리 정리를 해 두자면
S[0...] = "banana"
S[1...] = "anana"
S[2...] = "nana"
S[3...] = "ana"
S[4...] = "na"
S[5...] = "a" 이고,
Rank[0] = 3
Rank[1] = 2
Rank[2] = 5
Rank[3] = 1
Rank[4] = 4
Rank[5] = 0 이다.
접미사 배열에서 이웃한 두 접미사의 lcp가 이웃하지 않고 떨어져 있는 lcp보다 크거나 같다는 뜻이다.
예를 들어 x가 1, y가 2, z가 3라 생각해보자.
lcp(Pos[1], pos[2]) >= lcp(Pos[1], Pos[3])의 의미는 lcp(3, 1) >= lcp(3, 0)
즉, S[3...]와 S[1...]의 최대 공통 접두사는 3이고, S[3...]와 S[0...]의 최대 공통 접두사 0이다.
따라서 3 >= 0을 만족하게 된다.
이웃한 접미사 배열(S[Pos[x-1]...]와 S[Pos[x]...])의 최대 공통 접두사가 1보다 크면(첫 글자가 같으면),
Rank[Pos[x-1]+1] < Rank[Pos[x]+1] 이어야 한다.
즉 x = 2일때 보면 S[Pos[1]...]와 S[Pos[2]...] 즉, S[3...]와 S[1...]는 lcp가 3이니
if( 3 > 1 )을 만족한다.
이때 Rank[Pos[1]+1] < Rank[Pos[2]+1] -> Rank[3+1] < Rank[1+1] -> Rank[4] < Rank[2]
따라서 4 < 5이라는 의미이다.
이웃한 접미사 배열의 최대 공통 접두사가 1보다 크면, lcp(Pos[x-1]+1, Pos[x]+1) = lcp(Pos[x-1], Pos[x])-1이다.
x = 2일때 보면 lcp(Pos[1]+1, Pos[2]+1) = lcp(Pos[1], Pos2]) - 1인데,
lcp(3+1, 1+1) = lcp(3,1) - 1 -> lcp(4, 2) = lcp(3, 1) - 1 -> 2 = 2를 만족한다.
즉, 이웃한 접미사 배열의 첫 글자가 같기 때문에 첫 글자를 뺀 길이 또한 같다는 의미이다.
이러한 사실들을 기반하여 LCP Array 소스 코드를 구현하면 다음과 같다.
아래는 특정 문자열을 입력하였을 때, Suffix가 사전순으로 정렬된 모습, Suffix Array, LCP가 나타난다.
소스 코드
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #include <cstdio> #include <algorithm> #include <cstring> #define MAX_N 600000 using namespace std; char str[MAX_N]; int t, n, g[MAX_N], tg[MAX_N], SA[MAX_N], RANK[MAX_N], LCP[MAX_N]; bool cmp(int x, int y) { // 현재 보고있는 단어가 같으면 t번째 기준으로 정렬 if (g[x] == g[y]) return g[x + t] < g[y + t]; // 현재 보고있는 단어가 다르다면 바로 정렬 return g[x] < g[y]; } void getSA(const char* str) { t = 1; n = (int)strlen(str); //글자의 수 배정 //첫 글자에 의한 그룹을 정해주는 전처리 for (int i = 0; i < n; i++) { SA[i] = i; g[i] = str[i] - 'a'; } //1,2,4,8... 씩 단어의 길이보다 작은 경우를 탐색 while (t <= n) { // g[n] = -1; sort(SA, SA + n, cmp); //그룹에 의한 정렬 tg[SA[0]] = 0; //다음 그룹을 할당하기 위하여 tempgroup의 첫번째 번호 배정 //다음 그룹 배정 for (int i = 1; i < n; i++) { //그룹이 다를 경우 다음 그룹 번호 할당 if (cmp(SA[i - 1], SA[i])) tg[SA[i]] = tg[SA[i - 1]] + 1; //그룹이 같을 경우 같은 그룹 번호 할당 else tg[SA[i]] = tg[SA[i - 1]]; } //새로운 그룹 할당 for (int i = 0; i < n; i++) g[i] = tg[i]; t <<= 1; // t *= 2; } } int main() { scanf("%s", &str); getSA(str); // RANK 배열에는 접미사 배열 순서가 들어간다. for (int i = 0; i < n; i++) RANK[SA[i]] = i; int len = 0; for (int i = 0; i < n; i++) { int k = RANK[i]; if (k) { int j = SA[k - 1]; while (str[j + len] == str[i + len]) len++; LCP[k] = len; if (len) len--; } } printf("\n[Suffix Array]\n"); for (int i = 0; i < n; i++) printf("%s\n", str + SA[i]); printf("\n[Suffix Array Order]\n"); for (int i = 0; i < n; i++) printf("%d ", SA[i] + 1); printf("\n\n[LCP]\n"); for (int i = 0; i < n; i++) { if (!i) printf("x "); else printf("%d ", LCP[i]); } printf("\n\n"); return 0; } // This source code Copyright belongs to Crocus // If you want to see more? click here >> | Crocus |
'Applied > 알고리즘' 카테고리의 다른 글
이진 탐색 트리 특징을 이용한 빠른 삽입, 탐색 (2) | 2017.03.06 |
---|---|
전위, 중위 순회를 알 때 후위 순회 구하는 알고리즘 (0) | 2017.03.03 |
접미사 배열(Suffix Array) (2) | 2017.02.25 |
비트 마스크를 이용한 에라토스테네스의 체 (0) | 2017.02.13 |
LIS(Longest Increasing Subsequence) 알고리즘 (8) | 2017.01.31 |