字符串匹配
暴力匹配算法
暴力匹配算法的时间复杂度是O(n*m),其中n是目标字符串的长度,m是要查找的字符串的长度
1 | // 字符串定义 |
1 | /** |
KMP算法
KMP算法的时间复杂度是O(n+m),其中n是目标字符串的长度,m是要查找的字符串的长度,KMP算法的核心是next数组
1 | int match_find(String s, String t, const int next[]) { |
next数组
next数组手算
next数组的计算过程可以看作模式串与自身的匹配过程
例1
1 | a b a b a |
在如上字符串的next数组中:
第一位元素为-1。第一位元素之前没有字符串,无法取前后缀
1 | ↓ |
第二位元素为0。第二位元素之前的字符串,前后缀皆为空集,没有相同前后缀
1 | ↓ |
第三位元素为0。第三位元素之前字符串的前缀为{a}
,后缀为{b}
,没有相同前后缀,当第三位元素匹配失败时,从字符串第一位开始匹配(下标0)
1 | ↓ |
第四位元素为1。第四位元素之前字符串的前缀为{a, ab}
,后缀为{a, ba}
,最长相同前后缀为a
,当第四位元素匹配失败时,从字符串第二位开始匹配(下标1)
1 | ↓ |
第五位元素为2。第五位元素之前字符串的前缀为{a, ab, aba}
,后缀为{b, ab, bab}
,最长相同前后缀为ab
,当第五位元素匹配失败时,从字符串第三位开始匹配(下标2)
1 | ↓ |
其实就是在某个字符之前的所有字符串里,找开头和结尾相同的最长字符串的长度,这个长度可以作为当前字符的next值,当匹配失败时,就可以根据next值来调整指针位置,从而减少匹配次数
例2
1 | a b a a b c |
在如上字符串的next数组中:
第一位元素为-1。第一位元素之前没有字符串,无法取前后缀
第二位元素为0。第二位元素之前的字符串,前后缀皆为空集,没有相同前后缀
第三位元素为0。第三位元素之前字符串的前缀为{a}
,后缀为{b}
,没有相同前后缀,从字符串第一位开始匹配(下标0)
第四位元素为1。第四位元素之前字符串的前缀为{a, ab}
,后缀为{a, ba}
,最长相同前后缀为a
,从字符串第二位开始匹配(下标1)
第五位元素为1。第五位元素之前字符串的前缀为{a, ab, aba}
,后缀为{a, aa, baa}
,最长相同前后缀为a
,从字符串第二位开始匹配(下标1)
第六位元素为2。第六位元素之前字符串的前缀为{a, ab, aba, abaa}
,后缀为{b, ab, aab, baab}
,最长相同前后缀为ab
,从字符串第三位开始匹配(下标2)
1 | a b a a b c |
例3
1 | a b c a b a b |
在如上字符串的next数组中:
第一位元素为-1。第一位元素之前没有字符串,无法取前后缀
第二位元素为0。第二位元素之前的字符串,前后缀皆为空集,没有相同前后缀
第三位元素为0。第三位元素之前字符串的前缀为{a}
,后缀为{b}
,没有相同前后缀,从字符串第一位开始匹配(下标0)
第四位元素为0。第四位元素之前字符串的前缀为{a, ab}
,后缀为{c, bc}
,没有相同前后缀,从字符串第一位开始匹配(下标0)
第五位元素为1。第五位元素之前字符串的前缀为{a, ab, abc}
,后缀为{a, ca, bca}
,最长相同前后缀为a
,从字符串第二位开始匹配(下标1)
第六位元素为2。第六位元素之前字符串的前缀为{a, ab, abc, abca}
,后缀为{b, ab, cab, bcab}
,最长相同前后缀为ab
,从字符串第三位开始匹配(下标2)
第七位元素为1。第七位元素之前字符串的前缀为{a, ab, abc, abca, abcab}
,后缀为{a, ba, aba, caba, bcaba}
,最长相同前后缀为a
,从字符串第二位开始匹配(下标1)
1 | a b c a b a b |
next数组计算
next数组的计算过程可以看作模式串与自身的匹配过程,计算next数组的时间复杂度是O(m),其中m是模式串的长度
1 | void get_next(String str, int next[]) { |
nextval数组
nextval是对next数组的优化
1 | void get_next(String str, int next[]) { |