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位的字元...