1. 程式人生 > 實用技巧 >KMP 大問題分割成小問題 分步理解

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中pattern

text不匹配時就使用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){
if(pattern[i] == pattern[j])i
	next[++i] = ++j;
else
	i = next[i];
}

把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
}