private static void rabinKarp(String target, String pattern) {
int fl = target.length();
int pl = pattern.length();
final int P = 10;
// 맨 큰 단위에서 곱해지는 수
long head = 1;
long pHash = 0;
long tHash = 0;
// 초기 해쉬 구하기
for (int j = 0; j < pl; j++) {
pHash = (pHash * P + pattern.charAt(j));
tHash = (tHash * P + target.charAt(j));
if (j != 0) {
head = (head * P);
}
}
for (int i = 0; i <= fl - pl; i++) {
if (pHash == tHash) {
System.out.print(i + "\t");
}
// 해쉬가 다르다면 다음 해쉬 찾아보기
if (i + pl < fl) {
tHash = (tHash - target.charAt(i) * head) * P + target.charAt(pl + i);
}
}
}
하지만 이렇게 작성한 코드를 적용해보면 문제는 fail이 된다. 답이 틀렸단다.
곱해지는 P를 10이 아닌 7로 바꾸로 시도해보면 성공한다. 이런 케바케가 발생하다니. ㅜㅜ
어디가 잘못된걸까? 문제는 해쉬과정에 있다. 간단하게 만든 해쉬 함수이기 때문에 해쉬값이 일치하더라도 실제 패턴과 일치하지 않는 경우가 발생한다. 이런 경우를 해쉬 충돌이라고 한다. P를 10으로 처리했을 때는 해쉬 충돌이 발생해서 동일하지 않은 문자열이 동일하다고 판단되었고 P가 7인 경우는 다행히 해쉬 충돌이 발생하지 않은 것이다.
이를 보완하기 위해 해쉬값이 일치하는만 실제 문자열을 비교해보는 방법이 있다.
for (int i = 0; i <= fl - pl; i++) {
// 해쉬 값이 일치하는 상황에서 문자열 비교
if (pHash == tHash && pattern.equals(target.substring(i, i + pl))) {
System.out.print(i + "\t");
}
// 해쉬가 다르다면 다음 해쉬 찾아보기
if (i + pl < fl) {
tHash = (tHash - target.charAt(i) * head) * P + target.charAt(pl + i);
}
}
하지만 이번에는 시간 초과가 발생한다. ㅜㅜ
패턴의 길이가 매우 길다면 그냥 brute-force 형태가 되기 때문에 시간이 그만큼 소요되는 것이다.
결국 Rabin-Karp는 해쉬값을 구하는 방식과 테스트 케이스에 따라 될 수도 있고 안될 수도 있다.