2024-04-03-08. String Matching
문자열의 접미부와 접두부
문자열 x에 대해, 접두부와 접미부를 다음과 같이 정의한다.
- x의 접두부 : 문자열 x의 앞에 오는 부분 문자열
- 문자열 x의 크기가 k인 접두부를 xk으로 표기한다. 즉, xk = x[1..k] 이다.
- x = “banana” 일 때, x1=”b”, x2=”ba”, x3=”ban”, ... 이다.
- x의 접미부 : 문자열 x의 뒤에 오는 부분 문자열
- “e”, “le”, “ple” 등은 문자열 x = “apple” 의 접미부이다.
- 배열 A에 대해, A[a..b] 는 인덱스 a부터 b까지의 원소를 순서대로 나열한 A의 부분 배열을 의미한다. 문자열도 배열의 일종이다.
String Matching 문제 정의
- 텍스트 : 길이가 n인 문자열 T
- 패턴 : 길이가 m인 문자열 P. 단, m<=n
- 텍스트 T와 패턴 P를 구성하는 알파벳들의 집합을 ∑로 표기한다.
- 예를 들어, T = “acaabc” 이고 P = “aab” 이면 ∑ = { a, b, c } 이다.
String Matching은 텍스트에서 패턴이 나타나는 위치를 모두 찾는 문제이다.
- ex) 텍스트 abababcabcabcdabccbaabdabcabcdabcd 에서 패턴 abcabcd를 찾는 문제
좀 더 정확히 정의하면,
m <= i <= n 이고 T[(i-m+1)..i] = P 를 만족하는 s값을 찾는 문제, 즉 Ti의 접미부가 P인 i값을 찾는 문제이다.
String Matching의 응용
- Word Processor의 검색
- 웹 검색 엔진
- 바이러스 검사 프로그램 : 바이러스와 유사한 바이트열을 string matching으로 찾는다.
- 생물학 : 특정 염기 서열을 찾는 데 사용된다.
String Matching 알고리즘의 출력값과 접미부 함수
String Matching 알고리즘의 출력값을 생성하는 데 접미부 함수가 사용된다.
접미부 함수 σ는 정의역이 ∑* (∑의 원소로 구성할 수 있는 모든 문자열의 집합) 이고 공역이 {0, 1, ..., m} 인 함수로, σ(x)는 문자열 x의 접미부이면서 패턴 P의 가장 긴 접두부인 문자열의 길이를 나타낸다.
예를 들어, P = “ab” 일 때, σ(”ccaca”) = 1, σ(”ccab”) = 2 이다.
String Matching 알고리즘의 출력값은 접미부 함숫값의 배열 output = [ σ(T1), σ(T2), σ(T3), ..., σ(Tn) ] 이다.
예를 들어, T = “abababacaba”, P = “ababaca” 이면, output = [ 1, 2, 3, 4, 5, 4, 5, 6, 7, 2, 3 ] 이다.
output 배열에서 값이 m인 원소의 인덱스를 통해 텍스트에서 패턴이 나타나는 위치를 알 수 있다.
위 예시에서 output[9] 에 m=7이 저장되어 있으므로, T9의 접미부에 P가 나타난다.
Naive String Matching 알고리즘
int[] naivematch(char T[], int n, char P[], int m) {
int output[n];
for(int k = 0; k <= n-m; k++) {
for(int j=1; j<=m; j++) {
if(T[k+j] == P[j])
output[k+j] = max(output[k+j], j+1);
// output[s+j]에 더 큰 값이 이미 저장되어 있을 수 있으므로 max 함수 사용
else
break;
}
}
return output;
}
naive string matching 알고리즘은 T[k+1..k+m]과 P[1..m] 을 비교한다. (단, k = 0, 1, 2, ..., n-m)
- 안본 데부터 시작하면 못 찾는 경우가 생긴다.
- 전체 문자열의 길이가 n, 찾으려는 문자열의 길이가 m일 때, 시간복잡도는 O(m(n-m))이다.
유한 오토마타(Finite Automaton)
string matching 알고리즘의 성능을 개선하기 위해 유한 오토마타를 사용할 수 있다.
유한 오토마타 M은 다음과 같은 5-튜플 (Q, q0, A, ∑, δ)로 구성된다.
- Q : 유한한 개수의 상태(state)의 집합
- q0 ∈ Q : 시작 상태
- A ⊆ Q : “받아들이는” 상태의 집합
- ∑ : 입력 알파벳의 집합
- δ : Q x ∑ 에서 Q로 매핑되는 함수. M의 전이 함수라고 불린다.
오토마타는 상태 q0에서 시작해 입력 문자열에서 한 번에 한 문자를 읽는다. 오토마타의 상태가 q이고 입력 문자 a를 읽으면 상태 q는 상태 δ(q, a)로 바뀐다. 현재의 상태 q가 A에 속하면 오토마타가 현재까지 읽은 문자열을 받아들인다고 한다. 그렇지 않으면 거절한다고 한다.
예를 들어, 상태 집합이 Q = {0, 1} 이고, 시작 상태는 q0 = 0, 받아들이는 상태는 A = {1}, 입력 알파벳은 ∑ = {a, b}일 때, 다음과 같이 전이 함수 δ를 정의했다고 하자.
δ(0, a) = 1, δ(0, b) = 0, δ(1, a) = 0, δ(1, b) = 0
이때, 유한 오토마타 M은 다음 그림과 같이 나타낼 수 있다.
예를 들어, 문자열 “abaaa”가 입력되면 오토마타의 상태는 순서대로 0, 1, 0, 1, 0, 1 으로 변한다. 해당 문자열을 전부 읽었을 때, 오토마타의 상태는 A에 속하는 1이므로, 오토마타는 입력 “abaaa”를 받아들인다.
반면, abbaa가 입력되었을 때 오토마타의 상태는 순서대로 0, 1, 0, 0, 1, 0으로 변하므로, 오토마타는 입력 ”abbaa”를 거절한다.
오토마타의 최종 상태 함수 Φ(w)는 문자열 w를 전부 읽었을 때 오토마타의 최종적인 상태로 정의된다.
최종 상태 함수는 다음과 같이 순환적으로 정의 가능하다.
- Φ(””) = q0 (””는 빈 문자열을 의미)
- Φ(wa) = δ(Φ(w), a) (단, w는 ∑의 원소로 구성된 문자열이며 a는 ∑내의 문자이다)
- 두 문자열 x, y에 대해, x뒤에 y를 연결한 문자열은 xy로 표기한다. 위에서 wa는 w뒤에 문자 a를 연결한 문자열이다.
ex) Φ(”abbaa”) = 1, Φ(”abaaa”) = 0
오토마타가 문자열 w를 받아들일 필요충분조건은 Φ(w)∈A 이다.
DFA (Deterministic Finite Automaton)를 이용한 String Matching
오토마타가 텍스트 T에서 i개의 문자를 읽었을 때 상태가 σ(Ti) 라면, 즉 Φ(Ti) = σ(Ti)라면, 해당 오토마타를 String Matching에 사용할 수 있을 것이다. 오토마타의 상태를 output 배열에 저장하면 된다.
이러한 오토마타를 스트링 매칭 오토마타라고 한다.
예를 들어, ∑ = {a, b, c} 이고 P = “ababaca”, T = “abababacaba” 일 때, 스트링 매칭 오토마타의 최종 상태 함수는 다음과 같다.
아래 get_transition_fuction이 패턴 P에 대한 스트링 매칭 오토마타의 전이 함수를 리턴한다고 하자. 그렇다면 리턴된 오토마타를 이용해 다음과 같이 String Matching을 수행할 수 있다.
// O(|∑|m)
int[][] get_transition_fuction(char P[], int m, char[] ∑) {...}
// O(n)
int DFAmatch(char T[], int n, char P[], int m, char[] ∑) {
int[][] table = get_transition_fuction(P, m, ∑); // table[q][a] = δ(q, a)
int s = 0; // 오토마타의 상태
for(int i=1; i<=n; i++) {
output[i] = s = table[s][T[i]]; // δ(s, T[i])
}
}
위 알고리즘에서, 스트링 매칭 알고리즘의 전이 함수를 구하는 데 O(|∑|m), 전이 함수로 스트링 매칭을 수행하는 데 O(n) 시간이 걸린다.
총 O(|∑|m+n) 시간이 걸린다.
String Matching 오토마타의 구성
오토마타가 Φ(Ti) = σ(Ti) 를 만족하도록 하려면 오토마타의 전이 함수를 어떻게 정의해야 할까?
패턴 P에 대응하는 스트링 매칭 오토마타는 다음과 같이 정의한다.
- 상태 집합 Q는 {0, 1, ..., m} 이다.
- 초기 상태 q0는 0이다.
- 받아들이는 상태는 m 뿐이다. 즉, A = {m} 이다.
- 임의의 상태 q와 문자 a에 대해, 전이 함수 δ는 다음과 같다.
위와 같은 전이 함수를 가지는 오토마타는 0이상 n이하의 모든 정수 i에 대해 Φ(Ti) = σ(Ti) 를 만족한다.
이를 증명하기 위해 먼저 다음 두 가지 보조 정리를 증명한다.
- 임의의 문자열 x와 문자 a에 대해 σ(xa) <= σ(x) + 1 이다.
- 증명 :

