어떤 문제를 푸는가
긴 텍스트 \(T\)(길이 \(N\)) 안에서 패턴 \(P\)(길이 \(M\))가 나타나는 모든 위치를
찾습니다. 단순 비교는 각 시작 위치마다 패턴을 처음부터 맞춰 보아 최악
\(O(NM)\)입니다. KMP는 이를 \(O(N + M)\)으로 줄입니다.
단순 검색의 낭비
T = aaaaab, P = aaab를 단순 비교하면, 한 위치에서 aaa까지 맞다가 b에서
어긋나면 텍스트 포인터를 한 칸만 뒤로 옮겨 처음부터 다시 봅니다. 하지만 우리는
이미 aaa가 맞았다는 정보를 알고 있습니다. 이 정보를 버리지 않는 것 이
KMP의 핵심입니다.
실패 함수 (failure / pi 배열)
패턴 자신에 대해, 각 접두사의 "진부분 접두사이면서 동시에 접미사 인 가장 긴
문자열의 길이"를 미리 계산합니다. 이를 \(\pi[i]\)라 합니다.
$$ \pi[i] = P[0..i] \text{의 접두사이면서 접미사인 가장 긴 진부분 문자열 길이} $$
예: P = ababa면 \(\pi = [0, 0, 1, 2, 3]\). abab의 접두=접미는 ab(길이 2),
ababa는 aba(길이 3)입니다.
이 값이 있으면, 매칭 중 어긋났을 때 "패턴의 어디로 점프해 다시 비교할지"를
즉시 알 수 있습니다. 텍스트 포인터는 절대 뒤로 가지 않습니다.
매칭 과정
텍스트를 왼쪽에서 오른쪽으로 한 번 훑습니다. 현재 패턴의 \(j\)개 글자가 맞아
있는 상태에서 다음 글자가 어긋나면, \(j\)를 \(\pi[j-1]\)로 줄여 "이미 일치하는
접두사"를 활용해 다시 시도합니다. \(j\)가 \(M\)에 도달하면 한 번의 출현입니다.
왜 선형인가
텍스트 포인터는 매 글자마다 한 번만 전진하니 \(O(N)\). 패턴 포인터 \(j\)는
어긋날 때 줄어들지만, 전진(맞을 때 +1)한 총량을 넘게 줄어들 수 없습니다.
따라서 \(j\)의 총 변화량도 \(O(N)\)입니다. 실패 함수 계산도 같은 논리로 \(O(M)\).
전체
$$ O(N + M) $$
핵심 직관
실패 함수는 패턴 내부의 자기 닮음(반복 구조) 을 미리 요약한 표입니다.
이 표 덕분에 어긋남이 생겨도 처음부터 다시 보지 않고, 이미 확보한 일치를
재활용합니다. 다음 강의에서 두 배열의 구현과 응용을 봅니다.