한 구체
요구 사항 : 화이트 보드 코드 작성은 일반적으로 필요하지 않습니다.
1. 폭력적인 솔루션 ( O (MN) O (MN)O ( M N ))
// Java
public static int forceSearch(String txt, String pat) {
int M = txt.length();
int N = pat.length();
for (int i = 0; i <= M - N; i++) {
int j;
for (j = 0; j < N; j++) {
if (txt.charAt(i + j) != pat.charAt(j))
break;
}
if (j == N) {
return i;
}
// 更加聪明?
// 1. 预先判断 hash(txt.substring(i, M)) == hash(pat)
// 2. KMP
}
return -1;
}
2. Rabin-Karp 알고리즘
// Java
public final static int D = 256;
public final static int Q = 9997;
static int RabinKarpSerach(String txt, String pat) {
int M = pat.length();
int N = txt.length();
int i, j;
int patHash = 0, txtHash = 0;
for (i = 0; i < M; i++) {
patHash = (D * patHash + pat.charAt(i)) % Q;
txtHash = (D * txtHash + txt.charAt(i)) % Q;
}
int highestPow = 1; // pow(256, M-1)
for (i = 0; i < M - 1; i++)
highestPow = (highestPow * D) % Q;
for (i = 0; i <= N - M; i++) {
// 枚举起点
if (patHash == txtHash) {
for (j = 0; j < M; j++) {
if (txt.charAt(i + j) != pat.charAt(j))
break;
}
if (j == M)
return i;
}
if (i < N - M) {
txtHash = (D * (txtHash - txt.charAt(i) * highestPow) + txt.charAt(i + M)) % Q;
if (txtHash < 0)
txtHash += Q;
}
}
return -1;
}
3KMP
자세한 내용은 1, 2, 3의 동영상 및 기사를 참조하십시오.
3.1, 핵심 개념
-
접두사 : 마지막 문자를 제외하고 문자열의 모든 머리 조합;
-
접미사 : 첫 번째 문자를 제외하고 문자열의 모든 꼬리 조합.
예 : 문자열 S = "ABCDAB", 접두사는 [A, AB, ABC, ABCD, ABCDA], 접미사는 [BCDAB, CDAB, DAB, AB, B], 총 요소는 "AB", 길이는 2입니다.
3.2, KMP 처리 프로세스
1 : 접두사 표
문자열의 경우 모든 문자열의 접두사와 접미사 중 가장 긴 공통 요소의 길이를 찾습니다.
계산 된 공통 길이를 한 위치 뒤로 이동하면 첫 번째 위치는 -1입니다.
2. 문자열 매칭
1 단계 : i = 0, j = 0에서 시작하여 하나씩 같은지 비교합니다.
2 단계 : 부등식이 발생하면 먼저 접두사 테이블 아래의 해당 번호를 확인한 다음 포인터를 패턴 문자열의 해당 아래 첨자로 이동합니다. 등가 효과 패턴 문자열은 전체적으로 뒤로 이동합니다.
Step3 : 이동 후 효과는 아래와 같습니다.
암호:
// C V2.0
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void prefix_table(char pattern[], int prefix[], int n) {
prefix[0] = 0;
int len = 0;
int i = 1;
while (i<n) {
if ( pattern[i]==pattern[len] ) {
// ABA --> ABAB, 最长公共前缀长度由1 --> 2
len++;
prefix[i] = len;
i++;
}
else {
if (len > 0) {
// 防止越界,
// ABABCABA --> ABABCABAA。关键步骤,补充理解见下。
len = prefix[len-1];
}
else {
// pattern[i]!=pattern[len] && len==0, 防止死循环 A --> AB, 0 --> -1, 此时i = 0
prefix[i] = len;
i++;
}
}
}
}
void move_prefix_table(int prefix[], int n) {
int i;
for (i=n-1; i>0; i--) {
prefix[i] = prefix[i-1];
}
prefix[0] = -1;
}
void kmp_search(char text[], char pattern[]) {
int n = strlen(pattern);
int m = strlen(text);
int* prefix = malloc(sizeof(int) * n);
prefix_table(pattern, prefix, n);
move_prefix_table(prefix, n);
// text[i] , len(text) = m
// pattern[i] , len(pattern) = n
int i = 0;
int j = 0;
while (i < m) {
if (j==n-1 && text[i] == pattern[j]) {
printf("Found pattern at %d\n", i-j);
j = prefix[j];
}
if (text[i]==pattern[j]) {
i++; j++;
}
else {
j = prefix[j]; // 关键过程可见上图
if (j == -1) {
i++; j++;
}
}
}
}
/**/
int main() {
/**/
char pattern[] = "ABABCABAA";
char text[] = "ABABABCABAABABABAB";
kmp_search(text, pattern);
/*
char pattern[] = "ABABCABAA";
int prefix[9];
int n = 9;
prefix_table(pattern, prefix, n);
move_prefix_table(prefix, n);
int i;
for (i=0; i<n; i++) {
printf("%d\n", prefix[i]);
}
*/
return 0;
}
시간 복잡도 : O (m + n), {aaaaaaaab, ab}와 같이 최악의 경우 O (mn)로 저하 될 수
있습니다. 공간 복잡도 : O (1)
- 보충 이해 : 다음 어레이를 빠르게 구축하는 방법
요점 : 패턴 문자열은 자체적으로 일치합니다.
next [i]의 정의 : P [0] ~ P [i] 문자열의이 섹션에서 접두사와 접미사의 최대 공통 문자열의 길이, 즉 k 접미사와 동일한 k 접두사의 최대 k.
이해해야 할 주요 단계 : len = prefix [len-1], 왜? (분석은 참조 7에서 제공됨)
목표 : 이제 index = 12, 다음 배열의 마지막 숫자의 k 값, 즉 next [last] =?
- 알려진 정보 :
- 이제 문자열 A는 문자열 B와 동일합니다. next [last-1] = String_A.length = String_B.length = 5입니다.
- 단락의 전반부에서 next [now-1] = {a, b} .length = 2, 2는 하위 문자열 A의 접두사와 접미사의 가장 큰 공통 문자열의 길이입니다.
- 분석:
- P [now] == P [x]이면 바로 앞 자리에 +1을 더하면 5 + 1 = 6이됩니다.
- 하지만 P [지금]! = P [x], 다음 [마지막] = 다음 [지금] = 0? 당연히 아닙니다.
- Find next [last] =? P [0] ~ P [x-1]의 공통 접두사와 접미사를 알아야합니다.
String A = string B, P [now]! = P [x], 즉 접두어의 가장 큰 공통 문자를 이미 알고 있고 접미사 문자열 A 또는 B가 충족되지 않습니다. 공통 문자열을 줄여야하며 접두사와 접미사를 더 줄여야합니다.
분석을 계속하면 다음 반복의 접두사 (축약 됨)는 문자열 A에 속해야하고 접미사는 문자열 B에 속해야합니다. String A == String B이므로 문자열 A의 접두사와 접미사의 최대 공통 길이, 즉 next [now-1]을 찾습니다.
이제 점프, 특정 값 : now = next [now-1], 다음 비교 p [now] == p [x]? 같으면 다음 [마지막] = 지금 +1, 그렇지 않으면 지금 = 다음 [지금 -1], 루프를 계속합니다.
둘째, 참조
1. KMP 문자열 매칭 알고리즘 1
2. KMP 문자열 매칭 알고리즘 2
3. 문자열 매칭을 위한 KMP 알고리즘
4, 문자열 매칭을 위한 KMP , BoyerMoore, 일요일 알고리즘
5, 문자열 매칭 무차별 대입 코드 예제
6, Rabin-Karp 코드 샘플
7. KMP 알고리즘을 더 잘 이해하고 마스터하는 방법은 무엇입니까?