1. 程式人生 > 其它 >【字串】KMP演算法

【字串】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;
}