문자 하나씩 비교하면 앞의 5가지 문자는 일치했지만 마지막 B가 달라서 매칭에 실패하게 되고,
A
B
A
C
A
A
B
A
A
B
A
C
A
B
한 칸 앞으로 가서 위와 같은 비교를 반복할 것이다.
눈으로 봐도 불필요한 연산이 다시 진행되는 것을 확인할 수 있다.
그러면 단순히 틀린 위치부터 탐색하면 되지 않는가?
라고 생각할 수 있지만 그렇게 비교하게 되면
3번째 위치가 A고 패턴의 시작도 A기 때문에 이 경우에 정답이 존재할 수도 있으므로 스킵하면 안된다.
그래서 접두사와 접미사를 활용하여 확실한 중복 연산들을 판별하여 줄여나가며 연산할 수 있는 KMP 알고리즘을 사용한다.
KMP 알고리즘이란?
▶문자열 매칭 검색 시접두사와접미사를 활용하여 중복 연산을 줄여나가O(N+M)의 속도로 빠르게 연산하는 기법
( N : 텍스트의 길이, M : 패턴의 길이 )
여기서 접두사와 접미사란
ABACAABA
란 문자열이 있을 때
접두사 : A, AB, ABA, ABAC ...
처럼 앞에서 하나씩 끊어서 본 것을 말하고
접미사 : A, BA, ABA, AABA ...
처럼 뒤에서 하나씩 끊어서 본것을 말한다.
위를 바탕으로 pi배열을 구할 수 있다.
pi배열 : 부분문자열 중에서 접두사 == 접미사 가 될 수 있는 가장 긴 것의 길이
index
부분문자열
pi[index]
0
A
0
1
AB
0
2
ABA
1
3
ABAC
0
4
ABACA
1
5
ABACAA
1
6
ABACAAB
2
7
ABACAABA
3
public static void makeTable(String pattern) {
int pLen = pattern.length();
table = new int[pLen];
int index = 0;
for(int i = 1; i < pLen; i++) {
/* index > 0 => 문자열 매칭이 시작됨
연속으로 일치하지 않으면 index 값을 돌려주어 다시 매칭 시작 */
while(index > 0 && pattern.charAt(i) != pattern.charAt(index)) {
index = table[index - 1];
}
if(pattern.charAt(i) == pattern.charAt(index)) {
index += 1;
table[i] = index;
}
}
}
위 표를 자바코드로 구현하였다.
이제 위 pi배열을 활용하여
탐색을 진행해보자.
public static int search(String str, String pattern) {
int sLen = str.length();
int pLen = pattern.length();
int index = 0;
for(int i = 0; i < sLen; i++) {
while(index > 0 && str.charAt(i) != pattern.charAt(index)) {
index = table[index - 1];
}
if(str.charAt(i) == pattern.charAt(index)) {
if(index == pLen - 1) {
index = table[index];
return 1;
}
else {
index++;
}
}
}
return 0;
}
문자열 : ABACAABCCCABACAABA
패턴 : ABACAABA
일 때
str[7] 에서 매칭이 실패 (C != A) 할 것이다. 그럼 지금까지 매칭이 성공했었으므로 index 값도 7이다.