KMP 大问题分割成小问题 分步理解

KMP

分解成小问题:

  1. 求出next数组
    • 求字符串前缀与后缀的公共子串长度
  2. 当pattern与text不匹配时,按照next数组的指示进行跳转

求字符串前缀与后缀的公共子串长度

字符串 absdc:

前缀:a, ab, abs, absd, absdc

真前缀:a, ab, abs, absd

后缀:c, dc, sdc, bsdc, absdc

真后缀:c, dc, sdc, bsdc

用于next数组的应该是真前缀与真后缀的公共子串最大长度。

int tmp = 0, res = 0;
int len = s.length();
int j = 0;
for(int i = 1; i < len; i++){
    if(s[i] == s[j]){
        tmp ++;j++;
        res = tmp > res ? tmp : res;
    }
    else if(s[i] != s[j]){
        j = 0;
        tmp = 0;
        if(s[i] == s[j]){
            tmp ++;j++;
        	res = tmp > res ? tmp : res;
        }
    }
}
cout << res;

这个是针对确定的一个字符串的比较暴力的算法,一旦不匹配就从头重新匹配。(因为针对确定的一个字符串,所以前缀都是从第一个开始的嘛,不匹配就从头再判断咯)

求next数组

其实构建next数组的本质就是求解pattern中的 前缀子串中的 最长公共前后缀。

next[i] = k pattern[i]之前的子串中,存在最长长度为k的公共前后缀,因此可以把当前不匹配的text的位,与k+1位pattern再次进行比较。(但是因为数组是从0开始的,所以下标可以直接被赋值next数组中的值,而不用+1

也就是它需要进行len-1次的上一节的行为。同时需要考虑尽可能重复利用结果(有点像KMP中patterntext不匹配时就使用next数组跳跃,这里也是利用next数组中已经产生的数据进行跳跃。

想象两个指针,一个指向前缀的最后,一个指向后缀的最后,当它们的值不同时:

i -> 前缀最后 j->从0到len-1循环的下标
pattern[i] != pattern[j]时
让i往回走,类似kmp
i = next[i];//现在i是最长公共前后缀中前缀的后一位的下标

当它们的值相同时,那公共前后缀的长度增加:

next[++j] = ++i;注意next数组的下标对应的循环变量j
使用next时,记住使用的下标的值是不匹配的当位。
//字符串数组下标从0开始
int next[pattern.length()+1];
next[0] = -1;//用于pattern与text进行匹配且第一个数不匹配时
int i = 0, j = -1;//i是循环变量,j是代表前缀最后比较的下标
next[++i] = ++j;//初始化
int len = pattern.length();
while(i < len){//wihle循环变量用的是i
if(pattern[i] == pattern[j])i
	next[++i] = ++j;//next[i]作用是模式串在第i位不匹配了,就将下标i跳到next[i]处,所以next中应该使用循环变量i
else
	j = next[j];//不匹配时是j在回退
}

把pattern和text进行匹配,利用next数组加速

j = 0; i = 0;//j是text的下标,i是pattern的下标
while(j < text.length()){
	while(i >= 0 && pattern[i] != text[j]){
    	i = next[i];
    }
    //循环退出,说明i<0或者当前位匹配
    i++; j++;//继续向后匹配
    //进行跳出判断
    if(i == len)
        return j-len
}
原文地址:https://www.cnblogs.com/peekapoooo/p/14159169.html