KMP不成文的見解

(部分分析靈感及樣例來源於 @xehoth)
複習模板的時候打了幾遍,比當時學的時候感覺要清晰一些,於是抱着KMP在屏幕上滾來滾去滾來滾去滾來滾去~

吶,首先對於幾乎所有第一次接觸KMP的人來說的那個大坑(喂就是那個神奇的next[ ]數組),我們先按下不表。
假設模式串是 abcieruabc ,目標串是 abc

abcieruabc
---
abc

第一次匹配,匹配成功,如果不用KMP算法

abcieruabc
 |
 abc

第一個就不匹配,若是繼續用這種樸素的方法,下一次還是第一個就不匹配。(等等我們換一個更具特色的樣例)

ttittittyyios
-----|
ttitty

吶,如果用樸素的方法

ttittittyyios
 -|
 ttitty

再然後

ttittittyyios
  |
  ttitty

。。。。我們還是談談KMP好啦。
由於目標串中”tt”重複了兩次,則第二次匹配完全可以從第一次第二個”tt”所在的位置開始匹配。
當然,從某一個方面來說’t’也重複了幾次,但是

ttittittyyios
    -|
    ttitty

就匹配失敗了,所以要從最大重複的字串入手。

放道題來討論吧。

字符串匹配【KMP模板】

給定兩個由小寫字母構成的字符串 L 和 S 。
請你從左到右,找出子串 L 在母串 S 中每次出現的開始位置(匹配位置)。

輸入格式

第一行:給一個全由小寫字母構成的母串 S(0<S的長度≤1000000);
第二行:給一個全由小寫字母構成的子串 L(0<L的長度≤S的長度)。

輸出格式

按升序輸出一行一個整數,分別表示子串 L 在母串 S 中每次出現的開始位置。
如果子串 L 在母串 S 中沒有出現,則輸出“NO”。

樣例數據 1

輸入
yuabcierabcde
abc

輸出
3
9

樣例數據 2

輸入
abcdefg
abcdefu

輸出
NO

備註

【樣例1說明】
從第3個位置起,第一次匹配;
從第9個位置起,第二次匹配。

for(int i=2,j=0;i<=m;i++)
    {
        while(j!=0&&t[i]!=t[j+1])
            j=next[j];
        if(t[i]==t[j+1]);
            j++;
        next[i]=j;
    }

這就是對目標串t的一個初始判定
其實後來二者匹配的時候代碼差不多,因爲第一個其實可以看做是目標串自己和自己匹配。只是第一次是不要求是否匹配完全,只要求找出是否存在可以匹配的來降低複雜度與時間;第二次就需要討論一下 j==m 的情況,及是否匹配完全,若完全,將標記 f 進行修改,輸出此時最後一個位置i減去目標串的長度m再加1,即此時目標串首位的位置。最終若f作了修改,,則繼續尋找下一個匹配點;若沒有,輸出”NO”,再找下一個匹配點。

bool f=1;
    for(int i=1,j=0;i<=n;i++)
    {
        while(j!=0&&s[i]!=t[j+1])
            j=next[j];
        if(s[i]==t[j+1])
            j++;
        if(j==m)
        {
            cout<<i-m+1<<endl;
            j=next[m],f=0;
        }
    }
    if(f==1)
        cout<<"NO"<<endl;

那麼大體上算法如何實現的已經說完了,接下來淺顯簡短地說一下那個神奇的next[ ]數組。(明明就是自己也不怎麼懂吧。。。。人艱不拆穿)
據說是

next 數組就是模式串前綴的最長公共前後綴的長度。

唔那大概是這樣的

ttitty      前綴        後綴        next
1 t          -          -           0
2 tt         t          t           1
3 tti       t,tt       ti,i         0
4 ttit    t,tt,tti   tit it t       1
5 ttitt     ……        ……         ……
6 ttitty    ……        ……         ……

(部分分析靈感及樣例來源於 @xehoth)

說了那麼多,還有完整版無水代碼。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;

int n,m;
int next[1000010];
char s[1000010],t[1000010];

int main()
{
    scanf("%s%s",s+1,t+1);
    n=strlen(s+1),m=strlen(t+1);
    for(int i=2,j=0;i<=m;i++)
    {
        while(j!=0&&t[i]!=t[j+1])
            j=next[j];
        if(t[i]==t[j+1])
            j++;
        next[i]=j;
    }
    bool f=1;
    for(int i=1,j=0;i<=n;i++)
    {
        while(j!=0&&s[i]!=t[j+1])
            j=next[j];
        if(s[i]==t[j+1])
            j++;
        if(j==m)
        {
            cout<<i-m+1<<endl;
            j=next[m],f=0;
        }
    }
    if(f==1)
        cout<<"NO"<<endl;
    return 0;
}

喵嗚~

(部分分析靈感及樣例來源於 @xehoth)
來自2017.10.10

——我認爲return 0,是一個時代的終結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章