语义
在一个很长的字符串 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)
}