語義
在一個很長的字符串 T 中,查找是否存在子字符串 P。例如搜索引擎收錄的大量網站數據,當用戶輸入關鍵字後,就會在這些數據中進行匹配,並返回合適的網站。
語義:假定字符串長度爲 j,則所有字符串都在 [0, j)
這樣的集合中。
返回首次匹配的字符的位置。注意這裏調用方需要判斷位置是否正確,例如對於長度爲 i 的字符串,要查找是否有長度爲 j 的字符串,如果返回值在 [0, i - j)
則爲正確匹配到數據,否則就是失敗。
暴力匹配 brute force
每次用 T 的一個字符匹配 pattern 的所有字符,全部匹配成功則返回首字符下標,否則 T 前進一個字符,繼續匹配:
#include <stdio.h>
#include <string.h>
int match(char *str, char *pattern) {
int i = 0, j = 0;
int sLen = strlen(str);
int pLen = strlen(pattern);
if (sLen < 1 || pLen < 1) {
return -1;
}
while (i < sLen && j < pLen) {
if (str[i] == pattern[j]) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
return i - j;
}
int main(void) {
char *str = "sfeiwojdsljfldshgew";
printf("str length is:%ld\n", strlen(str));
char *pattern[] = {
"",
"a",
"abc",
"woj",
"wojdsljflda"
};
int i, ret;
int len = sizeof(pattern) / sizeof(char *);
for (i = 0; i < len; i++) {
ret = match(str, pattern[i]);
printf("ret is:%4d, correct range is:%ld, raw is:%s\n", ret, strlen(str) - strlen(pattern[i]), pattern[i]);
}
return 0;
}
KMP
最長公共前後綴
這裏通常取的是真前綴(不包含最後一個字符)和真後綴(不包含第一個字符)。長度相同的前後綴就是公共前後綴。
例如:
a
: ``,最長公共前後綴長度爲 0aa
:a
,最長公共前後綴長度爲 1ab
: ``,最長公共前後綴長度爲 0aaa
:aa
,最長公共前後綴長度爲 2abab
:ab
,最長公共前後綴長度爲 2
前綴表
任意的字符串 P,對其每個字符前面的子字符串找最長公共前後綴,得到的這個數組就是前綴表。
例如,對於字符串 ababc
:
a
: ``,真前綴爲空字符串,其最長公共前後綴長度記爲 -1ab
: ``,真前綴爲a
,其最長公共前後綴長度爲 0aba
:a
,真前綴爲ab
,其最長公共前後綴長度爲 0(最長前綴a
和最長後綴a
不匹配)abab
:ab
,真前綴爲aba
,其最長公共前後綴長度爲 1ababc
: ``,真前綴爲abab
,其最長公共前後綴長度爲 2
最終的前綴表數組:[-1, 0, 0, 1, 2]
KMP 算法
package main
import "fmt"
func kmp(target string, pattern string) []int {
var ret []int
length := len(pattern)
prefixTable := buildPrefixTable(pattern)
i := 0
k := 0
for {
if i + length > len(target) {
break
}
if target[i + k] == pattern[k] {
k += 1
if k == length {
ret = append(ret, i)
k = 0
i += 1
continue
}
} else {
diff := k - prefixTable[k]
i += diff
if k > 0 {
k -= diff
}
}
}
return ret
}
func buildPrefixTable(str string) []int {
ret := []int{-1}
for i := range str {
if i == 0 {
continue
}
pre := str[:i]
j := len(pre) - 1
for j > 0 {
if pre[:j] == pre[len(pre) - j:] {
ret = append(ret, j)
break
}
j = j - 1
}
if len(ret) < len(pre) + 1 {
ret = append(ret, 0)
}
}
return ret
}
func main() {
ret := kmp("abacaca", "aca")
fmt.Println(ret)
}