Suffix Array란, 문자열 S가 있을 때 그 접미사들을 정렬해 놓은 배열이다. 예를 들어, 문자열 S=banana의 접미사는 아래와 같이 총 6개가 있다.
Suffix | i |
---|---|
banana | 1 |
anana | 2 |
nana | 3 |
ana | 4 |
na | 5 |
a | 6 |
이를 Suffix 순으로 정렬하면 아래와 같다.
Suffix | i |
---|---|
a | 6 |
ana | 4 |
anana | 2 |
banana | 1 |
na | 5 |
nana | 3 |
정렬된 i의 배열 [6,4,2,1,5,3]을 S의 Suffix Array라고 한다.
(출처 :: https://www.acmicpc.net/problem/9248)
Suffix Array는 위와 같이 정의되고 Suffix Array를 구하는 방법은 다음과 같다.
단순히 접미사를 구하고, 정렬을 하는 방법은 이라는 시간 복잡도가 걸린다.
(단어 비교 시 정렬 시 )
따라서 값이 커진다면 Suffix Array를 해결하기에는 매우 불리한 알고리즘이다.
이를 해결하기 위해 현재 O(n)에 동작하는 알고리즘이 존재하긴 하지만,
이 알고리즘은 너무 복잡하기에 PS(Problem Solving)에서는 이용하지 않는다.
그래서 이번에는 에 동작하는 맨버-마이어스 알고리즘(Manber-Myers Algorithm)에 대해 알아보려 한다.
맨버-마이어스 알고리즘의 기본 동작 원리는 다음과 같다.
첫 1, 2, 4, 8 ... 2^n 글자를 기준으로 정렬하는 방식이다.
이렇게 logn번의 정렬을 하고 나면 원하는 접미사 배열을 얻을 수 있게 된다.
예를 들어 위의 banana에 대해 알아보자.
정렬되지 않은 Suffix가 이렇게 있다.
이때 맨버-마이어스 알고리즘에 의해 정렬을 하게 된다면 우선 첫 한 글자를 기준으로 정렬을 한다.
이 후, 첫 두 글자를 기준으로 정렬을 한다.
이 후, 첫 네 글자를 기준으로 정렬을 한다.
이 후로는 문자열의 길이가 banana = 6이므로 첫 여덟 글자 기준은 정렬할 필요가 없다.
시간 복잡도
이렇게 정렬을 하게 되면 번의 정렬을 하고 매번 정렬을 하는데 이므로
결국의 시간이 걸리게 된다.
소스 코드를 통해 맨버-마이어스 알고리즘을 다시 한번 파악해보자.
소스 코드
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 | #include <cstdio> #include <algorithm> #include <cstring> #define MAX_N 600000 using namespace std; /* str :: 문자열이 들어갈 배열 t :: 단어의 위치를 보는 변수 n :: str의 길이 g :: 그룹 tg :: 팀 그룹 SA :: Suffix Array */ char str[MAX_N]; int t, n, g[MAX_N], tg[MAX_N], SA[MAX_N]; bool cmp(int x, int y) { // 그룹 번호가 같으면 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); 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"); return 0; } | Crocus |
'Applied > 알고리즘' 카테고리의 다른 글
전위, 중위 순회를 알 때 후위 순회 구하는 알고리즘 (0) | 2017.03.03 |
---|---|
LCP 알고리즘(Longest Common Prefix) (4) | 2017.02.25 |
비트 마스크를 이용한 에라토스테네스의 체 (0) | 2017.02.13 |
LIS(Longest Increasing Subsequence) 알고리즘 (8) | 2017.01.31 |
최대 공약수, 최소 공배수 한줄 코딩 (0) | 2017.01.25 |