- σ(x) = k 라 하자.
- σ(xa) = r > k+1 라고 가정한다. 이때, Pr는 xa의 접미부가 된다.
- Pr의 끝과 xa의 끝에서 a를 삭제하면, Pr-1은 x의 접미부가 된다.
- r-1 > k 이므로, Pr-1은 Pk보다 긴 x의 접미부가 된다.
- 이는 σ(x) = k라는 전제와 모순이다.
- 따라서 r <= k + 1, 즉 σ(xa) <= σ(x) + 1 여야 한다.
- 임의의 문자열 x와 문자 a에 대해 σ(x) = q 이면 σ(xa) = σ(Pqa) 이다.
- 증명 :

- σ의 정의에 의하면, Pq는 x의 접미부이다.
- Pqa는 xa의 접미부이므로 σ(Pqa) <= σ(xa) 가 성립한다. ...ㄱ
- σ(xa) = r 이라 하면 보조정리 1에 의해 r<=q+1 이다.
- Pqa는 xa의 접미부이고, Pr도 xa의 접미부이며, |Pr|<=|Pqa| 이므로, Pr은 Pqa의 접미부이다.
- 따라서 r <= σ(Pqa), 즉 σ(xa) <= σ(Pqa)이다. ...ㄴ
- ㄱ, ㄴ을 종합하면 σ(xa) <= σ(Pqa) <= σ(xa), 즉 σ(xa) = σ(Pqa) 가 성립한다.
위 두 가지 보조 정리를 바탕으로, 전이 함수가 δ(q, a) = σ(Pqa)인 오토마타는 0이상 n이하의 모든 정수 i에 대해 Φ(Ti) = σ(Ti) 를 만족함을 증명해 보자.
- Base : i = 0일 때, Φ(T0) = q0 = 0 = σ(T0) 로 성립한다.
- Step : 어떤 정수 i에 대해 Φ(Ti) = σ(Ti)라 하면
- Φ(Ti+1) = Φ(TiT[i+1]) = δ(Φ(Ti), T[i+1]) (→ Φ의 정의에 의해)
- Φ(Ti) = σ(Ti) = q 라 하면, δ(Φ(Ti), T[i+1]) = δ(q, T[i+1]) = σ(PqT[i+1]) (→ δ의 정의에 의해)
- 보조정리 2에 의해, σ(PqT[i+1]) = σ(TiT[i+1]) 이 성립한다.
- σ(TiT[i+1]) = σ(Ti+1)이므로, Φ(Ti+1) = σ(Ti+1) 이다.
전이 함수를 리턴하는 알고리즘의 구체적인 구현은 생략한다.
∑ = {a, b, c} 이고 P = “ababaca” 일 때, 스트링 매칭 오토마타의 전이 함수는 다음과 같다.