对于字符串的匹配问题,即在字符串原串中找到子串第一次出现在原串的下标,第一个方案是遍历字符串原串,在每次原串字符偏移时,遍历子串,如果子串可以遍历完成则代表已经找到了子串第一次在原串中出现的位置,返回此时原串遍历指针的下标。
int strSearch(const char *src, const char *sub) {
int srclen = strlen(src);
int sublen = strlen(sub);
int i;
int j;
for(i = 0; srclen - i >= sublen; i++) {
for (j = 0; src[i + j] == sub[j]; ++j) {
}
if (sub[j] == 0) {
return i;
}
}
return NOT_FOUND;
}
虽然这个方法可以很容易找到与子串匹配的原串下标,但是它的时间复杂度太高了,对原串进行遍历需要 n-m 次循环,n为原串中字符个数,m为遍历子串字符个数。而遍历子串需要m次循环,所以整个算法的时间复杂度为O(m*(n-m)),近似与O(n^2)。时间复杂度较高,于是抛弃这种算法,选择KMP算法来实现串匹配。
KMP算法中最核心的部分之一便是next函数,关于next函数,它是为了在遍历字符串子串时可以选择性跳过一些重复判断的字符,极大的减小了算法的时间复杂度。形成一个next数组,数组中存放跳过的数字。
例: a a b a a b a a
0 0 1 0 1 2 3 4
由于字符的特点,前两个字符必定为0,在遍历子串时需要从第三个字符进行遍历
a a b a a b a a
0 0 1 如果在第三位失配时,前面仅有a一个相同。则next[2] = 1;
a a b a a b a a
0 0 1 0 如果在第四位失配时,前面没有相同的。则next[3] = 0;
a a b a a b a a
0 0 1 0 1 2 3 如果在第七位失配时,前面有aab 相同,则next[5] = 3;
第一次比较时比较的第一个字符,如果相同则第二次只用比较第二个字符,即比较时用子串中比较未相同或没有进行比较过的sub[ j ] 与 失配前一个字符即sub[i - 1];
sub[ j ] == sub[i -1]!
推导:
第一次:
i = 2
a a b a a b a a 第三位失配,则比较i = 1的字符与j = 0的字符 ,
0 0 相同 : j++; next[2] = 1; i++;
j = 0
第二次:
i = 3
a a b a a b a a 第四位失配,则比较i = 2的字符与j = 1的字符,
0 0 1 不相同:则需要与上一次所比较的字符来比较 即b 与第一个 a 比较;
j = 1 即比较i = 2的字符与j = 0的字符;
不相同:且 j = 0;无法再比较,next[3] = 0; i++;
第三次:
i = 4
a a b a a b a a 第五位失配,则比较i = 3的字符与j = 0的字符,
0 0 1 0 相同 : j++; next[4] = 1; i++;
j = 0
第四次:
i = 5
a a b a a b a 第六位失配,则比较i = 2的字符与j = 1的字符,
0 0 1 0 1 相同 : j++; next[5] = 2; i++;
j = 2
第五次:
i = 6
a a b a a b a 第七位失配,则比较i = 2的字符与j = 1的字符,
0 0 1 0 1 2 相同 : j++; next[6] = 3; i++;
j = 3
由上述推导过程可以得到以下代码实现:
while (sub[i]) {
if (sub[i-1] == sub[j]) {
next[i++] = ++j;
} else if (j != 0) {
j = next[j];
} else {
next[i++] = j;
}
}
至此,next函数实现就已经完成了。