最近在学习这 K M P KMP KMP时,总感觉 n e x t next next数组的建立有些稀里糊涂,然后自己就看了一堆视频,最后发现 K M P KMP KMP每个人的写法不尽相同,更是把我绕晕了。故写这篇文章就是为了总结一下高手们的代码
n e x t next next数组的用途
next数组的建立主要是 K M P KMP KMP算法在匹配失败的时候,回去看最后一个匹配的字符它所对应的next数值—代表子串中我们能跳过”重新匹配“的字符个数
由于next数组的这里特性,就不再需要回退在主串上的指针,只需要一次主串的遍历就可以完成匹配,效率自然比之前的暴力算法高很多
n e x t next next数组的理解
next数组其实理解起来很简单就是字符串的最长公共前后缀的长度,比如 a b a b abab abab和 a b b a abba abba的最长公共前后缀的长度分别是2和1。但是为什么代码看起来很抽象,主要是因为 K n u t h Knuth Knuth, M o r r i s Morris Morris和 P r a t t Pratt Pratt三位大佬在实现 n e x t next next数组时使用了递归思想(里面‘回退’)
n e x t next next数组建立的总思想
如果下一字符依然相同的话,代表可以构成一个更长的前后缀,长度+1即可
如果下一字符不同的话,就寻找该字符串的前缀的最长公共前缀
- 这一部分不懂的话可以参考链接: KMP算法实现全过程
- 好了,接下来就是重点部分
n e x t next next数组代码理解
int main() {
string str, str1;
int kmp[100] = {0}, next[100] = {0};
while (cin >> str) {
int n = str.length();
int i = 1;
int k = 0;
while ( i <= n - 1) {
if ( str[i] == str[k]) {
kmp[i] = ++k;
i++;
} else {
if ( k == 0) {
kmp[i] = 0;
i++;
} else {
k = kmp[k - 1 ];
}
}
}
for ( int i = 0 ; i <= n - 1 ; i++ ) {
cout << kmp[i] << " ";
}
}
}
这里的k就是代表当前字符串的长度
主要难点就是
k = kmp[k - 1 ];
很多人感到很怪异的地方就是kmp[k - 1 ]
?
k代表的是长度为什么放在kmp的下标中呢?长度怎么可以当做下标?
以‘ababc‘为例:
首先
a
b
a
b
abab
abab的长度是
2
2
2,会发现
2
−
1
2-1
2−1就是前缀‘
a
b
ab
ab’的
b
b
b所在的下标,所以
k
k
k代表的长度,
k
−
1
k-1
k−1代表的是最长前后缀中前缀最后一个元素的下标,然后再
根据
如果下一字符不同的话,就寻找该字符串的前缀的最长公共前缀
那么就自然的写出来
k = next[k-1]
还有人疑惑
k == 0
其实
k
=
n
e
x
t
[
i
]
k=next[i]
k=next[i]在str[i]!=str[k]
的情况下死循环,然而根据我们所讲单独一个字符是没有前后缀的,所以停止循环的条件,所以到第一个字符的时候要让
t
a
ta
ta停止
还有一种写法就是当k从1开始算起,其实原理是一样的:
void get_next(string T, int *next) {
int i, k;
i = 1;
k = 0;
next[100] = {0};
while ( i < T[0]) //T[0]存放长度
{
if ( k == 0 || T[i] == T[k]) {
++i;
++k;
next[i] = k;
} else {
k = next[k];
}
}
}
大家可以想一下为什么这里是
k = next[k]
记得刚在所说的长度和下标转换吗?
当时由于下标是从
0
0
0算起,所以最长公共长度为
1
1
1时,下标是
0
0
0
但是这里下标是从
1
1
1算起,所以最长公共长度为
1
1
1时,下标是
1
1
1