1. 程式人生 > >從暴力匹配到KMP算法

從暴力匹配到KMP算法

return 詳細 輸出 代碼 exce include 位置 pan namespace

前言

現在有兩個字符串:\(s1\)\(s2\),現在要你輸出\(s2\)\(s1\)當中每一次出現的位置,你會怎麽做?


暴力匹配算法

基本思路

用兩個指針分別指向當前匹配到的位置,並對當前狀態進行分類討論:若相同則繼續往下匹配,否則回溯

大致思路

\(i\)來存儲\(s1\)當前匹配到的位置,用\(j\)來存儲\(s2\)當前匹配到的位置,則可得初始狀態下\(i=j=0\)

對於當前狀態,有兩種可能性:

①:\(s1[i]==s2[j]\)。則\(i++,j++\)

②:\(s1[i]!=s2[j]\)。則\(i-=(j-1),j=0\)

評價

時間復雜度:\(O(nm)\)。顯然,這個方法效率並不高,每一次回溯要耗去大量時間,能不能進行優化呢?


\(KMP\)算法

簡介

\(KMP\)算法是對暴力匹配算法的改進,由\(D.E.Knuth\)\(J.H.Morris\)\(V.R.Pratt\)同時發現,因此人們稱它為\(Knuth-Morris-Pratt\)算法(簡稱\(KMP\)算法)。

基本思路

\(KMP\)算法的關鍵是利用匹配失敗後的信息,盡量減少模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是實現一個\(Next\)函數,函數本身包含了模式串的局部匹配信息。

大致思路

還是用\(i\)來存儲\(s1\)當前匹配到的位置,用j來存儲\(s2\)當前匹配到的位置,則可得初始狀態下\(i=j=0\)
對於當前狀態,有兩種可能性:

①:\(s1[i]==s2[j]\)。則\(i++,j++\)
②:\(s1[i]!=s2[j]\)。則\(j=Next[j]\)(i不變)
其中\(Next\)數組存儲的是當前這一位的部分匹配值(這在後面會詳細介紹),所以只要讓\(j\)變成\(Next[j]\),就可以繼續對當前字符串進行匹配了,省去了i回溯所耗去的大量時間

\(Next\)數組

在匹配過程中,你可以發現一個基本事實是:當\(s1[i]\)\(s2[j]\)不匹配時,你其實知道前面\(j-1\)字符是什麽。

\(KMP\)算法的想法是,設法利用這個已知信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向後移,這樣就提高了效率。

所以,我們就可以把當前所得到的部分匹配值給求出來。又由於對於同一個字符串,部分匹配值是固定不變的,所以可以把它存在\(Next\)數組裏。

那麽\(Next\)數組怎麽求呢?

\(Excerpt\)

\(Next\)數組的過程就是一個\(KMP\)的過程。

首先,令\(i=0\)\(j=-1\)\(Next[0]=-1\),且當前要求的是\(Next[i+1]\)。則對於當前狀態,有兩種可能性:
\(j==-1\)\(s2[i]==s2[j]\)。則\(i++,j++,Next[i]=j\)
\(j!=-1\)\(s2[i]!=s2[j]\)。則\(j=Next[j]\)//把\(j\)賦值為\(j\)的部分匹配值
這樣就可以輕松求出\(Next\)數組了。

代碼
#include<bits/stdc++.h>
#define N 1000000
#define pc(ch) (pp_<100000?pp[pp_++]=ch:(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=ch))
int pp_=0;char pp[100000];
using namespace std;
int len1,len2,Next[N+5];//len1存儲s1的長度,len2存儲s2的長度,這樣不用調用strlen(),strlen()會超時;Next[]存儲部分匹配值 
char s1[N+5],s2[N+5];
inline void write(int x)
{
    if(x>9) write(x/10);
    pc(x%10+'0');
}
inline void GetNext()//求出Next[]數組 
{
    register int i=0,j=Next[0]=-1;//初始化 
    while(i<=len2)//類似於一個KMP的過程 
    {
        if(j==-1||s2[i]==s2[j]) i++,j++,Next[i]=j;
        else j=Next[j];
    }
}
int main()
{
    register int i=0,j=0;
    scanf("%s%s",s1,s2),len1=strlen(s1),len2=strlen(s2),GetNext(); 
    while(i<=len1)//KMP的過程 
    {
        if(j==-1||s1[i]==s2[j]) {++i;if(++j==len2) write(i-len2+1),pc('\n'),j=Next[j];/*如果找到答案就輸出*/} 
        else j=Next[j];//如果匹配失敗,就更新j為其部分匹配值 
    }    
    for(i=1;i<=len2;++i) write(Next[i]),pc(' ');//依照題意輸出Next[]數組 
    return fwrite(pp,1,pp_,stdout),0;
}

從暴力匹配到KMP算法