KMP算法之前看过一遍代码随想录,第一遍看的时候,勉强弄明白了KMP算法的基本原理。第二遍看之前已经忘记了(2个月之前看的)。
第二遍看的时候,捡起来很快。算法原理的理解上没有花太多时间,具体的实现细节还是会有想不明白的地方。
今天先记录一下KMP算法的具体实现的细节。
首先是next[]数组,也就是前缀表。需要先把前缀表求出来。前缀表不减1的步骤如下:
1.初始化。
int j = 0;
next[0] = 0;
j为前缀末尾,
next[0]=0 对于前缀表不减1的实现方式来说就等于next[0]=0。
2.当前缀和后缀不相等的时候,j进行退回的操作。
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j > 0 && s[i] != s[j]) { // 前后缀不相同了
j = next[j-1]; // 向前回退
}
while的条件里面,首先j>=0,是因为当j=0的时候说明已经是退回到底了,不可以再退回了。所以只有当j>0才可能退回。
其次就是s[i] != s[j]这个基本的退回条件。当前的前缀和后缀不相等时。
退回的操作是j = next[j-1]。j退回到j的前一个next数组值所指向的位置。因为next数组记录着j(包括j)之前的子串的相同前后缀的长度。(这里比较难想明白,先记住吧。)
3.当前缀和后缀相等的时候,j自增
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
回退和自增结束之后,此时i对应的next数组的值next[i] = j;
整体的求next数组的代码如下:
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) {
j = next[j - 1];
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
其中有个关于C++语法的问题是,这个函数的形参是const string& s,为什么不能是string s呢?
搜索到如下回答:
区别在于:
const 限定变量只读,
string& 的目的是引用,避免再复制一个std::string
所以说,const string s 需要一次复制操作,比较浪费,既然已经是只读了就直接用引用多好。在讲引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。
主要是引用传递实际参数本身,不用拷贝,节约时间和空间。
关于strStr()函数本身,在haystack字符串中找到needel字符串的方法的原理和实现跟求next()数组的过程是一样的。当发生字符不匹配的时候,退回到j前一个next()数组值所指向的位置,如果匹配则j++。直到j等于needle的末尾。
函数如下所示:
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
int next[needle.size()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.size(); i++) {
while(j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == needle.size() ) {
return (i - needle.size() + 1);
}
}
return -1;
}