**Knuth-Morris-Pratt Algorithm**
Naive한 문자열 매칭의 시간 복잡도?
패턴 문자열의 길이를 M
, 주어진 문자열의 길이를 N
이라고 할 때,
시간복잡도 : O(NM)
inputString = "aaaaaa"
targetString = "aa"
matchList = []
for i in range(len(inputString) - len(targetString)):
flg = True
for j in range(len(targetString):
if inputString[i + j] != targetString[j]:
flg = False
break
if flg:
matchList.append(i) #시작 위치 넣어주기!
#InputString의 각 위치마다 최대 M번만큼 반복될 수 있으므로, O(N * M) 복잡도가 나옴
조금 더 효율적으로 바꿀 수는 없을까?
이미 알고 있는 정보를 활용하자!
현재까지 검사한 문자열을 기준으로, 접두사와 접미사가 일치하는 부분에선 이어서 탐색 가능!
(ABCAB
라면, AB
가 일치하므로, 문자열 검사 도중 MisMatch가 일어나면, AB or A
로 이동해 탐색을 다시 진행 할 수 있음.
뭐가 더 효율적일까? → 어차피 A
로 이동하더라도, AB
까지 일치할 걸 알고 있으니, 굳이 실제로 진행할 필요가 없는 A
대신 가장 긴 AB
로 이동하는 게 최적의 선택
즉, 어떤 위치에서 MisMatch가 일어날 경우, 접두사 / 접미사가 일치하는 SubString
중 가장 긴 상태로 전이 하면, 효율적으로 탐색을 이어서 진행 할 수 있음
TargetString = “ABAABB”
InputString = “ABAAABAABB”
Input |
A | B | A | A | A | B | A | A | B | B |
---|---|---|---|---|---|---|---|---|---|---|
Target |
A | B | A | A | B | B | ||||
용어 정리
startIndex | 각 문자열 매칭과정에서, 문자열을 비교할 첫 위치를 나타내는 인덱스 |
---|---|
nowIndex | 현재 상태를 나타내는 인덱스 |
Fail(x) → y | x상태에서 Fail(MisMatch)가 일어났을 때 이동할 상태 |
현재 상태까지의 문자열에서, 접두사 / 접미사가 일치하는 최대의 길이를 나타냄 |
더 이상 매칭 되지 않는 경우, startIndex
를 ****굳이 **startIndex + 1
**로 옮겨야 할까?
현재 상태 **ABAA
**에 대해서, 이동이 가능한 상태, 불가능한 상태는 어떤 경우일까?
→ 해당 상태의 접두사와 대응되는 현재 상태 **ABAA
**의 동일한 길이의 접미사가
일치하지 않는 경우!
현재 위치한 인덱스를 **nowIndex
**라고 할 때,
3번 인덱스는 현재 **Fail(Miss-Match)
**이 일어난 위치이므로, 고려하는 다음 위치
nextIndex < 3 (nowIndex)
ABAA
의 접두사 목록
접두사 (Prefix) | 접미사 (Suffix) | 이동 가능 여부 |
---|---|---|
A |
A |
Yes |
AB |
AA |
No |
ABA |
BAA |
No |
0번 인덱스가 **A
**이므로, ****현재 상태 **ABAA
**에서 ****이동이 가능 (ABAA
의 접미사 A == A
)
1번 인덱스가 **AB
**이므로, 현재 상태 ABAA
에서 이동이 불가능(ABAA
의 접미사 AA != AB
)
2번 인덱스가 ABA
이므로, 현재 상태 ABAA
에서 이동이 불가능(ABAA
의 접미사 BAA != AB
)
이렇게 현재 상태에서 이동 가능한 상태, 즉 접두사와 접미사가 일치하는 가장 긴 길이를,
해당 상태의 Fail함수로 정의하고, **Fail(현재 상태) → 다음 상태
**로 표현
따라서, Fail(ABAA) → A
이렇게, 모든 상태에 대해서 표현해보면,
Status | Fail(Status) | Length |
---|---|---|
A |
Φ |
0 |
AB |
Φ |
0 |
**A**B**A** |
A |
1 |
**A**BA**A** |
A |
1 |
**AB**A**AB** |
AB |
2 |
ABAABB |
Φ |
0 |
모든 상태에 대해서 이렇게 Prefix
Suffix
를 구하는 건 매우 비효율적임! (O(M^2)
)
하지만, 이전 문자열들의 Fail(prev)
들을 통해 현재 문자열의 Fail(now)을
빠르게 구할 수 있음
~
→ 임의의 문자열 (Fail(~) → Φ
)