洛谷P3375 【模板】KMP字符串匹配

題目傳送門:戳我進入ios

 

KMP算法是用來處理字符串匹配的問題的,也就是給你兩個字符串,你須要回答B串是不是A串的子串,B串在A串中出現了幾回,B串在A串中出現的位置等問題。算法

KMP算法的意義在於,若是你在洛谷上發了一些話,kkksc03就能夠根據KMP算法查找你是否說了一些不和諧的字,而且屏蔽掉你的句子裏的不和諧的話(好比cxk雞你太美就會被屏蔽成cxk****),還會根據你句子中出現不和諧的字眼的次數對你進行處罰spa

 

舉個栗子:A:GCAKIOI      B:GC     ,那麼咱們稱B串是A串的子串指針

咱們稱等待匹配的A串爲主串,用來匹配的B串爲模式串。code

通常的樸素作法就是枚舉B串的第一個字母在A串中出現的位置並判斷是否適合,而這種作法的時間複雜度是O(mn)的,當你處理一篇較長文章的時候顯然就會超時。blog

咱們會發如今字符串匹配的過程當中,絕大多數的嘗試都會失敗,那麼有沒有一種算法可以利用這些失敗的信息呢?ip

KMP算法就是字符串

KMP算法的關鍵是利用匹配失敗後的信息,儘可能減小模式串與主串的匹配次數以達到快速匹配的目的get

設主串(如下稱爲T)string

設模式串(如下稱爲W)

用暴力算法匹配字符串過程當中,咱們會把T[0] 跟 W[0] 匹配,若是相同則匹配下一個字符,直到出現不相同的狀況,此時咱們會丟棄前面的匹配信息,而後把T[1] 跟 W[0]匹配,循環進行,直到主串結束,或者出現匹配成功的狀況。這種丟棄前面的匹配信息的方法,極大地下降了匹配效率。

咱們來看一看KMP是怎麼工做的

在KMP算法中,對於每個模式串咱們會事先計算出模式串的內部匹配信息(也就是說這個東西只和模式串有關,能夠預處理,這個處理咱們後面會提到),在匹配失敗時最大的移動模式串,以減小匹配次數。

好比,在簡單的一次匹配失敗後,咱們會想將模式串儘可能的右移和主串進行匹配。右移的距離在KMP算法中是如此計算的:在已經匹配的模式串子串中,找出最長的相同的前綴和後綴,而後移動使它們重疊。

咱們用兩個指針i和j分別表示A[i-j+1......i]和B[1......j]徹底相等,也就是說i是不斷增長的,而且隨着i的增長,j也相應的變化,而且j知足以A[j]結尾的長度爲j的字符串正好匹配B串的前j個字符,如今須要看A[i+1]和B[j+1]的關係

 

  • 當A[i+1]=B[j+1]時,咱們將i和j各增長1
  • 不然,咱們減少j的值,使得A[i-j+1......i]和B[1......j]保持匹配並嘗試匹配新的A[i+1]和B[j+1]

舉個栗子:

T: a b a b a b a a b a b a c b

W:a b a b a c b

當i=j=5時,此時T[6]!=W[6],這代表此時j不能等於5了,這個時候咱們要改變j的值,使得W[1...j]中的前j'個字母與後j'個字母相同,由於這樣j變成j'後(也就是將W右移j'個長度)才能繼續保持i和j的性質。這個j'顯然越大越好。在這裏W[1...5]是匹配的,咱們發現當ababa的前三個字母和後三個字母都是aba,因此j'最大也就是3,此時狀況是這樣

T: a b a b a b a a b a b a c b

W:      a b a b a c b

那麼此時i=5,j=3,咱們又發現T[6]與W[4]是相等的,而後T[7]與W[5]是相等的(這裏是兩步)

因此如今是這種狀況:i=7,j=5

T: a b a b a b a a b a b a c b

W:      a b a b a c b

這個時候又出現了T[8]!=W[6]的狀況,因而咱們繼續操做。因爲剛纔已經求出來了當j=5時,j'=3,因此咱們就能夠直接用了(經過這裏咱們也能夠發現j'是多少和主串沒有什麼關係,只和模式串有關係)

因而又變成了這樣

T: a b a b a b a a b a b a c b

W:            a b a b a c b

這時,新的j=3依然不能知足A[i+1]=B[j+1],因此咱們還須要取j'

咱們發現當j=3時aba的第一個字母和最後一個字母都是a,因此這時j'=1

