문자열 매칭 (String matching)
직관 → 조작 → 예시 → 함정 → 답안
String matching은 긴 text T 안에서 짧은 pattern P가 시작되는 모든 shift(Verschiebung)를 찾는 문제입니다. 핵심은 '문자열이 비슷해 보인다'가 아니라, 각 shift sft에 대해 모든 j에서 \(T[\text{sft}+j]=P[j]\)T[sft+j]=P[j]인지 확인하는 것입니다. Sheet07은...
T: 검색 대상 텍스트 배열(text array), |T| = n\(P: 찾을 패턴\cdot \text{Muster} 배열, |P| = m, m \le n\)P: 찾을 패턴·Muster 배열, |P| = m, m ≤ nSigma: 유한 알파벳(finite alphabet / endliches Alphabet)\(\text{sft}: 이동량(\text{shift} / \text{Verschiebung}), 0 \le \text{sft} \le n-m\)sft: 이동량(shift / Verschiebung), 0 ≤ sft ≤ n-m문자열 매칭은 “패턴을 어디에 올려놓을 수 있나”를 모두 검사하는 문제입니다
문자열 매칭(String matching)을 처음 보면 Rabin-Karp, KMP, 유한 상태 기계(FSM) 같은 이름이 먼저 보여서 복잡해 보입니다. 하지만 출발점은 아주 단순합니다. 긴 텍스트(text) T 위에 짧은 패턴(pattern) P를 왼쪽부터 한 칸씩 올려놓고, 어느 시작 위치에서 완전히 같은지 찾는 문제입니다. 이 시작 위치를 이동량(shift, Verschiebung)이라고 부릅니다.
시험에서 가장 많이 틀리는 지점은 “한 번 일치했으니 패턴 길이만큼 뛰어도 된다”는 생각입니다. 출현 위치(occurrence)는 겹칠 수 있습니다. 예를 들어 \(T=\text{aaaaaa}\)T=aaaaaa, \(P=\text{aaa}\)P=aaa이면 정답 이동량은 0,1,2,3입니다. 따라서 모든 유효 이동량(valid shift)을 정확히 찾는 감각을 먼저 익히고, 그다음 빠른 알고리즘을 배워야 합니다.
1. 후보 위치를 정한다
sft는 P[0]을 T[sft] 밑에 놓는다는 뜻입니다. 가능한 범위는 \(0 \le \text{sft} \le n-m\)0 ≤ sft ≤ n-m입니다.
2. 모든 글자를 확인한다
유효 이동량(valid shift)이 되려면 모든 j에 대해 \(T[\text{sft}+j]=P[j]\)T[sft+j]=P[j]가 성립해야 합니다. 첫 글자만 맞으면 부족합니다.
3. 빠른 알고리즘은 후보를 줄인다
Rabin-Karp는 해시(hash)로 후보를 빠르게 고르고, 반드시 문자 비교로 거짓 일치(false hit)를 제거합니다. FSM/KMP 계열은 접두사·접미사(prefix/suffix) 정보를 상태로 재사용합니다.
4. 답안 문장
“가능한 모든 이동량(shift)을 검사하고, 유효한 이동량이면 목록 L에 넣는다. 겹침(overlap)도 허용한다.” 이 문장을 먼저 말하면 대부분의 함정을 피할 수 있습니다.
정의와 기호
아래 내용은 Übung\AuD26_Sheet07-GrpSol.pdf 1~3쪽의 문자열 매칭 문제(String-Matching-Problem), 단순 문자열 매칭(NaiveStringMatching), 정확성 불변식(correctness invariant), 실행 시간(Laufzeit) 분석에 근거합니다.
입력 길이
텍스트 길이 |T|=n, 패턴 길이 |P|=m, m≤nT는 검색할 긴 텍스트, P는 찾을 패턴(Muster)입니다. 두 배열의 원소는 유한 알파벳(Alphabet) Sigma의 문자라고 둡니다.
가능한 이동량
가능한 이동량의 범위: 0 ≤ sft ≤ n−msft는 P[0]을 T[sft] 밑에 놓는 후보 시작 위치입니다. 마지막 후보는 패턴이 텍스트 밖으로 나가지 않는 n-m입니다.
유효 이동량 조건
sft가 유효 ⇔ 모든 j=0,…,m−1에서 T[sft+j]=P[j]첫 글자만 맞으면 안 됩니다. P의 모든 인덱스 j가 해당 텍스트 구간과 같아야 유효 이동량(gültige Verschiebung, valid shift)입니다.
단순 탐색 실행 시간
최악의 경우 O((n−m+1)m)가능한 이동량이 n-m+1개이고, 각 이동량에서 최악의 경우 m번 비교합니다. 실제 실행은 불일치(mismatch)에서 일찍 멈출 수 있지만 최악의 경우 상한(worst-case bound)은 이처럼 계산합니다.
이동량(shift)은 패턴을 한 칸씩 밀어보는 후보 시작점입니다
\(\text{sft} = 2\)sft = 2이면 P[0]은 T[2], P[1]은 T[3]와 비교됩니다. 이 관계를 기억하면 인덱스(index) 실수가 줄어듭니다.
한 번에 하나의 정렬 위치 확인하기
겹치는 출현 위치(occurrence)도 모두 정답입니다
초보자가 가장 자주 놓치는 부분입니다. 일치(match)를 찾았다고 m칸 건너뛰면 겹침(overlap)을 놓칩니다.
수식이 포함된 학습 항목: \(T = [a,a,a,a,a,a], P = [a,a,a]\)T = [a,a,a,a,a,a], P = [a,a,a]
Sheet07 예시: 모든 후보 이동량을 검사합니다
근거는 Übung\AuD26_Sheet07-GrpSol.pdf 1~3쪽입니다. 의사코드(pseudocode)는 \(L=[]\)L=[]로 시작하고, \(\text{sft}=0..n-m\)sft=0..n-m마다 \(j=0..m-1\)j=0..m-1 비교를 수행한 뒤 모두 같으면 L에 추가(append)합니다.
L = []
\(\text{for} \text{sft} = 0..n-m\)for sft = 0..n-m
\(j = 0..m-1\)j = 0..m-1을 비교
모두 같을 때만 append
\(O((n - m + 1) \cdot m)\)O((n - m + 1) * m)
| 항목 (sft) | 텍스트 구간 T[sft..sft+m-1] | \(P=[a,a,b]\)P=[a,a,b]와 비교 |
판정 | 이 행 뒤의 L |
|---|---|---|---|---|
| 0 | [a,a,b] | \(a=a, a=a, b=b\)a=a, a=a, b=b | 유효 | [0] |
| 1 | [a,b,a] | \(a=a, b\ne a\)a=a, b!=a | 불일치 | [0] |
| 2 | [b,a,a] | \(b\ne a\)b!=a | 불일치 | [0] |
| 3 | [a,a,a] | \(a=a, a=a, a\ne b\)a=a, a=a, a!=b | 불일치 | [0] |
| 4 | [a,a,a] | \(a=a, a=a, a\ne b\)a=a, a=a, a!=b | 불일치 | [0] |
| 5 | [a,a,a] | \(a=a, a=a, a\ne b\)a=a, a=a, a!=b | 불일치 | [0] |
| 6 | [a,a,b] | \(a=a, a=a, b=b\)a=a, a=a, b=b | 유효 | [0,6] |
정확성 불변식(correctness invariant): 바깥 반복문의 sft번째 반복 전에 L은 모든 유효 이동량 \(t < \text{sft}\)t < sft를 포함합니다. 반복이 끝나면 현재 sft도 일치할 때 추가되므로 불변식이 유지됩니다.
해시값의 일치는 후보일 뿐, 실제 일치를 확정하지 않습니다
근거는 Übung\AuD26_Sheet07-GrpSol.pdf 4~6쪽입니다. Rabin-Karp는 전처리(preprocessing)로 p, t0, \(h=d^{m-1}\)h=dᵐ⁻¹을 만들고, 굴림 갱신(rolling update)으로 다음 구간의 해시를 계산합니다.
전처리(preprocessing)
전처리 p,t₀ ∈ Θ(m), h = dᵐ⁻¹ mod qSheet07 예시는 십진법 밑(decimal base) \(d=10\)d=10으로 설명합니다. 일반 알파벳이면 |Sigma| = d로 생각합니다.
굴림 갱신(rolling update)
tₛ𝒻ₜ₊₁ = (d(tₛ𝒻ₜ − T[sft]h) + T[sft+m]) mod q왼쪽으로 빠지는 문자의 기여분을 빼고, 한 자리 밀고, 새 문자를 넣습니다. 나머지 연산 버전(modulo version)에서는 계산을 mod q로 관리합니다.
후보 검사(candidate check)
p ≡ tₛ𝒻ₜ (mod q)이면 sft는 검증할 후보입니다.\(p\not\equiv t_{\mathit{sft}}\pmod q\)p와 t(sft)의 q에 대한 나머지가 다름이면 확실히 불일치입니다. 하지만 나머지가 같아도 거짓 일치(unechter Treffer, false hit)가 가능하므로 문자별 검증이 필요합니다.
Sheet07의 거짓 일치(false hit)
P=[3,1,4], q=13일 때 거짓 일치 위치는 {6,10}Sheet07 표에서는 해시 후보 판정이 참이어도 실제 구간이 P와 달라 일치(Treffer)가 아닌 행이 나옵니다. 객관식에서 자주 나오는 함정입니다.
불일치 뒤에도 이미 맞은 정보를 버리지 않습니다
아래 접두사·접미사(prefix/suffix) 그림은 빠른 방법의 일반적인 직관을 보여 줍니다. Sheet07은 Rabin-Karp를 자세히 다루고, Sheet09는 FSMMatching 상태가 저장하는 정보를 묻습니다. 전체 KMP 실패 함수(failure function) 표 계산은 이 페이지에서 일반 지식·추후 보강 항목으로 구분합니다.
접두사·접미사의 겹침 정보
상태 st는 이동량이 아니라 일치한 접두사의 길이입니다
근거는 Übung\AuD26_Sheet09.pdf 4~5쪽 및 Übung\AuD26_Sheet09-GrpSol.pdf 7~9쪽입니다. Sheet09 H1은 \(P=[\text{lambda},\text{delta},\text{lambda},\text{sigma}]\)P=[lambda,delta,lambda,sigma]에 대한 유한 오토마톤(endlicher Automat, FSM)을 그리고, FSMMatching(T,delta,m)의 실행 시간과 상태 의미를 묻습니다.
상태의 의미
st = 지금까지 읽은 텍스트의 접미사와 패턴 접두사가 일치하는 최대 길이st는 현재 후보 시작점 sft가 아닙니다. 지금까지 읽은 텍스트의 접미사가 패턴의 접두사와 얼마나 길게 맞는지를 저장합니다.
출력할 이동량
st=m이면 출력 이동량 sft=i−m+1현재 텍스트 인덱스가 i이고 수용 상태(accepting state)에 도달하면, 방금 끝난 출현 위치의 시작점은 i-m+1입니다.
탐색 실행 시간
전이 함수가 주어졌을 때 FSM 탐색 시간은 O(n)자동 전이 함수(transition function) delta가 이미 주어졌다면 텍스트 문자를 한 번씩 읽으며 상태를 갱신하므로 탐색은 선형(linear)입니다.
오토마톤 구성
오토마톤 구성 비용은 이 문제의 분석 범위에서 제외합니다.Sheet09 H1(c)는 “오토마톤 구성 제외(without automaton construction)”를 명시합니다. 따라서 이 페이지에서는 구성 비용을 확정하지 않고, 출처로 확인되는 탐색 실행 시간만 제시합니다.
\(P = [\text{lambda}, \text{delta}, \text{lambda}, \text{sigma}]\)P = [lambda, delta, lambda, sigma]의 FSM 상태 흐름
Naive / Rabin-Karp / FSM / KMP 비교
요구된 네 가지 알고리즘을 시험 답안용으로 정리했습니다. KMP의 자세한 실패 함수 계산은 현재 자료 모음(corpus)에서 직접 확인한 출처 구간이 부족하므로 일반 알고리즘 지식으로 표시합니다.
| 방법 | 적용 조건·입력 가정 | 말해야 할 실행 시간 | 접두사·실패 함수·상태 함정 | 근거 |
|---|---|---|---|---|
| NaiveStringMatching | 유한 알파벳 위의 모든 배열에 적용하며 0..n-m을 모두 검사합니다. |
최악의 경우 \(O((n-m+1)\cdot m)\)O((n-m+1)*m)입니다. |
실패 정보를 기억하지 않습니다. 일치 뒤에 겹치는 위치를 건너뛰면 안 됩니다. | Sheet07-GrpSol 1~3쪽 |
| Rabin-Karp | 알파벳을 밑 d의 숫자로 바꾸고, 제한된 크기로 계산하려면 나머지 연산용 소수 q를 선택합니다. |
전처리는 \(\Theta(m)\)Θ(m), 이동량별 굴림 갱신은 \(O(1)\)O(1)이며 후보는 직접 검증합니다. |
해시값의 일치는 실제 일치의 증명이 아닙니다. 거짓 일치는 기호별로 검증해야 합니다. | Sheet07-GrpSol 4~6쪽 |
| FSMMatching | 전이 함수 delta와 패턴 길이 m이 탐색 전에 이미 주어집니다. |
오토마톤 구성을 제외하면 \(O(n)\)O(n)입니다. |
st는 현재 이동량이 아니라 가장 긴 접미사·접두사의 일치 길이를 저장합니다. |
Sheet09 4~5쪽 |
| KMP | 일반 알고리즘 지식: 패턴 P의 접두사·실패 함수를 사용합니다. |
일반 알고리즘 지식: 전처리 \(O(m)\)O(m), 탐색 \(O(n)\)O(n)입니다. |
실패 함수 값은 재사용할 수 있는 접두사의 길이이며, “m칸 이동”이나 출현 인덱스가 아닙니다. |
현재 AUD 구간에서는 출처가 불명확하므로 Sheet07·09의 사실과 구분합니다. |
시험 함정
아래 주장을 보면 먼저 출처로 확인되는 조건, 실행 시간, 인덱스 규칙을 붙여서 반박합니다.
일치 뒤에 m칸 이동?
틀립니다. 출현 위치는 겹칠 수 있습니다. \(T=\text{aaaaaa}, P=\text{aaa}\)T=aaaaaa, P=aaa의 답은 [0,1,2,3]입니다.
이동량 범위가 0..n-1?
틀립니다. 패턴 전체가 텍스트 안에 있어야 하므로 \(0 \le \text{sft} \le n-m\)0 ≤ sft ≤ n-m입니다.
첫 글자만 같으면 유효?
틀립니다. 모든 \(0 \le j < m\)0 ≤ j < m에 대해 \(T[\text{sft}+j]=P[j]\)T[sft+j]=P[j]여야 합니다.
단순 탐색은 항상 nm번 비교?
틀립니다. 실제로는 불일치에서 일찍 멈출 수 있습니다. \(O((n-m+1)m)\)O((n-m+1)m)은 최악의 경우 상한입니다.
Rabin-Karp 해시가 같으면 실제 일치?
틀립니다. 나머지 연산 충돌로 거짓 일치가 가능하므로 문자별 확인이 필요합니다.
FSM 상태 st가 현재 이동량?
틀립니다. st는 지금까지 읽은 텍스트 접미사와 패턴 접두사가 맞는 최대 길이입니다.
말로 바로 확인하기
각 질문에는 한 문장 정의, 하나의 예시, 실행 시간의 근거까지 답해 보세요.
1
문자열 매칭 문제(String-Matching-Problem)를 T, P, n, m, Sigma, sft로 형식적으로 정의하세요.
2
유효 이동량 조건을 전칭 기호(quantifier)로 말하고, sft의 가능한 범위를 설명하세요.
3
\(T=[a,a,a,a,a,a], P=[a,a,a]\)T=[a,a,a,a,a,a], P=[a,a,a]에서 L을 구하고 겹침 함정을 설명하세요.
4
Sheet07 예시 \(T=[a,a,b,a,a,a,a,a,b], P=[a,a,b]\)T=[a,a,b,a,a,a,a,a,b], P=[a,a,b]의 단순 탐색 결과를 말하세요.
5
NaiveStringMatching의 정확성 불변식과 최악의 경우 실행 시간을 반복 횟수로 유도하세요.
6
Rabin-Karp의 굴림 갱신이 무엇을 빼고 무엇을 넣는지 설명하세요.
7
\(p\equiv t_{\mathit{sft}}\pmod q\)p와 t(sft)의 q에 대한 나머지가 같음이 실제 일치의 확정이 아니라 후보일 뿐인 이유를 거짓 일치로 반박하세요.
8
FSMMatching에서 st가 저장하는 정보와 \(\text{st}=m\)st==m일 때 출력할 이동량을 말하세요.
출처 근거
Übung\AuD26_Sheet07-GrpSol.pdf1~3쪽: 문자열 매칭 문제의 형식적 정의, 유효 이동량 조건, NaiveStringMatching 의사코드, 불변식, 실행 시간, \(L=[0,6]\)L=[0,6]예시.Übung\AuD26_Sheet07-GrpSol.pdf4~6쪽: Rabin-Karp 전처리, 굴림 갱신, 나머지 연산 후보 검사, 직접 검증, 거짓 일치.Übung\AuD26_Sheet09.pdf4~5쪽 및Übung\AuD26_Sheet09-GrpSol.pdf7~9쪽: \(P=[\text{lambda},\text{delta},\text{lambda},\text{sigma}]\)P=[lambda,delta,lambda,sigma]의 FSMMatching 연습, 오토마톤 구성을 제외한 실행 시간, 상태의 의미.
일반 지식·추후 보강
- 현재 확인한 AUD 출처 구간에는 전체 KMP 표의 유도가 없으므로, KMP 전처리·탐색 상한과 실패 함수 함정은 일반 알고리즘 지식으로 수록했습니다.
- Sheet09가 오토마톤 구성을 제외한 탐색 실행 시간을 묻기 때문에 FSM 오토마톤 구성 비용은 단정하지 않았습니다.
- 전체 KMP/FSM 구성을 다룬 원본 강의 페이지가 자료 모음에 추가되면, 정확한 강의 표기법으로 이 페이지를 보강해야 합니다.
읽는 순서가 보이는 핵심 공식
정의, 수식, 시험 판정 문장을 분리해서 공식이 답안에서 어떻게 쓰이는지 바로 확인합니다.
문자열 매칭 입력(String matching input)
문제의 모든 답은 이 표기에서 출발합니다.
T[0..n-1],\quad P[0..m-1],\quad m\le n,\quad T[i],P[j]\in\Sigma- T: 검색할 text array
- P: 찾을 pattern/Muster array
- Sigma: 가능한 문자들의 유한 alphabet
- n,m: text와 pattern의 길이
index convention이 바뀌면 shift 답이 하나씩 밀리므로 시험에서는 0-based인지 1-based인지 먼저 고정합니다.
유효 이동량(Valid shift)
valid shift는 window 전체가 pattern과 같은 경우입니다.
\mathit{sft}\text{가 유효}\Longleftrightarrow \forall j\in\{0,\ldots,m-1\}: T[\mathit{sft}+j]=P[j]- sft: P[0]을 T[sft] 아래에 놓는 시작 위치
- j: pattern 내부 index
- T[sft+j]: text window의 j번째 문자
첫 글자 또는 마지막 글자만 같다는 조건은 충분하지 않습니다.
이동량 범위(Shift range)
pattern 전체가 text 안에 들어가야 합니다.
0\le\mathit{sft}\le n-m,\qquad \#\{\text{후보 이동량}\}=n-m+1- n-m: 마지막으로 가능한 시작 위치
- n-m+1: inclusive range의 후보 개수
\(m > n\)m > n인 입력은 이 lesson에서 다루는 Sheet07 전제 \(m \le n\)m ≤ n밖입니다.
단순 탐색의 최악 실행 시간(Naive worst-case runtime)
후보 shift 수와 각 shift에서의 문자 비교 수를 곱합니다.
T_{\mathrm{naive}}(n,m)\in O((n-m+1)m)\subseteq O(nm)- 바깥 반복문: n-m+1회 반복
- 안쪽 반복문: 최대 m회 비교
- early mismatch는 실제 실행을 줄일 수 있지만 worst-case bound는 유지됩니다.
\(O(\text{nm})\)O(nm)만 외우지 말고 왜 (n-m+1)m이 먼저 나오는지 말해야 감점 위험이 줄어듭니다.
단순 탐색의 불변식(Naive invariant)
정확성 증명은 L이 지금까지 확인한 모든 valid shift를 담는다는 약속입니다.
\text{이동량 }\mathit{sft}\text{ 처리 전}:\quad L=\{t\mid 0\le t<\mathit{sft},\ T[t..t+m-1]=P\}- L: output list
- \(t < \text{sft}\)
t < sft: 이미 처리된 shift만 말합니다. - termination에서 \(\text{sft}=n-m+1\)
sft=n-m+1을 넣으면 모든 가능한 shift가 포함됩니다.
Sheet07 solution은 Initialization, Fortsetzung, Terminierung 구조로 이 invariant를 증명합니다.
Rabin-Karp 창의 수치값(Rabin-Karp window value)
문자 array를 base-d 숫자처럼 보아 window를 빠르게 비교합니다.
p=Σ[i=0]ᵐ-1}P[i]dᵐ-1-i},\qquad t_{\mathit{sft}}=Σ[i=0]ᵐ-1}T[\mathit{sft}+i]dᵐ-1-i}- d: 알파벳 크기 또는 진법(base)
- p: 패턴의 수치값(pattern numeric value)
- t(sft): 현재 텍스트 창의 수치값
Sheet07은 decimal alphabet 예시 \(d=10\)d=10으로 설명합니다.
굴림 갱신(Rolling update)
다음 window는 대부분 이전 window와 겹치므로 \(O(1)\)O(1)로 갱신할 수 있습니다.
t_{\mathit{sft}+1}=d\bigl(t_{\mathit{sft}}-T[\mathit{sft}]dᵐ-1}\bigr)+T[\mathit{sft}+m]- T[sft]·dᵐ⁻¹: 빠져나가는 왼쪽 문자의 기여
- d(...): 남은 문자들의 자릿수를 왼쪽으로 이동
- T[sft+m]: 새로 들어오는 오른쪽 문자
modulo version에서는 같은 식을 mod q로 관리합니다.
나머지를 이용한 후보 검사(Modulo candidate test)
작은 수로 빠르게 거르되, equality는 match 확정이 아닙니다.
t_{\mathit{sft}}\not\equiv p\pmod q\Rightarrow\text{불일치},\qquad t_{\mathit{sft}}\equiv p\pmod q\Rightarrow\text{문자 검증}- q: prime modulus
- not congruent: 안전하게 제외 가능
- congruent: candidate only, false hit 가능
Sheet07 table에서 \(\text{sft}=6,10\)sft=6,10은 modulo candidate지만 실제 match가 아닌 false hit입니다.
유한 상태 기계의 상태 의미(FSM state meaning)
FSMMatching의 state는 지금까지 읽은 text 끝부분이 pattern 앞부분과 얼마나 맞는지입니다.
\mathit{st}=\max\{k\mid P[0..k-1]\text{가 지금까지 읽은 텍스트의 접미사}\}- st: 일치한 접두사의 길이(matched-prefix length)
- k: 가능한 prefix 길이
- suffix of text read so far: 방금 읽은 text prefix의 끝부분
st를 current shift라고 말하면 Sheet09 H1(c)(ii)에서 바로 틀립니다.
유한 상태 기계의 출력 이동량(FSM output shift)
accepting state에 도달한 순간 occurrence의 시작 위치를 역산합니다.
\mathit{st}=m\text{이고 현재 인덱스가 }i\Rightarrow\text{ }i-m+1\text{을 출력}- i: 현재 읽은 text index
- m: pattern length
- i-m+1: 방금 끝난 occurrence의 시작 위치
Sheet07 FSM example은 \(P=[n,a,n,o]\)P=[n,a,n,o]에서 \(L=[5,11]\)L=[5,11]을 얻습니다.
Opening hook: 문자열 찾기는 쉬워 보이지만 시험에서는 index 문제입니다
String matching을 처음 보면 '그냥 text에서 pattern을 찾으면 되는 것 아닌가?'라고 느낍니다. 실제로 문제 자체는 직관적입니다. 긴 배열 T가 있고, 짧은 배열 P가 있습니다. 우리는 P가 T 안에서 어디서 시작하는지 찾습니다. 하지만 AUD 시험에서는 이 단순함 때문에 오히려 실수가 많이 납니다. 답이 첫 번째 위치 하나인지, 모든 위치 list인지, shift가 0-based인지 1-based인지, pattern이 겹쳐서 나타나는지, hash 값이 같을 때 진짜 match라고 말해도 되는지, FSM state가 shift인지 matched prefix length인지가 모두 함정입니다.
이 chapter의 목표는 '알고리즘 이름을 안다'가 아닙니다. 목표는 칠판 앞에서 String-Matching-Problem을 formal하게 정의하고, NaiveStringMatching의 invariant와 runtime을 말하고, Rabin-Karp가 왜 빠른 후보 검사를 하지만 false hit 검증이 필요한지 설명하고, FSMMatching에서 state st의 의미를 한 문장으로 정확히 말하는 것입니다. KMP는 prefix/failure intuition을 이해하되, 현재 확인한 AUD source window에는 전체 failure table derivation이 드러나지 않으므로 course-grounded claim과 general knowledge를 분리합니다.
- 시험 답안의 첫 문장: text T length n, pattern P length m, finite alphabet Sigma over arrays.
- 시험 답안의 두 번째 문장: output은 all valid shifts L.
- 가장 흔한 오답: match 후 pattern 길이 m만큼 점프해서 overlap을 놓침.
- Rabin-Karp 핵심 오답: hash equality를 real match로 확정함.
- FSM 핵심 오답: state st를 current shift로 착각함.
한 문장 요약: String matching은 모든 \(\text{sft} \text{with} 0 \le \text{sft} \le n-m\quad\text{and}\quad \text{forall} j, T[\text{sft}+j]=P[j]\)sft with 0 ≤ sft ≤ n-m and forall j, T[sft+j]=P[j]를 찾는 문제입니다.
선수 개념과 필수 용어 지도(Prerequisites and vocabulary map)
이 단원을 이해하기 위해 필요한 사전 지식은 많지 않지만, 단어를 정확히 써야 합니다. 첫째, array index입니다. T[sft+j]처럼 시작 위치와 pattern 내부 위치가 더해지는 표기를 자연스럽게 읽어야 합니다. 둘째, finite alphabet(Sigma)입니다. Sheet07은 P와 T의 원소가 유한 alphabet Sigma의 Zeichen(character)라고 둡니다. 셋째, loop invariant(Schleifeninvariante)입니다. NaiveStringMatching의 correctness proof는 이미 처리한 shift들에 대해 L이 정확하다는 invariant로 진행됩니다. 넷째, modulo arithmetic입니다. Rabin-Karp는 큰 숫자를 직접 비교하기 어렵기 때문에 prime q로 나눈 나머지를 사용합니다.
용어 map을 머릿속에 그리면 좋습니다. Text T는 검색 대상입니다. Pattern P 또는 Muster는 찾고 싶은 짧은 배열입니다. Shift sft 또는 Verschiebung은 P[0]을 T의 어디 아래에 놓을지 정하는 시작 위치입니다. Valid shift 또는 gueltige Verschiebung은 그 alignment에서 P 전체가 T window와 같은 경우입니다. Occurrence는 실제 등장입니다. Output list L은 모든 occurrence의 시작 shift를 담습니다. Rabin-Karp의 p는 pattern의 numeric/hash value이고, t(sft)는 sft에서 text window의 value입니다. FSMMatching의 delta는 transition function, st는 state이며, 이 state는 지금까지 읽은 text suffix와 pattern prefix의 match 길이입니다.
- Array index: T[sft+j]를 P[j]와 비교합니다.
- Loop invariant: correctness proof에서 initialization, Fortsetzung, termination을 말합니다.
- Modulo arithmetic: a congruent b mod q는 q로 나눈 나머지가 같다는 뜻입니다.
- Prefix/Suffix: prefix는 앞에서 시작하는 부분, suffix는 뒤에서 끝나는 부분입니다.
- FSM/endlicher Automat: 상태와 transition으로 입력을 한 글자씩 읽습니다.
문제의 엄밀한 정의(Formal problem definition)
Sheet07은 String-Matching-Problem을 명확하게 formuliert합니다. 검색할 text는 length n의 array T이고, pattern은 length m의 array P이며 \(m \le n\)m ≤ n입니다. P와 T의 element는 finite alphabet Sigma의 character입니다. 우리가 찾는 것은 모든 natural number sft입니다. 단, 가능한 sft는 아무 자연수나가 아닙니다. P가 T 안에 완전히 들어가려면 P의 마지막 index m-1이 T의 index n-1을 넘지 않아야 하므로 \(\text{sft} + m - 1 \le n - 1,\)sft + m - 1 ≤ n - 1,즉 \(\text{sft} \le n - m\)sft ≤ n - m입니다. 따라서 \(0 \le \text{sft} \le n-m\)0 ≤ sft ≤ n-m입니다.
이제 valid shift 조건을 씁니다. sft가 valid라는 것은 \(T[\text{sft}, ..., \text{sft}+m-1] = P\)T[sft, ..., sft+m-1] = P라는 뜻이고, element-wise로는 모든 \(0 \le j \le m-1\)0 ≤ j ≤ m-1에 대해 \(T[\text{sft}+j] = P[j]\)T[sft+j] = P[j]라는 뜻입니다. 이 quantifier가 중요합니다. 한두 글자만 맞아서는 valid가 아닙니다. 반대로 모든 j가 맞으면 그 shift는 반드시 L에 들어가야 합니다. 또한 occurrence가 여러 개이면 모두 들어가야 합니다. AUD Sheet07 G2(c)의 예시에서는 \(T=[a,a,b,a,a,a,a,a,b], P=[a,a,b]\)T=[a,a,b,a,a,a,a,a,b], P=[a,a,b]일 때 \(L=[0,6]\)L=[0,6]입니다.
\mathit{sft}\text{가 유효}\Longleftrightarrow\forall j\in\{0,\ldots,m-1\}:T[\mathit{sft}+j]=P[j]정의 문제에서는 'all valid shifts'와 'for all j'를 빠뜨리지 마세요.
NaiveStringMatching: 가장 단순하지만 증명하기 좋은 기준 알고리즘
NaiveStringMatching은 모든 possible shift를 하나씩 검사합니다. Outer loop는 \(\text{sft}=0\)sft=0부터 n-m까지 돕니다. 각 sft에서 inner loop는 \(j=0\)j=0부터 m-1까지 돌며 P[j]와 T[sft+j]를 비교합니다. mismatch가 발견되면 isValid를 false로 두고, 끝까지 mismatch가 없으면 sft를 L에 append합니다. 마지막에 L을 반환합니다. 이 방법은 똑똑하게 건너뛰지 않습니다. 그래서 빠르지는 않지만, 정의를 그대로 구현하므로 correctness proof가 매우 깨끗합니다.
초보자가 여기서 하는 실수는 세 가지입니다. 첫째, shift를 1부터 n까지로 돌립니다. Sheet07의 pseudocode는 0-based이고 0..n-m입니다. 둘째, 한 shift에서 mismatch가 났다고 다음 shift들이 모두 불가능하다고 착각합니다. naive는 각 shift를 독립적으로 검사합니다. 셋째, match를 찾자마자 return합니다. String-Matching-Problem의 output은 모든 valid shifts이므로 끝까지 검사해야 합니다.
- \(\text{Initialize} L=[]\)
Initialize L=[] - \(\text{sft}=0..n-m\)
sft=0..n-m각각에서 \(\text{isValid}=\text{true}\)isValid=true로 시작합니다. - \(j=0..m-1\)
j=0..m-1에 대해 P[j]와 T[sft+j]를 비교합니다. - 불일치가 나오면 invalid로 표시합니다.
- 끝까지 유효하면 sft를 L에 추가합니다.
- 모든 이동량을 처리한 뒤 L을 반환합니다.
Naive는 inefficient하지만 source definition과 거의 같은 구조라 correctness proof의 기준점이 됩니다.
Correctness proof intuition: L은 이미 지나온 shift들에 대해 정확하다
NaiveStringMatching의 correctness는 outer loop invariant로 말합니다. sft번째 outer loop iteration이 시작되기 직전에 L은 모든 \(\text{유효} \text{shifts} t \text{with} t < \text{sft}\)valid shifts t with t < sft를 정확히 포함한다는 invariant입니다. Initialization은 쉽습니다. \(\text{sft}=0\)sft=0전에는 \(t<0\)t<0인 shift가 없으므로 \(L=[]\)L=[]이 맞습니다. Fortsetzung에서는 invariant가 sft 전에는 맞다고 가정합니다. 현재 iteration에서 algorithm은 T[sft..sft+m-1]이 P와 같은지 symbol by symbol로 검사합니다. valid라면 sft를 L에 넣고, valid가 아니라면 넣지 않습니다. 그러면 다음 iteration sft+1 전에는 L이 모든 \(\text{유효} \text{shifts} t < \text{sft}+1\)valid shifts t < sft+1을 담습니다.
Termination에서는 outer loop가 \(\text{sft}=n-m+1\)sft=n-m+1전에 멈춥니다. invariant에 \(\text{sft}=n-m+1\)sft=n-m+1을 대입하면 L은 모든 \(\text{유효} \text{shifts} t < n-m+1\)valid shifts t < n-m+1을 담습니다. 가능한 shift의 최대가 n-m이므로 이것은 가능한 모든 valid shifts입니다. 따라서 L은 정확한 output입니다. 이 proof에서 핵심은 inner loop의 역할도 짧게 설명하는 것입니다. inner loop가 모든 j를 검사하기 때문에 isValid가 true로 남는 것과 window equality가 동치입니다.
\mathit{sft}\text{ 처리 전}: L=\{t\mid 0\le t<\mathit{sft},\ t\text{는 유효}\}구두시험 답안: Initialization은 empty prefix, Fortsetzung은 current sft를 정확히 append하거나 skip, Terminierung은 \(\text{sft}=n-m+1\)sft=n-m+1대입입니다.
Naive runtime: \(O(\text{nm})\)O(nm)를 외우기 전에 loop count를 세기
Sheet07 solution은 runtime을 loop count로 분석합니다. 모든 기본 연산을 constant time으로 보면 outer loop는 n-m+1번 실행됩니다. 각 outer iteration에서 inner loop는 최대 m번 실행됩니다. 그래서 total runtime은 \(O((n-m+1) \cdot m)\)O((n-m+1) * m)입니다. 이것은 흔히 \(O(\text{nm})\)O(nm)으로 완화해서 말할 수 있지만, 시험에서는 가능하면 먼저 정확한 product를 쓰는 것이 좋습니다. 왜냐하면 shift 후보 개수와 pattern 비교 개수를 이해하고 있다는 증거가 되기 때문입니다.
주의할 점은 worst-case와 실제 실행을 구분하는 것입니다. 어떤 shift에서는 첫 글자에서 mismatch가 나서 inner loop가 빨리 끝날 수 있습니다. 그래도 worst-case에서는 많은 shift가 긴 prefix를 공유해서 거의 m개씩 비교할 수 있습니다. 예를 들어 T가 거의 전부 a이고 P도 앞부분이 a로 길게 이어지다가 마지막에서만 다르면, 각 shift에서 많은 비교를 하고 실패합니다. 따라서 'Naive는 항상 정확히 nm번 비교한다'는 말은 틀립니다. 'Worst-case upper bound가 \(O((n-m+1)m)\)O((n-m+1)m)'이라고 말해야 합니다.
\text{바깥 반복}=n-m+1,\quad \text{안쪽 비교}\le m,\quad T\in O((n-m+1)m)MC trap: best/typical early mismatch를 worst-case guarantee로 일반화하지 마세요.
Worked-through overlap: match 후 m칸 점프하면 왜 틀리는가
Overlap은 String matching에서 가장 쉬우면서도 가장 자주 틀리는 함정입니다. \(T=[a,a,a,a,a,a], P=[a,a,a]\)T=[a,a,a,a,a,a], P=[a,a,a]를 보겠습니다. \(n=6, m=3\)n=6, m=3이므로 가능한 shift는 0,1,2,3입니다. \(\text{sft}=0\)sft=0에서는 \(T[0..2]=[a,a,a]\)T[0..2]=[a,a,a]라서 valid입니다. \(\text{sft}=1\)sft=1에서도 \(T[1..3]=[a,a,a]\)T[1..3]=[a,a,a]입니다. \(\text{sft}=2\)sft=2와 \(\text{sft}=3\)sft=3도 마찬가지입니다. 따라서 \(\text{출력} L=[0,1,2,3]\)output L=[0,1,2,3]입니다.
만약 첫 \(\text{match} \text{sft}=0\)match sft=0을 찾은 뒤 \(\text{pattern} \text{length} m=3\)pattern length m=3만큼 jump해서 \(\text{sft}=3\)sft=3으로 간다면 \(\text{sft}=1\)sft=1과 \(\text{sft}=2\)sft=2를 놓칩니다. 이 jump는 non-overlapping occurrence만 찾을 때는 쓸 수 있을지 모르지만, Sheet07의 String-Matching-Problem은 all valid shifts를 요구합니다. 따라서 일반 문제에서는 match 후에도 다음 후보 shift를 조심해야 합니다. KMP나 FSM 같은 빠른 알고리즘도 overlap을 보존하도록 설계되어야 합니다. 빠르게 건너뛰는 것과 valid occurrence를 생략하는 것은 완전히 다른 일입니다.
T=\text{aaaaaa},\quad P=\text{aaa}\quad\Rightarrow\quad L=[0,1,2,3]반례 한 줄: \(T=\text{aaaaaa}, P=\text{aaa}\)T=aaaaaa, P=aaa에서 m칸 jump는 \(\text{sft}=1,2\)sft=1,2를 놓칩니다.
Rabin-Karp motivation: 비교한 정보를 버리지 않기
Sheet07 G3는 NaiveStringMatching이 relatively inefficient한 이유를 설명합니다. 한 shift에서 text에 대해 얻은 정보를 다른 shift 처리에 사용하지 않기 때문입니다. Rabin-Karp는 window를 숫자로 바꾸어 빠르게 비교하는 아이디어를 사용합니다. alphabet Sigma의 크기를 d라고 보고, 각 character를 0..d-1의 digit처럼 identify합니다. Sheet07은 단순화를 위해 \(\text{Sigma}=\{0,...,9\}, d=10\)Sigma={0,...,9}, d=10인 decimal system을 사용합니다.
\(\text{Pattern} P=[2,1,5]\)Pattern P=[2,1,5]라면 \(p=215\)p=215입니다. Text window T[sft..sft+m-1]도 같은 방식으로 t(sft)라는 숫자로 봅니다. 만약 실제 숫자 p와 t(sft)가 다르면 window는 pattern과 다릅니다. 같으면 pattern과 같습니다. 문제는 m이 커지면 p와 t(sft)가 너무 커질 수 있다는 점입니다. 그래서 modulo q를 사용합니다. modulo는 숫자를 작게 유지해 빠른 candidate test를 가능하게 하지만, collision 때문에 equality가 match 확정은 아닙니다.
- Pattern P를 base-d number p로 계산합니다.
- 각 텍스트 구간을 d진수 값 t(sft)로 계산합니다.
- 다음 window는 이전 window에서 왼쪽 digit을 빼고 오른쪽 digit을 넣어 갱신합니다.
- 큰 수 문제는 prime q modulo로 줄입니다.
- Modulo equality는 candidate이며, symbol verification이 필요합니다.
Rabin-Karp preprocessing: Horner schema로 p와 t0 계산
Sheet07 해설은 p를 계산하는 두 표현을 보여줍니다. 직접 쓰면 \(p=P[0]\)p=P[0]·10ᵐ⁻¹+P[1]·10ᵐ⁻²+…+P[m−1]입니다. 하지만 실제 계산에는 호너 방법(Horner-Schema)이 자연스럽습니다. p를 0으로 시작하고, \(i=0\)i=0부터 m−1까지 \(p=10p+P[i]\)p=10p+P[i]로 갱신합니다. 예를 들어 \(P=[2,1,5]\)P=[2,1,5]이면 p=(2·10+1)·\(10+5=215\)10+5=215입니다. 같은 방식으로 \(t_{0}\)t₀도 T[0..m−1]에서 계산합니다.
이 전처리(preprocessing)의 실행 시간은 \(\Theta(m)\)Θ(m)입니다. 반복문이 m번 도므로 상한이 \(\Theta(m)\)Θ(m)이고, p를 알려면 P를 적어도 한 번 읽어야 하므로 하한도 \(\Theta(m)\)Θ(m)입니다. 나머지 연산을 쓰는 버전에서는 각 단계에서 \(p=(10p+P[i]) \bmod q, t_{0}=(10t_{0}+T[i]) \bmod q\)p=(10p+P[i]) mod q, t₀=(10t₀+T[i]) mod q처럼 관리합니다. 시험에서 '전처리는 공짜'라고 생각하면 안 됩니다. 총 실행 시간이나 알고리즘을 비교할 때는 전처리 비용과 검색 비용을 분리해서 말해야 합니다.
p\leftarrow0;\quad i=0,\ldots,m-1\text{에서 }p\leftarrow d\,p+P[i]Preprocessing \(\Theta(m)\)Θ(m), search phase는 별도로 분석합니다.
Rolling update: 한 칸 옮길 때 전체 window를 다시 숫자로 만들지 않는다
Rabin-Karp의 핵심 계산은 t(sft+1)을 t(sft)에서 상수 시간에 얻는 것입니다. 길이 m의 window를 한 칸 오른쪽으로 옮기면, 가운데 m-1개의 문자는 그대로 남고 자릿수만 한 칸 올라갑니다. 빠지는 것은 T[sft]의 높은 자리 기여 T[sft]·dᵐ⁻¹이고, 새로 들어오는 것은 T[sft+m]입니다. 따라서 \(h=d\)h=dᵐ⁻¹을 미리 계산해 두면 \(t(\text{sft}+1)=d(t(\text{sft})-T[\text{sft}]h)+T[\text{sft}+m]\)t(sft+1)=d(t(sft)-T[sft]h)+T[sft+m]입니다.
Sheet07의 decimal 예시에서는 \(d=10\)d=10이고 \(h=10\)h=10ᵐ⁻¹입니다. modulo version에서는 h=(dᵐ⁻¹ mod q)를 쓰며 update 전체를 mod q로 계산합니다. 중요한 직관은 'window 전체 m개를 다시 읽지 않는다'입니다. Naive처럼 매 shift에서 m개를 모두 비교하지 않고, rolling hash로 대부분의 shift를 빠르게 제외합니다. 그러나 candidate가 나오면 여전히 문자별 verification이 필요합니다. 이 검증 비용이 Rabin-Karp의 worst-case 논의에서 중요할 수 있습니다.
t_{\mathit{sft}+1}=\bigl(d(t_{\mathit{sft}}-T[\mathit{sft}]h)+T[\mathit{sft}+m]\bigr)\bmod qrolling update 설명은 'left contribution removed, remaining digits shifted, right symbol added' 순서로 말하면 안전합니다.
False hit: Rabin-Karp의 hash equality는 증거가 아니라 후보입니다
Rabin-Karp에서 가장 중요한 객관식 함정은 p와 t(sft)의 q에 대한 나머지가 같다는 사실을 일치 확정으로 읽는 것입니다. Sheet07은 명시적으로 말합니다. 두 나머지가 다르면 실제 값도 다르므로 일치하지 않습니다. 하지만 두 나머지가 같다고 해서 실제 값까지 같다고 결론낼 수는 없습니다. 서로 다른 두 숫자가 같은 나머지를 가질 수 있기 때문입니다. 이런 경우를 거짓 일치(unechter Treffer, false hit)라고 합니다.
Sheet07의 표는 \(P=[3,1,4], q=13\)P=[3,1,4], q=13예시를 줍니다. \(\text{sft}=2,9,13\)sft=2,9,13은 실제 구간이 [3,1,4]라서 일치합니다. 반면 \(\text{sft}=6\)sft=6과 \(\text{sft}=10\)sft=10은 나머지가 p와 같아 후보가 되지만, 실제 구간은 P와 다르므로 거짓 일치입니다. 따라서 RabinKarpMatch 의사코드는 나머지가 같을 때 곧바로 추가하지 않고 \(b=\text{true}\)b=true로 둔 뒤 \(j=0..m-1\)j=0..m-1문자별 검사를 합니다. 이 검사가 통과할 때만 L에 추가합니다.
\text{해시 후보}+\text{명시적 문자 검증}\Longrightarrow\text{유효 이동량}MC 반박: 같은 나머지는 같은 문자열을 보장하지 않습니다.
FSMMatching: automaton은 shift가 아니라 기억 상태를 들고 간다
Sheet07 후반과 Sheet09 H1은 finite automaton(endlicher Automat)을 이용한 String matching을 다룹니다. FSMMatching의 입력은 text T, transition function delta, pattern length m입니다. Automaton construction은 별도이고, Sheet09 H1(c)는 'without automaton construction' runtime을 묻습니다. Search algorithm은 \(\text{st}=0\)st=0에서 시작해 text를 왼쪽부터 오른쪽으로 한 글자씩 읽습니다. 각 index i 또는 sft에서 \(\text{st}=\text{delta}(\text{st},T[i])\)st=delta(st,T[i])로 state를 갱신합니다. 만약 \(\text{st}=m\)st=m이 되면 방금 pattern 하나가 끝났다는 뜻이므로 i-m+1을 L에 append합니다.
state st의 의미가 핵심입니다. st는 현재 shift가 아닙니다. st는 지금까지 읽은 text prefix가 pattern P[0..st-1]로 끝나고, 그보다 더 긴 prefix로 끝나지는 않는다는 정보를 저장합니다. 다르게 말하면 text read so far의 suffix 중 pattern prefix와 일치하는 가장 긴 길이입니다. 예를 들어 \(\text{st}=3\)st=3이면 방금 읽은 text의 끝 3글자가 pattern의 앞 3글자와 같다는 뜻입니다. 다음 문자를 읽으면 delta가 이 정보를 갱신합니다.
\mathit{st}\leftarrow0;\quad i=0,\ldots,n-1:\quad \mathit{st}\leftarrow\delta(\mathit{st},T[i]);\quad \mathit{st}=m\Rightarrow\text{ }i-m+1\text{ 출력}Sheet09 답안 핵심 문장: st speichert die Länge des laengsten Suffixes des gelesenen Textes, das Praefix von P ist.
FSM example intuition: \(P=[n,a,n,o]\)P=[n,a,n,o]에서 \(L=[5,11]\)L=[5,11]
Sheet07 solution의 FSM example은 \(\text{alphabet} \text{Sigma}=\{a,g,n,o\}, \text{pattern} P=[n,a,n,o], \text{text} T=[g,a,n,a,n,n,a,n,o,n,a,n,a,n,o,g]\)alphabet Sigma={a,g,n,o}, pattern P=[n,a,n,o], text T=[g,a,n,a,n,n,a,n,o,n,a,n,a,n,o,g]를 사용합니다. Automaton은 state 0부터 4까지 있습니다. state 4는 \(\text{pattern} \text{length} m=4\)pattern length m=4이므로 accepting state입니다. Text를 한 글자씩 읽으며 st를 갱신하면 index 8에서 \(\text{st}=4\)st=4가 되어 \(\text{shift} 8-4+1=5\)shift 8-4+1=5를 append합니다. 이후 계속 읽다가 index 14에서 다시 \(\text{st}=4\)st=4가 되어 shift 11을 append합니다. 그래서 \(L=[5,11]\)L=[5,11]입니다.
이 예시가 좋은 이유는 overlap과 fallback state를 보여주기 때문입니다. 중간에 n,a,n까지 맞았다가 다음 글자가 o가 아니면, automaton은 무조건 state 0으로만 가는 것이 아니라, 방금 읽은 suffix가 pattern prefix와 얼마나 맞는지에 따라 적절한 state로 이동합니다. 이 점이 prefix/suffix memory입니다. 하지만 시험에서 delta table 전체를 새로 만들라는 문제와, 이미 주어진 delta로 matching runtime을 묻는 문제를 구분해야 합니다. Sheet09는 H1(c)(i)에서 construction 제외 runtime을 묻기 때문에 \(O(n)\)O(n)이 source-grounded answer입니다.
i=8:\quad 8-4+1=5,\qquad i=14:\quad 14-4+1=11Accepting state 도달 index i에서 output은 i-m+1입니다.
KMP intuition: source-grounded claim과 general knowledge를 분리하기
KMP(Knuth-Morris-Pratt)는 prefix/failure function을 이용해 mismatch 후 어디서 비교를 재개할지 정하는 대표적인 linear-time string matching algorithm입니다. 일반 알고리즘 지식으로는 preprocessing \(O(m)\)O(m), search \(O(n)\)O(n), total \(O(n+m)\)O(n+m)이라고 말할 수 있습니다. Prefix/failure function은 pattern의 각 prefix에 대해 proper prefix이면서 suffix인 가장 긴 길이를 저장해, 이미 맞춘 정보를 버리지 않도록 합니다.
하지만 이 Study Hub에서는 course-specific claim을 조심해야 합니다. 현재 확인한 AUD chunks에서 Sheet07은 Naive와 Rabin-Karp, FSMMatching을 자세히 다루고, Sheet09는 FSMMatching exercise를 다시 묻습니다. 전체 KMP failure table derivation은 확인된 source window에 충분히 나타나지 않습니다. 따라서 이 페이지의 KMP 설명은 '일반 지식'으로 표시합니다. 시험 대비에서는 KMP를 말할 때도 source가 요구하는 notation과 algorithm이 무엇인지 먼저 확인해야 합니다. 만약 문제 statement가 FSMMatching을 주면 KMP table을 끌고 오지 말고 delta/st 의미를 답해야 합니다.
- 일반 지식: KMP는 접두사·실패 함수(prefix/failure function)를 사용합니다.
- 일반 지식: 전처리 \(O(m)\)
O(m), 탐색 \(O(n)\)O(n)입니다. - 현재 자료에서 직접 확인되는 내용: Sheet07의 Naive·Rabin-Karp와 Sheet07·09의 FSMMatching입니다.
- Exam discipline: asked algorithm의 state/preprocessing/runtime을 정확히 분리합니다.
Unsupported course-specific detail은 'general algorithm knowledge'라고 분리해 말합니다.
Naive·Rabin-Karp·FSM·KMP 비교
네 방법을 한 줄씩 비교해 봅시다. Naive는 preprocessing이 거의 없고 모든 shift에서 직접 비교합니다. correctness proof가 쉽지만 worst-case \(O((n-m+1)m)\)O((n-m+1)m)입니다. Rabin-Karp는 pattern과 text window를 numeric/hash value로 바꾸고 rolling update로 \(O(1)\)O(1) 후보 검사를 합니다. modulo q를 쓰면 false hit가 가능하므로 candidate verification이 필요합니다. FSMMatching은 pattern에 대한 transition function delta가 준비되어 있으면 text를 한 번 읽으며 state st를 갱신하고, \(\text{st}=m\)st=m에서 output shift를 냅니다. Search runtime은 construction 제외 \(O(n)\)O(n)입니다. KMP는 일반 지식상 prefix/failure table을 preprocessing하여 search를 \(O(n)\)O(n)에 수행합니다.
시험에서는 '빠르다'보다 조건이 중요합니다. Rabin-Karp는 hash equality가 sufficient condition이 아닙니다. FSM은 delta가 이미 주어졌는지, automaton construction을 포함하는지에 따라 말이 달라질 수 있습니다. KMP는 현재 course source가 요구하는지 확인해야 합니다. Naive는 worst-case가 느리지만 모든 algorithm의 정의와 correctness를 이해하는 baseline입니다.
- Naive: 이동량 사이의 기억 없이 직접 검증하며 \(O((n-m+1)m)\)
O((n-m+1)m)입니다. - Rabin-Karp: 굴림 해시로 후보를 고른 뒤 실제 문자를 검증하며 거짓 일치가 가능합니다.
- FSMMatching: 전이 함수 delta가 주어지면 상태 st를 유지하며 \(O(n)\)
O(n)에 탐색합니다. - KMP: 접두사·실패 함수를 쓰는 일반적인 선형 시간 방법이지만 현재 출처에는 전체 표 유도가 없습니다.
수식 너머의 증명 직관(Proof intuition)
문자열 매칭 알고리즘의 핵심은 다음 위치로 이동할 때 필요한 정보를 얼마나 보존하느냐입니다. 단순 매칭(Naive)은 이동 사이의 정보를 보존하지 않지만 각 위치를 끝까지 비교하므로 증명이 단순합니다. 라빈-카프(Rabin–Karp)는 현재 구간을 압축한 숫자 요약인 해시를 보존합니다. 해시가 다르면 안전하게 탈락시킬 수 있지만, 해시가 같을 때는 실제 문자열 비교가 필요합니다. 유한 상태 기계(FSM)는 지금까지 읽은 텍스트의 접미사 중 패턴의 접두사와 일치하여 앞으로도 의미가 있는 정확한 길이를 상태로 보존합니다. 정확성은 전이 함수 δ가 가장 긴 접미사·접두사 일치를 유지한다는 성질에 기대고 있습니다.
이 관점으로 보면 객관식 함정도 쉽게 정리됩니다. 단순 매칭에서 '첫 일치 뒤 반환'은 문제의 출력 불변식을 깨뜨립니다. 라빈-카프에서 '해시가 같으면 바로 추가'하는 것은 거짓 일치(false hit) 때문에 정확성을 깨뜨립니다. FSM에서 '상태는 이동 위치다'라고 하는 것은 상태 불변식을 잘못 이해한 것입니다. KMP에서 '실패 함수는 다음 출현 위치다'라고 말하는 것도 틀립니다. 실패 함수·접두사 정보는 패턴 내부에서 다시 쓸 수 있는 접두사의 길이를 뜻합니다.
알고리즘마다 '무엇을 기억하는가'를 말하면 proof intuition이 선명해집니다.
시험 답안 틀(Exam answer templates)
정의형 문제에서는 이렇게 답합니다. 'Gegeben ist ein Textarray T der Länge n und ein Musterarray P der Länge \(m \le n\)m ≤ n ueber einem endlichen Alphabet Sigma. Gesucht sind alle Verschiebungen sft mit 0 <= sft <= n-m, sodass für alle 0 <= \(j < m\)j < m gilt \(T[\text{sft}+j]=P[j].\)T[sft+j]=P[j].' 그 다음 output list L을 언급합니다.
Naive correctness 문제에서는 invariant를 바로 씁니다. 'Vor der sft-ten Iteration der aeusseren Schleife enthaelt L genau alle gueltigen Verschiebungen \(t < \text{sft}.\)t < sft.' 그런 다음 initialization, Fortsetzung, termination을 각각 한두 문장으로 처리합니다. Runtime은 outer n-m+1, inner m, total \(O((n-m+1)m)\)O((n-m+1)m)입니다.
Rabin-Karp 문제에서는 p, t(sft), \(h=d\)h=dᵐ⁻¹, q를 말합니다. rolling update를 설명하고, modulo equality는 candidate only라고 강조합니다. FSM 문제에서는 st meaning을 먼저 말합니다. 'st is the length of the longest prefix of P that is a suffix of the text read so far.' \(\text{st}=m\)st=m이면 output i-m+1입니다.
- 정의 답안: T, P, n, m, Sigma, sft 범위, 모든 j에 대한 조건을 말합니다.
- Naive 답안: L 불변식, 초기화·유지·종료, \(O((n-m+1)m)\)
O((n-m+1)m)을 말합니다. - Rabin-Karp 답안: 창의 수치값, 굴림 갱신, mod q, 거짓 일치 검증을 말합니다.
- FSM 답안: st의 의미, delta 갱신, 수용 상태 출력, 구성 제외 \(O(n)\)
O(n)을 말합니다.
Edge cases: 작아 보이는 조건이 답 전체를 바꿉니다
String matching의 edge case는 대부분 index와 output convention에서 나옵니다. Sheet07은 \(m \le n\)m ≤ n을 전제로 설명하므로, 이 chapter도 그 전제를 유지합니다. 만약 다른 문제에서 \(m > n\)m > n이 주어지면 가능한 shift가 없으므로 \(L=[]\)L=[]가 자연스럽지만, 그것은 문제 statement가 허용할 때만 말해야 합니다. \(m=1\)m=1이면 가능한 shift는 0..n-1이고, valid shift는 \(T[\text{sft}]=P[0]\)T[sft]=P[0]인 모든 위치입니다. 이 경우에도 output은 첫 위치 하나가 아니라 모든 위치입니다. P가 모든 문자와 같은 한 글자라면 L은 그 문자가 나타나는 모든 index입니다.
또 다른 edge case는 repeated characters입니다. T가 aaaaa처럼 반복되고 P가 aaa처럼 반복되면 overlap이 많이 생깁니다. 이런 input은 naive의 worst-case 논의와 overlap trap을 동시에 보여 줍니다. 반대로 P의 첫 글자가 T에 거의 없으면 naive는 대부분 첫 비교에서 멈추므로 실제 비교 횟수가 작습니다. 그러나 runtime answer에서는 worst-case guarantee를 말해야 하므로 \(O((n-m+1)m)\)O((n-m+1)m)을 유지합니다. 마지막으로 index base를 조심해야 합니다. Sheet07 pseudocode와 이 page는 0-based shift를 사용합니다. 만약 답안에서 1-based occurrence position을 쓰고 싶다면 처음에 convention을 바꾸었다고 말하고, 모든 output을 일관되게 변환해야 합니다.
- \(m \le n\)
m ≤ n은 Sheet07의 전제입니다. - \(m=1\)
m=1이면 가능한 shift는 0..n-1입니다. - repeated-character input은 overlap과 worst-case를 동시에 만듭니다.
- 0-based와 1-based를 섞으면 정답 shift가 하나씩 밀립니다.
Edge case를 말할 때는 'under the Sheet07 assumption \(m \le n\)m ≤ n'처럼 전제를 붙이면 안전합니다.
Inner loop reasoning: isValid는 무엇을 증명하는가
Outer loop invariant만 외우면 proof가 약해질 수 있습니다. 그 invariant가 유지되는 이유는 inner loop가 current shift를 정확히 판정하기 때문입니다. sft가 고정되어 있다고 합시다. isValid는 처음 true입니다. inner loop가 \(j=0\)j=0부터 m-1까지 진행하면서 mismatch를 하나라도 발견하면 false가 됩니다. 따라서 loop가 끝난 뒤 isValid가 true라는 것은 모든 j에서 mismatch가 없었다는 뜻입니다. 즉 \(\text{forall} j, T[\text{sft}+j]=P[j]\)forall j, T[sft+j]=P[j]입니다. 반대로 window가 pattern과 같다면 어떤 j에서도 mismatch가 없으므로 isValid는 true로 남습니다. 그래서 isValid after inner loop iff current sft is valid입니다.
이 작은 equivalence가 outer proof의 연결 고리입니다. Fortsetzung에서 '현재 sft를 검사한다'라고만 쓰면 부족합니다. 무엇으로 검사하는지, 왜 정확한지 한 문장을 붙여야 합니다. 예를 들어 'Die innere Schleife vergleicht alle m Positionen; daher ist isValid genau dann true, wenn \(T[\text{sft}..\text{sft}+m-1]=P\)T[sft..sft+m-1]=P gilt'라고 쓰면 좋습니다. 시험 채점자는 이 문장을 보고 append/skip이 arbitrary choice가 아니라 valid condition의 정확한 구현임을 확인할 수 있습니다.
\text{안쪽 반복 뒤 }\mathit{isValid}=\mathrm{true}\Longleftrightarrow\forall j\in\{0,\ldots,m-1\}:T[\mathit{sft}+j]=P[j]Correctness proof에서 inner loop equivalence 한 문장을 넣으면 답안이 훨씬 단단해집니다.
Rabin-Karp correctness: 빠른 reject와 느린 accept의 비대칭
Rabin-Karp의 정확성을 직관적으로 설명할 때는 두 방향을 분리해야 합니다. 첫째, p와 t(sft)의 q에 대한 나머지가 다르면 실제 값도 다릅니다. 실제 값이 같다면 어떤 q로 나누어도 나머지도 같아야 하기 때문입니다. 따라서 나머지 불일치는 안전한 불일치 증거입니다. 이때는 문자별 검증을 하지 않아도 됩니다. 둘째, 두 나머지가 같으면 실제 값이 같을 수도 있고 다를 수도 있습니다. 서로 다른 숫자가 같은 나머지를 가질 수 있기 때문입니다. 따라서 나머지 일치는 확정 증거가 아니라 후보 증거입니다.
이 비대칭을 이해하면 RabinKarpMatch 의사코드가 자연스럽습니다. 두 나머지가 같을 때 곧바로 추가하지 않고 \(b=\text{true}\)b=true로 둔 뒤 j 반복문으로 실제 P[j]와 T[sft+j]를 비교합니다. 불일치가 있으면 \(b=\text{false}\)b=false가 되고 추가하지 않습니다. b가 끝까지 true로 남을 때만 L에 추가합니다. 즉 Rabin-Karp의 출력 정확성은 해시 검사만으로 성립하지 않고, 해시 검사와 직접 검증을 합쳐야 성립합니다. 해시는 대부분의 유효하지 않은 이동량을 빠르게 버리기 위한 필터입니다. 블룸 필터의 거짓 양성 직관과도 비슷하지만, 여기서는 후보 뒤에 정확한 검증을 수행한다는 점이 중요합니다.
- 나머지가 다르면 실제 값도 같을 수 없습니다.
- 나머지가 같다고 실제 값까지 같은 것은 아닙니다.
- 따라서 해시가 같으면 바로 추가하지 않고 검증합니다.
- 올바른 출력은 후보를 실제 문자로 확인한 뒤에만 얻습니다.
Rabin-Karp를 'probabilistic guess'처럼 말하지 말고, modulo filter plus exact verification으로 말하세요.
Rabin-Karp runtime nuance: 평균을 말하기 전에 worst-case를 조심하기
Rabin-Karp는 굴림 갱신(rolling update) 덕분에 각 이동량의 해시 후보 검사를 \(O(1)\)O(1)에 수행할 수 있습니다. 전처리로 p, t0, h를 계산하는 데 \(\Theta(m)\)Θ(m)이 들고, n-m+1개의 이동량을 순회하며 굴림 갱신을 수행합니다. 하지만 나머지가 같은 경우가 자주 발생하면 각 후보마다 문자별 검증이 필요합니다. 검증 하나는 최악의 경우 \(O(m)\)O(m)입니다. 따라서 'Rabin-Karp는 항상 \(O(n+m)\)O(n+m)'이라고 말하면 위험합니다. 좋은 해시와 q, 보통 입력에서는 후보가 적어 빠르게 동작하지만, 최악의 경우에는 많은 거짓 일치 또는 실제 후보 때문에 검증 비용이 커질 수 있습니다.
현재 Sheet07의 핵심은 정확한 평균 시간 정리보다 작동 원리입니다. p와 t0를 \(\Theta(m)\)Θ(m)에 계산하고, t(sft+1)을 상수 시간에 굴림 갱신하며, 나머지 충돌 때문에 직접 검증한다는 점입니다. 시험에서 실행 시간의 범위가 모호하면 전처리, 굴림 순회, 후보 검증 비용을 분리해서 쓰는 것이 가장 안전합니다. 즉 전처리 \(\Theta(m)\)Θ(m), 이동량마다 갱신 \(O(1)\)O(1), 후보 하나마다 검증 \(O(m)\)O(m)이라고 쓰면 근거 없는 단정 없이 구조를 정확히 전달할 수 있습니다.
T=\Θ(m)+O(n-m+1)+\text{후보에 대한 문자 검증 비용}Rabin-Karp runtime은 candidate verification을 빼먹지 않는 것이 핵심입니다.
FSM transition intuition: delta는 fallback을 미리 표로 만든 것입니다
FSMMatching에서 delta(st,c)는 현재 matched-prefix length가 st이고 새 character c를 읽었을 때 다음 matched-prefix length를 알려줍니다. 이 transition은 '방금까지 맞았던 정보와 새 문자를 합친 문자열의 suffix 중 pattern prefix와 가장 길게 맞는 것'을 찾는다고 생각하면 됩니다. 예를 들어 pattern이 \(P=[n,a,n,o]\)P=[n,a,n,o]이고 현재 \(\text{st}=3\)st=3이라면 지금까지 text 끝이 [n,a,n]과 맞았다는 뜻입니다. 다음 문자가 o이면 \(\text{st}=4\)st=4가 되어 match가 끝납니다. 다음 문자가 o가 아니라 n이라면 완전히 0으로 떨어질 수도 있고, suffix [n]이 pattern prefix [n]과 맞기 때문에 \(\text{st}=1\)st=1이 될 수도 있습니다. 이런 fallback 정보가 delta에 들어 있습니다.
이 설명은 KMP intuition과 연결되지만, 시험에서는 표현을 조심합니다. FSM problem에서 우리는 delta를 transition function으로 받거나 직접 automaton을 그립니다. KMP problem에서는 failure/prefix table을 사용합니다. 두 방식 모두 prefix/suffix overlap을 재사용한다는 큰 직관은 같지만, 답안에서 요구한 object가 다릅니다. Sheet09가 'endlichen Automaten zeichnen'이라고 하면 states와 labeled transitions를 그려야 하고, 'FSMMatching runtime without automaton construction'이라고 하면 delta가 이미 준비된 search loop의 \(O(n)\)O(n)을 말해야 합니다.
\delta(\mathit{st},c)=\text{ }P[0..\mathit{st}-1]\text{ 뒤에 }c\text{를 붙인 문자열의 접미사이면서 }P\text{의 접두사인 최대 길이}delta는 current alignment 그림이 아니라, reusable suffix/prefix length를 갱신하는 table입니다.
이 장을 능동적으로 공부하는 방법(How to study actively)
이 단원은 눈으로 읽으면 쉬워 보이지만, 손으로 table을 채워야 실력이 생깁니다. 첫 번째 drill은 definition drill입니다. 아무 T와 P를 잡고 n, m, possible shift range, valid shift condition을 말합니다. 두 번째 drill은 naive scan입니다. 작은 T와 P를 만들고 sft별 window, first mismatch 위치, L after row를 표로 적습니다. 세 번째 drill은 proof drill입니다. NaiveStringMatching의 outer invariant를 보지 않고 말하고, initialization, Fortsetzung, termination을 각각 20초 안에 설명합니다.
네 번째 drill은 Rabin-Karp false-hit drill입니다. modulo q가 같아도 실제 window가 다를 수 있음을 숫자 예시로 설명합니다. Sheet07의 \(q=13 \text{table}\)q=13 table을 외우려 하지 말고, 왜 \(\text{sft}=6\)sft=6과 10이 false hit인지 window를 직접 비교합니다. 다섯 번째 drill은 FSM state drill입니다. \(\text{st}=0..m\)st=0..m이 무엇을 뜻하는지 pattern prefix length로 말하고, \(\text{st}=m\)st==m일 때 output이 왜 i-m+1인지 작은 example로 계산합니다. 마지막 drill은 source split입니다. '이 fact는 Sheet07/Sheet09 source-backed인가, 아니면 KMP general knowledge인가?'를 구분합니다. 이 습관이 unsupported course-specific claim을 막아 줍니다.
- 정의 연습: T, P, n, m, Sigma와 sft 범위를 말합니다.
- Naive 표 연습: 각 창, 불일치 위치, 행 처리 뒤의 L을 적습니다.
- 증명 연습: 불변식의 초기화·유지·종료를 말합니다.
- Rabin-Karp 연습: 후보와 거짓 일치를 구분합니다.
- FSM 연습: 상태 의미와 출력 i-m+1을 설명합니다.
- 출처 구분 연습: 강의에서 확인되는 내용과 일반 지식을 나눕니다.
Counterexample bank: MC 문장을 빠르게 반박하는 작은 입력들
Multiple choice에서는 긴 proof보다 작은 반례가 더 빠를 때가 많습니다. 'Occurrence는 겹칠 수 없다'라는 문장은 \(T=\text{aaaaaa}, P=\text{aaa}\)T=aaaaaa, P=aaa하나로 반박됩니다. 'shift range는 0..n-1이다'라는 문장은 \(n=5, m=3\)n=5, m=3일 때 \(\text{sft}=4\)sft=4를 놓아 보면 P[2]가 T[6]을 요구하게 되어 text 밖으로 나간다는 설명으로 반박됩니다. '첫 글자만 맞으면 valid shift다'라는 문장은 \(T=[a,b], P=[a,a]\)T=[a,b], P=[a,a]로 충분합니다. 첫 글자는 맞지만 두 번째에서 mismatch입니다.
Rabin-Karp 문장도 반례 bank를 준비합니다. '같은 modulo value면 같은 string이다'는 일반 정수 반례로도 깨집니다. 예를 들어 \(q=13\)q=13에서 314과 132는 둘 다 나머지 2를 가질 수 있지만 숫자 자체는 다릅니다. Sheet07 table은 이 추상 반례를 실제 string window false hit로 보여 줍니다. FSM 문장에서는 'st is shift'를 반박하려면 st의 범위와 sft의 범위를 비교합니다. st는 0..m 사이를 오가는 state이고, sft 또는 i는 0..n-1까지 증가하는 text index입니다. 같은 종류의 값이 아닙니다.
- No overlap claim 반례: \(T=\text{aaaaaa}, P=\text{aaa}.\)
T=aaaaaa, P=aaa. - Wrong shift range 반례: \(n=5, m=3\)
n=5, m=3에서 \(\text{sft}=4\)sft=4는 text 밖을 참조합니다. - First-character-only claim 반례:\(T=[a\)
T=[a\(b]\)b]\(P=[a\)P=[a\(a].\)a]. - Hash-equality claim 반례: same modulo remainder but different windows.
- st-is-shift claim 반례: st in 0..m, text index in 0..n-1.
반례는 작을수록 좋습니다. 단, course source의 notation과 index convention을 유지하세요.
Self-grading rubric: 답안을 채점자 눈으로 점검하기
String matching 답안을 쓰고 나면 네 줄 rubric으로 스스로 채점합니다. 첫째, definition line이 있는가? T, P, n, m, Sigma, possible shift range, valid shift quantifier가 모두 있으면 좋습니다. 둘째, output convention이 있는가? L이 all valid shifts를 담는다고 말했는지 확인합니다. 셋째, algorithm-specific memory를 정확히 말했는가? Naive는 memory가 거의 없고 L invariant가 중심입니다. Rabin-Karp는 rolling hash value를 기억하지만 equality는 candidate입니다. FSM은 matched-prefix length st를 기억합니다. 넷째, runtime에서 excluded cost를 명시했는가? FSMMatching의 \(O(n)\)O(n)은 automaton construction 제외이고, Rabin-Karp는 candidate verification cost를 분리해야 합니다.
구두시험에서는 이 rubric을 더 짧게 씁니다. 답을 시작할 때 'Ich benutze 0-basierte Shifts'처럼 convention을 고정합니다. 알고리즘 이름을 말한 뒤 바로 invariant 또는 state meaning을 말합니다. Runtime을 말할 때 loop count나 scan count를 붙입니다. 마지막으로 trap을 하나 스스로 예방합니다. 예를 들어 Rabin-Karp 답안 끝에 'Bei gleicher Restklasse pruefe ich die Zeichen explizit, sonst koennte ein unechter Treffer auftreten'라고 붙이면 false-hit trap을 미리 막습니다. 이런 한 문장이 partial answer를 exam-ready answer로 바꿉니다.
- 정의를 빠짐없이 썼는가?
- 출력이 모든 유효 이동량인가?
- 알고리즘이 기억하는 상태를 정확히 설명했는가?
- 실행 시간에 전처리·구성 비용을 포함하는지 명시했는가?
- 대표 함정 하나를 미리 반박했는가?
좋은 답안은 정의, invariant/state, runtime, trap prevention이 모두 들어 있습니다.
출처에 근거한 범위와 자료의 한계(Source boundary)
이 페이지의 course-backed 핵심은 세 부분입니다. 첫째, Übung/AuD26_Sheet07-GrpSol.pdf pages 1-3은 \(\text{formal} \text{String}-\text{Matching}-\text{Problem}, \text{NaiveStringMatching} \text{pseudocode}, \text{invariant} \text{proof}, \text{runtime} O((n-m+1)m), \text{example} L=[0,6]\)formal String-Matching-Problem, NaiveStringMatching pseudocode, invariant proof, runtime O((n-m+1)m), example L=[0,6]을 제공합니다. 둘째, pages 4-6은 Rabin-Karp의 p, t0 계산, rolling update, modulo q, explicit verification, false hits를 제공합니다. 셋째, Sheet07 pages 12-13과 Sheet09 H1은 FSMMatching, state meaning, output shift, runtime without automaton construction을 제공합니다.
Gap도 명확히 남깁니다. 현재 확인한 corpus window에서는 full KMP failure table construction이 course material로 충분히 드러나지 않습니다. 따라서 KMP bounds와 failure-function intuition은 일반 알고리즘 지식으로 둡니다. 또한 FSM automaton construction cost는 Sheet09가 construction 제외 runtime을 묻기 때문에 이 페이지에서 확정하지 않습니다. 나중에 lecture slide나 exercise solution에서 exact construction rule and cost가 추가로 확인되면 이 chapter를 업데이트해야 합니다.
Source gap을 말하는 것은 약점이 아니라, unsupported course-specific claim을 피하는 안전장치입니다.
단계별 풀이 예제 (Worked Examples)
풀이 예제 1: Sheet07 형식의 NaiveStringMatching
문제: \(T=[a,a,b,a,a,a,a,a,b], P=[a,a,b].\)T=[a,a,b,a,a,a,a,a,b], P=[a,a,b].모든 valid shifts L을 구하라.
- \(n=9, m=3\)
n=9, m=3이므로 가능한 shift는 0..6입니다. - sft=0: \(T[0..2]=[a,a,b]=P\)
T[0..2]=[a,a,b]=P이므로 \(\text{append} 0, L=[0].\)append 0, L=[0]. - sft=1: \(T[1..3]=[a,b,a], \text{index} j=1\)
T[1..3]=[a,b,a], index j=1에서 \(b \ne a\)b != a이므로 invalid. - sft=2: \(T[2..4]=[b,a,a],\)
T[2..4]=[b,a,a],첫 문자부터 mismatch입니다. - sft=3: \(T[3..5]=[a,a,a],\)
T[3..5]=[a,a,a],마지막에서 \(a \ne b\)a != b입니다. - sft=4: \(T[4..6]=[a,a,a],\)
T[4..6]=[a,a,a],마지막에서 mismatch입니다. - sft=5: \(T[5..7]=[a,a,a],\)
T[5..7]=[a,a,a],마지막에서 mismatch입니다. - sft=6: \(T[6..8]=[a,a,b]=P\)
T[6..8]=[a,a,b]=P이므로 \(\text{append} 6, L=[0,6].\)append 6, L=[0,6].
\(L=[0,6].\)L=[0,6].이 예시는 Sheet07 G2(c)의 source-grounded result입니다.
풀이 예제 2: \(q=13\)q=13인 Rabin-Karp의 거짓 일치
문제: \(P=[3,1,4], T=[2,1,3,1,4,9,1,3,2,3,1,4,5,3,1,4], q=13\)P=[3,1,4], T=[2,1,3,1,4,9,1,3,2,3,1,4,5,3,1,4], q=13에서 modulo candidate와 true Treffer를 구분하라.
- \(\text{Pattern} \text{numeric} \text{value} p=314\)
Pattern numeric value p=314이고 \(p \bmod 13 = 2\)p mod 13 = 2입니다. - Sheet07 표에 따르면 \(\text{sft}=2,6,9,10,13\)
sft=2,6,9,10,13에서 t(sft)를 13으로 나눈 나머지가 2이므로 후보가 됩니다. - \(\text{sft}=2\)
sft=2의 window [3,1,4]는 P와 같으므로 true Treffer입니다. - \(\text{sft}=6\)
sft=6의 window [1,3,2]는 P와 다르므로 false hit(unechter Treffer)입니다. - \(\text{sft}=9\)
sft=9의 window [3,1,4]는 true Treffer입니다. - \(\text{sft}=10\)
sft=10의 window [1,4,5]는 false hit입니다. - \(\text{sft}=13\)
sft=13의 window [3,1,4]는 true Treffer입니다.
Valid shifts는 [2,9,13]이고, \(\text{sft}=6,10\)sft=6,10은 false hits입니다.
풀이 예제 3: FSMMatching의 출력 이동량
문제: \(P=[n,a,n,o], m=4\)P=[n,a,n,o], m=4인 FSMMatching에서 text index 8과 14에서 \(\text{st}=4\)st=4가 된다. 어떤 shifts를 output하는가?
- FSMMatching은 \(\text{st}=m\)
st==m일 때 현재 index i에서 occurrence가 끝났다고 봅니다. - 시작 shift는 i-m+1입니다.
- \(i=8\)
i=8이면 \(8-4+1=5\)8-4+1=5를 append합니다. - \(i=14\)
i=14이면 \(14-4+1=11\)14-4+1=11을 append합니다. - 따라서 \(L=[5,11]\)
L=[5,11]입니다.
\(L=[5,11].\)L=[5,11].
자주 생기는 오개념 (Common Misconceptions)
String matching은 첫 번째 occurrence만 찾으면 된다.
Sheet07 정의는 모든 gueltige Verschiebungen을 찾는 것입니다. Output은 list L입니다.
가능한 shift는 0부터 n-1까지다.
pattern 전체가 text 안에 들어가야 하므로 \(0 \le \text{sft} \le n-m\)0 ≤ sft ≤ n-m입니다.
첫 문자와 마지막 문자가 맞으면 valid shift다.
모든 \(0 \le j < m\)0 ≤ j < m에 대해 \(T[\text{sft}+j]=P[j]\)T[sft+j]=P[j]가 필요합니다.
NaiveStringMatching은 항상 정확히 nm번 비교한다.
\(O((n-m+1)m)\)O((n-m+1)m)는 worst-case bound입니다. 실제 실행은 early mismatch 때문에 더 적을 수 있습니다.
Rabin-Karp에서 p와 t(sft)의 q에 대한 나머지가 같으면 바로 L에 추가한다.
false hit가 가능하므로 symbol-by-symbol verification 후 append해야 합니다.
FSM state st는 현재 shift다.
st는 지금까지 읽은 text suffix와 pattern prefix가 일치하는 최대 길이입니다.
FSMMatching의 runtime을 말할 때 automaton construction을 항상 포함해야 한다.
Sheet09 H1(c)는 without automaton construction을 묻습니다. 그 경우 search runtime은 \(O(n)\)O(n)입니다.
KMP 세부 표 계산은 이 source에서 확정적으로 다룬다.
현재 확인한 source window에서는 full KMP table derivation이 부족하므로 general knowledge로 분리합니다.
반례로 확인하는 객관식 함정 (MC Traps With Counterexamples)
일치를 찾은 뒤 항상 m칸 이동해도 안전하다.
수정: 거짓입니다. 출현 위치가 서로 겹칠 수 있습니다.
반례/근거: \(T=\text{aaaaaa}, P=\text{aaa}\)T=aaaaaa, P=aaa이면 valid shifts는 [0,1,2,3]입니다. \(m=3\)m=3점프는 1과 2를 놓칩니다.
유효 이동량은 \(T[\text{sft}]=P[0]\)T[sft]=P[0]만 만족하면 된다.
수정: 거짓입니다. 패턴의 모든 위치가 일치해야 합니다.
반례/근거: \(T=[a,b,a], P=[a,a,b]\)T=[a,b,a], P=[a,a,b]에서 첫 글자는 같지만 window는 pattern과 다릅니다.
NaiveStringMatching의 실행 시간은 \(O(n+m)\)O(n+m)이다.
수정: 최악의 경우에는 거짓입니다. n-m+1개의 이동량마다 최대 m개 기호를 비교합니다.
반례/근거: 여러 이동량이 긴 접두사를 공유한 뒤 마지막에 실패하면 \(O((n-m+1)m)\)O((n-m+1)m)이 됩니다.
Rabin-Karp에서 q에 대한 해시가 같으면 문자열도 같음이 증명된다.
수정: 거짓입니다. 해시 일치는 검증할 후보만 만듭니다.
반례/근거: Sheet07의 \(q=13\)q=13예시에는 \(\text{sft}=6\)sft=6과 \(\text{sft}=10\)sft=10에서 거짓 일치가 있습니다.
현재 창의 해시값 t(sft)와 p가 q에 대해 합동이 아니어도 문자 검증이 필요하다.
수정: 거짓입니다. 나머지가 다르면 실제 값의 같음을 안전하게 배제할 수 있습니다.
반례/근거: 두 정수가 같다면 q로 나눈 나머지도 같아야 합니다. 그 대우에 따라 나머지가 다르면 안전하게 거절합니다.
FSMMatching은 텍스트 인덱스 i에서 \(\text{st}=m\)st=m이면 i를 출력한다.
수정: 거짓입니다. i-m+1을 출력합니다.
반례/근거: \(m=4\)m=4이고 \(i=8\)i=8에서 수용 상태가 되면 출현 위치는 8이 아니라 5입니다.
FSM 상태 st는 현재 검사 중인 이동량이다.
수정: 거짓입니다. st는 일치한 접두사의 길이입니다.
반례/근거: Sheet07 FSM 표에서 sft는 0부터 n-1까지 증가하지만 st는 delta에 따라 0..m 사이를 이동합니다.
KMP 실패 함수의 값은 다음에 점프할 텍스트 인덱스다.
수정: 일반적인 설명으로는 거짓입니다. 실패·접두사 정보는 패턴 내부에서 재사용할 수 있는 접두사 길이입니다.
반례/근거: 겹치는 패턴에서는 다음 텍스트 인덱스를 순서대로 읽으면서도 재사용 가능한 접두사 길이가 0보다 클 수 있습니다.
핵심 학습 항목 (Active Recall With Hints)
1
- Q1. String-Matching-Problem을 T, P, n, m, Sigma, sft로 formal하게 정의하세요. 힌트: output이 'first match'인지 'all valid shifts'인지 먼저 말하세요.
2
- Q2. 가능한 shift 범위가 왜 \(0 \le \text{sft} \le n-m\)
0 ≤ sft ≤ n-m인지 유도하세요. 힌트: P의 마지막 index가 T의 마지막 index를 넘지 않아야 합니다.
3
- Q3. valid shift 조건을 quantifier로 쓰세요. 힌트: 모든 \(0 \le j < m\)
0 ≤ j < m에 대해 무엇이 같아야 하나요?
4
- \(Q4. T=[a,a,b,a,a,a,a,a,b], P=[a,a,b]\)
Q4. T=[a,a,b,a,a,a,a,a,b], P=[a,a,b]에서 NaiveStringMatching의 output L을 구하세요. 힌트: 가능한 shift는 0..6입니다.
5
- Q5. NaiveStringMatching의 outer loop invariant를 정확히 말하세요. 힌트: sft번째 반복 전 L에는 어떤 t들이 들어 있나요?
6
- Q6. Naive runtime \(O((n-m+1)m)\)
O((n-m+1)m)을 loop count로 설명하세요. 힌트: outer 후보 수와 inner 비교 수를 분리하세요.
7
- \(Q7. T=\text{aaaaaa}, P=\text{aaa}\)
Q7. T=aaaaaa, P=aaa에서 match 후 m칸 jump가 왜 틀리는지 설명하세요. 힌트: valid shifts list를 직접 써보세요.
8
- Q8. Rabin-Karp에서 p, t(sft), h, q가 각각 무엇인지 말하세요. 힌트: p는 패턴 값, t(sft)는 현재 구간 값입니다.
9
- Q9. Rolling update 식을 말로 설명하세요. 힌트: 빠지는 왼쪽 문자, 자릿수 이동, 들어오는 오른쪽 문자 순서입니다.
10
- Q10. p와 t(sft)의 q에 대한 나머지가 같다는 사실이 왜 일치 확정이 아닌지 설명하세요. 힌트: 거짓 일치(false hit, unechter Treffer)를 사용하세요.
11
- \(Q11. \text{Sheet}07 q=13 \text{Rabin}-\text{Karp}\)
Q11. Sheet07 q=13 Rabin-Karp예시에서 valid shifts와 false hits를 구분하세요. 힌트: valid [2,9,13], false hit [6,10]입니다.
12
- Q12. FSMMatching pseudocode의 핵심 세 줄을 말하세요. 힌트: \(\text{st}=0, \text{st}=\text{delta}(\text{st},T[i]), \text{st}=m\)
st=0, st=delta(st,T[i]), st==m이면 append i-m+1.
13
- Q13. FSM state st가 저장하는 정보를 한 문장으로 설명하세요. 힌트: longest suffix of text read so far that is a prefix of P.
14
- Q14. FSMMatching의 search runtime을 construction 포함/제외 관점에서 조심해 말하세요. 힌트: Sheet09는 without automaton construction을 묻습니다.
15
- Q15. KMP에 대해 이 페이지가 source-grounded로 확정하지 않는 부분은 무엇인가요? 힌트: full failure table derivation입니다.
구두시험 답변 연습 (Oral Exam Scripts)
핵심 학습 항목 (60-second)
String matching은 text array T length n과 \(\text{pattern}/\text{Muster} P \text{length} m \le n\)pattern/Muster P length m ≤ n이 주어졌을 때, P가 T 안에서 시작하는 모든 shift sft를 찾는 문제입니다. 가능한 shift는 \(0 \le \text{sft} \le n-m\)0 ≤ sft ≤ n-m이고, valid shift 조건은 모든 \(0 \le j < m\)0 ≤ j < m에 대해 \(T[\text{sft}+j]=P[j]\)T[sft+j]=P[j]입니다. NaiveStringMatching은 모든 shift와 모든 pattern index를 직접 비교하므로 worst-case \(O((n-m+1)m)\)O((n-m+1)m)입니다. Correctness invariant는 sft번째 outer loop 전 L이 모든 \(\text{유효} \text{shifts} t < \text{sft}\)valid shifts t < sft를 담는다는 것입니다. Rabin-Karp는 window를 hash/numeric value로 보고 rolling update를 쓰지만, modulo equality는 false hit가 가능하므로 문자 검증이 필요합니다. FSMMatching은 delta가 주어졌을 때 state st를 갱신하며 text를 한 번 읽고, \(\text{st}=m\)st==m이면 i-m+1을 output합니다.
핵심 학습 항목 (3-minute)
먼저 formal definition을 말하겠습니다. T[0..n-1]은 text, P[0..m-1]은 pattern이고 둘 다 finite alphabet Sigma 위의 array입니다. Output은 모든 gueltige Verschiebungen sft의 list L입니다. sft는 0부터 n-m까지 가능하고, \(\text{유효}\quad\Longleftrightarrow\quad \text{forall} j, 0 \le j < m, T[\text{sft}+j]=P[j]\)valid iff forall j, 0 ≤ j < m, T[sft+j]=P[j]입니다. Naive는 이 정의를 그대로 구현합니다. \(L=[]\)L=[]로 시작하고, 각 sft에서 \(\text{isValid}=\text{true}\)isValid=true로 둔 뒤 \(j=0..m-1\)j=0..m-1을 비교합니다. mismatch가 있으면 invalid, 끝까지 맞으면 append합니다. Invariant는 outer loop 전 L이 이미 처리한 \(t<\text{sft}\)t<sft중 valid한 shift를 정확히 담는다는 것입니다. 종료 시 \(\text{sft}=n-m+1\)sft=n-m+1이므로 모든 가능한 shift가 처리되었습니다. Runtime은 outer n-m+1, inner 최대 m이므로 \(O((n-m+1)m)\)O((n-m+1)m)입니다.
Rabin-Karp는 숫자화를 통해 후보 검사를 빠르게 합니다. 패턴 값 p와 텍스트 구간 값 t(sft)를 d진수로 계산하고, \(h=d\)h=dᵐ⁻¹을 이용해 \(t(\text{sft}+1)=d(t(\text{sft})-T[\text{sft}]h)+T[\text{sft}+m]\)t(sft+1)=d(t(sft)-T[sft]h)+T[sft+m]로 갱신합니다. q로 나눈 나머지를 쓰면 값이 작아지지만 충돌 때문에 p와 t(sft)의 나머지가 같다는 사실은 후보를 뜻할 뿐입니다. Sheet07 예시처럼 거짓 일치가 있으므로 실제 문자를 다시 확인해야 합니다.
FSMMatching은 transition function delta와 m이 주어진 상태에서 text를 한 글자씩 읽습니다. \(\text{st}=\text{delta}(\text{st},T[i])\)st=delta(st,T[i])로 갱신하고 \(\text{st}=m\)st==m이면 i-m+1을 L에 append합니다. st는 current shift가 아니라 text read so far의 suffix 중 pattern prefix와 일치하는 가장 긴 길이입니다. Construction 제외 search runtime은 \(O(n)\)O(n)입니다.
핵심 학습 항목 (deep-dive)
깊게 보면 세 알고리즘의 차이는 '무엇을 기억하느냐'입니다. Naive는 cross-shift memory가 없습니다. 그래서 각 shift를 완전히 검증하고 correctness invariant도 output list L에 관한 단순한 prefix property입니다. Rabin-Karp는 current window의 compressed numeric/hash \(\text{summary}\)summary를 기억합니다. 이 \(\text{summary}\)summary는 rolling update 덕분에 shift가 하나 증가할 때 \(O(1)\)O(1)에 갱신됩니다. 하지만 modulo q로 압축하면 information loss가 생깁니다. 따라서 non-equality는 안전한 rejection이지만 equality는 안전한 acceptance가 아닙니다. 이 asymmetric logic이 Rabin-Karp proof와 MC trap의 중심입니다.
FSMMatching은 훨씬 구조적인 memory를 갖습니다. State st는 지금까지 읽은 text prefix의 suffix가 pattern prefix와 맞는 최대 길이입니다. Delta는 새 문자 c를 읽었을 때 이 invariant가 유지되도록 다음 st를 줍니다. 그래서 \(\text{accepting} \text{state} \text{st}=m\)accepting state st=m은 방금 읽은 text prefix가 P 전체로 끝난다는 뜻이고, occurrence start는 i-m+1입니다. 이 관점은 KMP의 prefix/failure intuition과도 연결됩니다. mismatch 후에도 이미 맞은 suffix/prefix overlap을 재사용할 수 있기 때문입니다. 다만 현재 AUD corpus에서 full KMP failure table derivation은 확인 부족이므로, course-backed answer에서는 Sheet07 Naive/Rabin-Karp와 Sheet07/09 FSM facts를 중심으로 말하고 KMP는 general knowledge로 분리하겠습니다.
기호와 수식 (Symbols and Formulas)
| 항목 (Symbol / term) | 항목 (Meaning) | 항목 (Exam note) |
|---|---|---|
T |
검색할 text array, 길이 n | T[sft+j]처럼 shift와 pattern index가 합쳐져 접근됩니다. |
P |
찾을 pattern/Muster array, 길이 m | \(m \le n\)m ≤ n전제를 확인합니다. |
Sigma |
유한 알파벳(finite alphabet/endliches Alphabet) | Rabin-Karp에서는 alphabet을 digits/base d로 identify합니다. |
sft |
shift/Verschiebung, P[0]을 T[sft]에 맞추는 시작 위치 | \(0 \le \text{sft} \le n-m\)0 ≤ sft ≤ n-m입니다. |
L |
모든 valid shifts를 담는 output list | 첫 match 하나가 아니라 모든 occurrence입니다. |
p |
Rabin-Karp에서 pattern의 numeric/hash value | mod q로 관리할 수 있습니다. |
t(sft) |
sft 위치 text window의 numeric/hash value | rolling update로 다음 window를 계산합니다. |
q |
Rabin-Karp의 나머지 계산용 소수(modulo prime) | collision 때문에 false hit가 가능합니다. |
delta |
유한 상태 기계의 전이 함수(FSM transition function) | delta가 주어진 search와 automaton construction을 구분합니다. |
st |
FSM 상태, 일치한 접두사의 길이(matched-prefix length) | current shift가 아닙니다. |
출처에 근거한 설명 (Source-Grounded Notes)
- Übung\AuD26_Sheet07-GrpSol.pdf 1~3쪽: T, P, n, m, Sigma를 사용한 문자열 매칭 정의, 유효 이동량, NaiveStringMatching 의사코드, 정확성 불변식, 실행 시간 \(O((n-m+1)m), L=[0,6]\)
O((n-m+1)m), L=[0,6]예시. - Übung\AuD26_Sheet07-GrpSol.pdf 4~6쪽: Rabin-Karp의 수치값 p와 현재 창의 값, Horner 방식 Compute(P), 굴림 갱신, mod q, 명시적 검증, \(q=13\)
q=13의 거짓 일치. - Übung\AuD26_Sheet07-GrpSol.pdf 12~13쪽: FSMMatching 의사코드, 상태 갱신 \(\text{st}=\text{delta}(\text{st},T[\text{sft}]),\)
st=delta(st,T[sft]),수용 상태의 출력 \(\text{sft}-m+1, P=[n,a,n,o]\)sft-m+1, P=[n,a,n,o]와 \(L=[5,11]\)L=[5,11]예시. - Übung\AuD26_Sheet09.pdf 7~8쪽과 Übung\AuD26_Sheet09-GrpSol.pdf: Sheet09 H1의 \(P=[\text{lambda},\text{delta},\text{lambda},\text{sigma}] \text{FSM},\)
P=[lambda,delta,lambda,sigma] FSM,전체 표 실행, 오토마톤 구성 제외 실행 시간, 현재 상태가 저장하는 정보. - 자료 범위 한계: 확인한 AUD 조각에는 KMP 실패 함수 표의 전체 유도가 없으므로 구현 세부 사항은 강의 고유 주장 대신 일반 지식으로 표시합니다.
- 자료 범위 한계: Sheet09가 오토마톤 구성 제외 실행 시간을 명시하므로 이 페이지에서는 FSM 오토마톤 구성 비용을 단정하지 않습니다.
AI 후속 학습 프롬프트
관련 개념
다음 튜터 프롬프트
마지막 생성: 2026-08-03 03:24