KMP next陣列的花式使用

2021-08-19 20:53:59 字數 3187 閱讀 4290

kmp演算法,是用來優化字串的模式匹配(源串s中是否存在模式串p),可以把暴力匹配的複雜度o(n²)降低到o(m+n)。

通過對模式串生成字首陣列(next陣列),來跳躍式的進行模式匹配,解決如hdu 1711(這樣的問題。我們一般使用的是把next[0]標記為負數(-1)的修正版kmp。

但是kmp演算法中最具價值的部分並不是模式匹配,而是next陣列本身。next陣列的一般定義是「next[n] = 0-n串中最長相同前字尾的長度」。定義是這樣沒錯,但是這一定義卻可以用另外一種方式來理解:next[n] = 當前字首上一次出現的索引。

這一理解是由定義和一定的腦洞推出的,當我們把next陣列生成目標串的起始索引標為1而不是0的時候生效(這就是腦洞部分…..)。

舉個例子:

a b a b a b-10

0123

4

上方是模式串,索引從1開始標記。從頭開始看,next[1]的值是0,代表這個字首第一次出現。而next[3] = 1,代表著當前位置的字首在串中不是第一次出現,上一次出現的位置是索引1,即「a」。

而next[4] = 2,也代表著不是第一次出現,上一次出現的位置是索引2,即「ab」。

根據這一變換定義,我們也可以寫出生成next陣列的**:

public

static

void

setprefix(int m)

i++;

}}

以上**是未修正版的next陣列,next值是錯位的,僅做演示證明結論的正確性,不建議使用。我們一般使用的模板是:

public

static

void

setprefix(int m) else

k = next[k];

}}

兩者的結果除了錯位以外完全相同,「next[n] = 當前字首上一次出現的索引」的腦洞正確。

通過這一腦洞,我們可以解決一些有趣的非模式匹配但是有關前字尾的問題。首先以poj2752(為例。這一題要你求所有「同時是字首又是字尾」的子串的長度。比如樣例「aaaa」的結果就是「1 2 3 4 5」。

我們可以用上面提到的next陣列腦洞版定義來求解這一題。

首先生成next陣列,然後倒著利用陣列的索引性來進行穿梭,最後正著輸出答案。

int count = 0;

for (int i = in.length(); i >= 1;)

ans[count++] = next[i];

i = next[i];// 跳到上一次出現的位置

}for (int i = count - 1; i >= 0; i--)

out.println(in.length());// 最後加上自己

由於字尾的定義「從任意字母開始到最後的子串」,才可以直接從最後開始穿梭。

除了純粹的穿梭操作以外,還可以利用這一性質數出每個字首重複出現的次數,舉例hdu3336(

這一題讓你求每個字首出現次數的和再%10007。

我們求出next陣列後可以寫出這樣的**:

// zhouge = 10007, 洲哥是一位值得膜的大人物,連續%可以增加ac率

int ans = 0;

for (int i = 1; i <= n; i++)

}out.println(ans);

每能夠穿梭一次,就代表字首出現了一次,+1記錄就好。我們來人肉跑一遍,以樣例「abab」為例,生成的next陣列是:

a b a b-10

012

從索引1開始。

next[1] = 0,代表字首」a」第一次出現,+1

next[2] = 0,字首」ab」第一次出現,+1

next[3] = 1,首先字首」aba」第一次出現,+1,然後因為next陣列不為0,穿梭,原來是字首」a」第二次出現了,+1

next[4] = 2,字首」abab」第一次出現,+1,穿梭,字首」ab」第二次出現,+1

結果ans = 6

畫外音:

q:為什麼next[4] = 2不是」b」第二次出現?

a: 因為b不是字首啊!!!只有博主這種zz才會在做題時腦子短路出現這種疑問。

通過人肉cpu我們發現,每乙個字首至少會出現一次,因此我們可以進行乙個小優化:

int ans = len;// 串的長度

for (int i = 1; i <= n; i++)

}out.println(ans);

直接在一開始就把這部分加進去,並避免不必要的穿梭。

索引性質的利用到這就說完了,接下來提一下其他的一些操作。舉例華工賽2018-e(之前部落格發過)和hdu2594(

華工賽那題太長了,以hdu2594為例。

讓你求同時s1串的字首和s2串字尾的最長子串。比如樣例「riemann」和「marjorie」答案是「rie」。

呃,操作就是把兩串拼起來跑一遍next陣列,然後輸出最後一位,原理上面已經提過了。其實這是擴充套件kmp的extend陣列解決的問題,即「定義母串s,和字串t,設s的長度為n,t的長度為m,求t與s的每乙個字尾的最長公共字首」。然而還是能用next陣列水過去。

值得一提的是拼接時要在中間加乙個倆串中都不會出現的特殊符號,例如「*」。如果不這麼做的話輸入「abcabc」和「abc」,或是「abc」和「abcabc」就會出bug,當然不加的話加個條件判斷也ok。

string

get = reader.next() + "*" + reader.next();

setprefix(get);

int ans = next[get.length()];

if (ans == 0) else

然後是迴圈子串的問題,直接丟萬能定理:假設s的長度為len,則s存在迴圈子串,當且僅當,len可以被len - next[len]整除,最短迴圈子串為s[len - next[len]]。

原理大佬比我講得好:

舉例poj2406(),求迴圈子串的長度。

int c = len - next[len];

if(len%c==0)else

通過len%0==0保證存在迴圈子串,然後輸出就行了。

以上是這幾天休閒打kmp的成果,以後有再補。去研究莫隊了。

KMP next陣列的求法

kmp 演算法的next陣列求法 void getnext char ptn,int next next j 更新自己的值。如果 1 j ptn i ptn j 這個條件成立,此時可以計算next i 1 的值為j 1。我看看這個過程 當i 6進入while迴圈之前,j next 6 當i 6進入w...

KMP next 陣列的思想

剛剛寫了點 我剛剛除錯了幾次 然後又在自行輸出 我先說說 啊,就是這個 next 陣列就為什麼可以求出前字尾最長 相同的數量?首先說說 前字尾,例如字串 aaaa,我們就直接用眼睛看的話,就是最長就是3,aaa a a aaa。這就是最長的前字尾,我們怎麼用 實現的呢?首先 前字尾 不能是一樣的 就...

KMP next陣列理解

3.15週末學長教了一下kmp,但是對next的構造尚存疑.在饒了一整天頭以後勉強弄懂了 記錄一下比較混亂就是了 這裡的對稱指的的字元塊對稱而非軸對稱 當next t 值為0時 顯然就意味著從字串的開頭到這個t部分的話對稱性是0 即當繼續計算下一位的next t 1 的大小時只需比較第n 1位的字元...