新的狀況:

 

T: a b a b a b a a b a b a c b

 

W:                  a b a b a c b

仍然不知足,這樣的話j須要減少到j'就是0(咱們規定當j=1時,j'=0)

T: a b a b a b a a b a b a c b

W:                     a b a b a c b

終於,T[8]=B[1],i變爲8,j變爲1,咱們一位一位日後,發現都是相等的,最後當j=7還知足條件時,咱們就能夠下結論:W是T的子串,而且還能夠找到子串在主串中的位置(i+1-m+1,由於下標從0開始)

這一部分的代碼其實很短,由於用了for循環

inline void kmp()
{
    int j=0;
    for(int i=0;i<n;i++)
    {
        while(j>0&&b[j+1]!=a[i+1]) j=nxt[j];
        if(b[j+1]==a[i+1]) j++;
        if(j==m) 
        {
            printf("%d\n",i+1-m+1);
            j=nxt[j]; 
    //當輸出第一個位置時 直接break掉 
    //當輸出全部位置時 j=nxt[j]; 
    //當輸出區間不重疊的位置時 j=0 
        }
    }
}

 

這裏就有一個問題:爲何時間複雜度是線性的?

咱們從上述的j值入手,由於每執行一次while循環都會使j值減少(但不能到負數),以後j最多+1,所以整個過程當中最多加了n個1.因而j最多隻有n個機會減少。這告訴咱們,while循環最多執行了n次,時間複雜度平攤到for循環上後,一次for循環的複雜度是O(1),那麼總的時間複雜度就是O(n)的(n是主串長度)。這樣的分析對於下文的預處理來講一樣有效,也能夠獲得預處理的時間複雜度是O(m)(m是模式串長度)

接下來是預處理

預處理並不須要按照定義寫成O(m2)甚至O(m3),窩們能夠經過nxt[1],nxt[2]....nxt[n-1]來求得nxt[n]的值

舉個栗子

 W :a b a b a c b

nxt:0 0 1 2 ??

假如咱們有一個串,而且已經知道了nxt[1~4]那麼如何求nxt[5]和nxt[6]呢?

咱們發現,因爲nxt[4]=2,因此w[1~2]=w[3~4],求nxt[5]的時候,咱們發現w[3]=w[5],也就是說咱們能夠在原來的基礎上+1,從而獲得更長的相同先後綴,此時nxt[5]=nxt[4]+1=3

W :a b a b a c b

nxt:0 0 1 2 3?

那麼nxt[6]是否也是nxt[5]+1呢?顯然不是,由於w[nxt[5]+1]!=w[6],那麼此時咱們能夠考慮退一步,看看nxt[6]是否能夠由nxe[5]的狀況所包含的子串獲得,便是否nxt[6]=nxt[nxt[5]]+1?

事實上,這樣一直推下去也不行,因而咱們知道nxt[6]=0

那麼預處理的代碼就是這樣的

inline void pre()
{
    nxt[1]=0;//定義nxt[1]=0 
    int j=0;
    rep(i,1,m-1)
    {
        while(j>0&&b[j+1]!=b[i+1]) j=nxt[j];
        //不能繼續匹配而且j尚未減到0,就退一步 
        if(b[j+1]==b[i+1]) j++;
        //若是能匹配,就j++ 
        nxt[i+1]=j;//給下一個賦值
    }
}

 

完整的代碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<algorithm>
#include<iomanip>
using namespace std;
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
typedef long long ll;
ll read()
{
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'||ch>'9') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

char a[1000005],b[1000005];
int nxt[1000005],n,m;

inline void pre()
{
    nxt[1]=0;
    int j=0;
    rep(i,1,m-1)
    {
        while(j>0&&b[j+1]!=b[i+1]) j=nxt[j]; 
        if(b[j+1]==b[i+1]) j++;
        nxt[i+1]=j; 
    }
}

inline void kmp()
{
    int j=0;
    for(int i=0;i<n;i++)
    {
        while(j>0&&b[j+1]!=a[i+1]) j=nxt[j];
        if(b[j+1]==a[i+1]) j++;
        if(j==m) 
        {
            printf("%d\n",i+1-m+1);
            j=nxt[j]; 
        }
    }
    rep(i,1,m) printf("%d ",nxt[i]);
    
}

int main()
{
    scanf("%s%s",a+1,b+1);
    n=strlen(a+1),m=strlen(b+1);
    pre();
    kmp();
    return 0;
}
相關文章
相關標籤/搜索