KMP算法 Next數組詳解


題面

題目描述

如題,給出兩個字符串s1和s2,其中s2為s1的子串,求出s2在s1中所有出現的位置。

為了減少騙分的情況,接下來還要輸出子串的前綴數組next。如果你不知道這是什么意思也不要問,去百度搜[kmp算法]學習一下就知道了。
輸入輸出格式

輸入格式:

第一行為一個字符串,即為s1(僅包含大寫字母)

第二行為一個字符串,即為s2(僅包含大寫字母)

輸出格式:

若干行,每行包含一個整數,表示s2在s1中出現的位置

接下來1行,包括length(s2)個整數,表示前綴數組next[i]的值。

輸入樣例:

ABABABC
ABA

輸出樣例:

1
3
0 0 1

說明

時空限制:1000ms,128M
數據規模:
設s1長度為N,s2長度為M
對於30%的數據:N<=15,M<=5
對於70%的數據:N<=10000,M<=100
對於100%的數據:N<=1000000,M<=1000

題解

這是一道KMP裸題(模板題。。)
我就是拿着它學習一下KMP算法
其實原來我學過KMP算法
但是一直沒有弄懂next(跳轉)數組是如何求出來的。
最近花了一個下午自己研究了一下KMP算法
現在終於覺得KMP很簡單了~




現在直接說next數組把
至於有什么作用,next數組是干什么的,請自行百度,有很多dalao總結的非常到位,看一看就會明白。


好,來說next數組


這里寫圖片描述


並不用在意這一坨黑的是什么東西,我們就假設他是我們要求next數組的字符串。


next數組求的東西就是從起始位置到當前位置最長的相等的前綴和后綴的長度。
(舉個例子China的前綴有:C、Ch、Chi、Chin、China ; 后綴有a、na、ina、hina、China)

這里寫圖片描述




我們繼續,如上圖紅色的是當前位置(設為j)前,所匹配上的最長前綴和后綴,藍色的是當前要匹配的位置。

這里寫圖片描述

那么,我們就拿當前位置和原來匹配到的最長前綴的后一位相比較
如果兩個位置相同,
顯然,
可以和前面的紅色連在一起,
此時就有next[j]=next[j-1]+1

如果兩個位置不相同,
根據next數組的性質,
顯然的,你的當前的相等的前綴和后綴只能夠繼續向前找,
也就是說,你當前的next數組一定會減小。

這里寫圖片描述


既然前面的紅色部分存在一小塊灰色,那么,后面的紅色部分也必然存在灰色部分。


這里寫圖片描述




所以,判斷當前位置和前面那一塊灰色的前綴的后一位是否相等。
如果這兩位相同的話,不就可以和前面的灰色部分連在一起了嗎


這里寫圖片描述

此時,又回到一開始的那一步。
因此,求解某個位置的next值是一個循環過程。
不斷檢查 上一位的 最長前綴的 后一位(i位置)(這句子有點拗口)
如果相等next[j]=next[i]+1
否則令 i=next[i-1]+1,繼續循環匹配

如果沒有看懂就自己多看幾遍,自己找幾個字符串算一算

所以:求解next數組的代碼:

inline void GetNext(string s)//獲得字符串s的next數組
{
    int l=s.length(),t;
    Next[0]=-1;//如果在0位置失配則是向下移動一位
    for(int i=1;i<l;++i)//依次求解后面的next數組
    {
        t=Next[i-1];
        while(s[t+1]!=s[i]&&t>=0)//循環求解next值 
            t=Next[t];
        if(s[t+1]==s[i])//如果是匹配上而退出循環 
            Next[i]=t+1;
        else            //否則則是匹配不上 
            Next[i]=-1; //指向頭 
    }
}


代碼很簡潔的~
那么,接下來如何利用Next數組求解匹配
那就自己baidu吧(知道了next數組,KMP就很好理解了)

接下來貼上小蒟蒻的源碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
const int MAX=1001;
int Next[MAX];
vector<int> Ans;
inline void GetNext(string s)//獲得字符串s的next數組
{
    int l=s.length(),t;
    Next[0]=-1;//如果在0位置失配則是向下移動一位
    for(int i=1;i<l;++i)//依次求解后面的next數組
    {
        t=Next[i-1];
        while(s[t+1]!=s[i]&&t>=0)//循環求解next值 
            t=Next[t];
        if(s[t+1]==s[i])//如果是匹配上而退出循環 
            Next[i]=t+1;
        else            //否則則是匹配不上 
            Next[i]=-1; //指向頭 
    }
}
inline void KMP(string s1,string s2)
{
    GetNext(s2);
    int l1=s1.length();
    int l2=s2.length();
    int i=0,j=0;
    while(j<l1)
    {
        if(s2[i]==s1[j])//當前位匹配成功,繼續匹配下一位
        {
            ++i;++j;
            if(i==l2)//完全匹配
            {
                Ans.push_back(j-l2+1);//儲存答案
                i=Next[i-1]+1;//繼續匹配                
            }
        }
        else
        {
            if(i==0)//在首位不匹配
                j++;//直接向后挪一位
            else
                i=Next[i-1]+1;//跳轉
        }
    }
}
int main()
{
    string s1,s2;
    int l;
    cin>>s1>>s2;
    l=s2.length();
    KMP(s1,s2);
    for(int i=0;i<Ans.size();++i)
        cout<<Ans[i]<<endl;
    for(int i=0;i<l;++i)
        cout<<Next[i]+1<<' ';
    cout<<endl;
    return 0;
}

最后再說一句
如果需要加深理解KMP的過程
請去SYC的blog看看他的gif動圖
你可能就會有更多了解
膜拜SYC大佬去


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM