【字串】KMP演算法
KMP演算法
基本概念
1、s[ ]是模式串,即比較長的字串(要去匹配上的字串)。
2、p[ ]是模板串,即比較短的字串。(用來去匹配的字串)
3、“非平凡字首”:指除了最後一個字元以外,一個字串的全部頭部組合(前面連續的部分)。
4、“非平凡字尾”:指除了第一個字元以外,一個字串的全部尾部組合。(後面會有例子,均簡稱為前/字尾)
注意:KMP演算法中前後綴指的都是非平凡,即不包括整個串。
5、“部分匹配值”:字首和字尾的最長共有元素的長度。
6、next[ ]是“部分匹配值表”,即next陣列,它儲存的是每一個下標對應的“部分匹配值”,是KMP演算法的核心。(後面作詳細講解)
next陣列的含義、手動模擬及求解
用p[x,y]來表示p串中位置起於x位置,終於y位置的子串。
對於任意一個j,且j小於等於p串的長度
有這麼一個定義,next[j]表示的是p[1,j]子串中字首和字尾相同的最大子串的長度。
為了演算法的方便,KMP演算法中字串p的下標從1開始
按照定義,對於位置j,有這麼一個性質,p[1,next[j]]=p[j-next[j]+1,j](即字首等於字尾)
比如,aba33aba99999
,取p[1,8],得到子串aba33aba
,其中aba33aba,所以有next[8]=3;
手動模擬
p | a | b | a | 3 | 3 | a | b | a |
---|---|---|---|---|---|---|---|---|
next | 0 | 0 | 2 | 0 | 0 | 1 | 2 | 3 |
經過手動的模擬,我們有了大概的對KMP演算法next陣列的求解規律的感覺。
next陣列的求解
-
有模式串去生成next陣列
-
在已經知道next[j]的前提下,嘗試去求next[j+1],可以發現next[j+1]<=next[j]+1.
-
由於求的是非平凡前後綴,所以KMP演算法的求next陣列的起始位置要從2開始。
-
由於是求一個字首和一個字尾,故而,需要兩個指標來完成任務。(一個用來確定字首,一個用來確定字尾)。
-
因為p[1,next[j]]=p[j-next[j]+1,j],設前子串為a,後子串為b,由於前後子串相等,且a子串內部也有對應的next[ a.length() ] ,且它會和b子串對應上。
-
這個性質在KMP快速匹配的時候非常有用(可以看成字首和字尾各自裂開,最終形成了四個相同部分),比如說這一句(i代表新加入來的點,而j代表的是原來為加入i這個點時的串p[1,i-1]的next[i-1])。
while(j&&p[i]!=p[j+1])j=next[j];
大致流程
程式碼理解,如果有必要的話就做一個裂開處理,然後裂開完後,再做進一步的確認
for(int i = 2, j = 0; i <= m; i++)
{
while(j && p[i] != p[j+1]) j = next[j];
if(p[i] == p[j+1]) j++;
next[i] = j;
}
處理好產生於較小長度的p串的next陣列後,就可以拿著這個next陣列再s串中進行匹配。
匹配
這裡也是需要分兩個指標,指標i和指標j,指標i用來在s串中掃,指標j用來在p串中掃。
同時在匹配過程中,如果沒有出現什麼意外(匹配不上)的話,匹配量就會一直加一加一,並且
for(int i = 1 , j=0;i<=s.length();i++)
{
while(j && s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j==m)//匹配成功
{
printf("%d",i-m+1)
j = ne[j];
}
}
重新開始匹配之旅且藉助next陣列快速開局(重生之我是kmp匹配)
#include <bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N = 1E6+10, M =1E5+10;
int n,m;
int ne[M];
char s[N],p[M];
int main()
{
m = read();
cin>>p+1;
n = read();
cin>>s+1;
for(int i=2,j=0;i<=m;i++)
{
while(j&&p[i]!=p[j+1]) j = ne[j];
if(p[i]==p[j+1]) j++;
ne[i] = j;
}
for(int i=1,j=0;i<=n;i++)
{
while(j&&s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j==m)
{
printf("%d ",i-m);
j = ne[j];
}
}
return 0;
}