算法導論————EXKMP

【例題傳送門:caioj1461


【EXKMP】最長共同前綴長度

【題意】
給出模板串A和子串B,長度分別爲lenA和lenB,要求在線性時間內,對於每一個A[i](1<=i<=lenA),求出A[i..lenA]與B的最長公共前綴長度
【輸入文件】
輸入A,B兩個串,(lenB<=lenA<=1000000) 
【輸出文件】
輸出lenA個數,表示A[i...lenA]與B的最長公共前綴長度,每一個數以前有空格 
【樣例輸入】
aabbabaaab
aabb
【樣例輸出】
4 1 0 0 1 0 2 3 1 0php


算法分析:

  學EXKMP前,必須將KMP學透,若是仍未學KMP,請出門左轉【傳送門html

  咱們在KMP算法中能夠理解,p數組是KMP的核心,p[i]表明着以i爲結尾和以開頭爲首的最長公共子串長度,也就是說對於st字符串數組的p[i]表明的就是st字符串數組從1開始到p[i]和從i-p[i]+1i是徹底相同的(st[1...p[i]]=st[i-p[i]+1...i]算法

  那麼擴展KMP就高級了。同樣仍是p數組(仍是原來的配方,仍是熟悉的味道!),可是既然是擴展KMP就不要用p,我就改爲extend數組了,表示的意義與普通KMP就大有不一樣。extend[i]表示的是以i爲首和以開頭爲首的最長前綴,也就是說對於st字符串數組中的extend[i]表示的就是st字符串數組從1開始到extend[i]和從ii+extend[i]-1是徹底相同的(st[1...extend[i]]=st[i...i+extend[i]-1]數組

  首先extend[1]就不用說了,直接等於len這個沒有問題吧,那麼由於extend[1]這個是具備必定性,因此咱們基本把這東西廢掉..那麼咱們就直接從extend[2]開始。而後咱們就定義一個k,這個k表示的就是在當前搜索過的範圍之內(由於這是線性算法,因此是從1到len)能到達最遠(也就是說k+extend[k]-1最大的)的編號,爲何要定義一個這樣子的東西,等下大家就知道了。spa

  咱們先定義一個p,讓它等於k+extend[k]-1,那麼由extend這個數組的定義咱們就能夠獲得一個等式:st[1...extend[k]]=st[k...p]。由於p=k+extend[k]-1,因此咱們又能夠獲得一條等式:extend[k]=p-k+1,把這個代換到上一條等式上,就會——瞬間爆炸!(好吧開個玩笑..)就會變成:st[1...p-k+1]=st[k...p],以下圖:code

 

   由於咱們如今要求從extend[i],那麼由上面這條等式又能夠獲得另外一條等式:st[i-k+1...p-k+1]=st[i...p],以下圖:htm

  在看此證實過程當中請各位一直記住extend數組所表達的含義,否則會有不少地方不懂的。那麼咱們再定義一個L=extend[i-k+1],咱們又能夠獲得一條等式:st[i-k+1...i-k+L](注意:原本獲得的應該是i-k+1+L-1,我直接把+1-1省略了)=st[1...L],以下圖:blog

  這個時候有人就會問了:爲何你上面的圖不和前幾個合在一塊兒呢?這個就是本算法的一個難點了!由於L的不定性,這個時候咱們須要考慮i-k+L和p-k+1的大小!那咱們就來分開來考慮。字符串

  (1)i-k+L<p-k+1,以下圖:get

  從上圖咱們能夠看到由於st[1...L]=st[i-k+1...i-k+L],st[i-k+1...p-k+1]=st[i...p],因此在st[i...p]中確定含有一段(從i開始)和st[1...L]是徹底相同的,也就是上圖標出來的藍色部分st[i...i+L-1]。由於st[i-k+1...p-k+1]=st[i...p]又由於i-k+L<p-k+1,因此咱們又能夠獲得:st[i-k+L+1]=st[i+L](也就是上圖所標的黃色位置),而由於extend[i-k+1]的定義,因此st[L+1](也就是上圖所標棕色位置)!=st[i-k+L+1],因此咱們能夠獲得:st[i+L]!=st[L+1],那麼extend[i]就直接等於L了。

  (2)i-k+L>p-k+1,以下圖:

 

  從上圖中咱們看到由於st[1...L]=st[i-k+1...i-k+L],又由於i-k+L>p-k+1,因此在st[1...L]中確定含有一段和st[i-k+1...p-k+1]徹底相同的(圖中綠色部分)。由於extend[k]的意義,因此st[p+1]!=st[p-k+2](圖中第二個棕色和黃色位置,爲何不相同不用解釋吧),又由於st[1...L]=st[i-k+1...i-k+L],因此st[p-i+2](圖中並未標出,第一個棕色位置)=st[p-k+2],那麼就會獲得:st[p-i+2]!=st[p+1],因此extend[i]就等於p-i+1了。

  (3)i-k+L=p-k+1,以下圖:

 

  從上圖咱們能夠發現由於st[i-k+1...i-k+L]=st[1..L],st[i-k+1...p-k+1]=st[i...p],又由於i-k+L=p-k+1,因此st[1...L]=st[i-k+1...p-k+1(可換成i-k+L)]=st[i...p](也就是指上圖中三段綠色部分)。那麼因爲extend[i-k+1]和extend[k]的意義表達,咱們能夠獲得:st[L+1]!=st[i-k+L+1],st[i-k+L+1]!=st[p+1],可是咱們不能肯定st[L+1]和st[p+1]是否相同,咱們只能肯定從i開始和從1開始有p-i+1這麼長的公共前綴但並不必定是最長的(這句話要好好理解,這很重要)。

  那麼咱們就設一個變量j=p-i+1,表示當前從i開始和從1開始的公共前綴長度,因爲上面加粗的那句話,咱們能夠直接從st[j+1]和st[i+j]來累加j的值來得出最長的公共前綴。

  注意事項:

  由於p是一個不定的數(由k和extend[k]來定),因此說有可能p-i+1是有可能爲負數,那麼第二種狀況顯然不對,公共前綴怎麼樣也不能等於負數吧,最小也會是0吧!這個時候也許你就會冒出一個想法,在第二種狀況下取p-i+1和0的最大值。很顯然這是不對的。請看下圖:

 

  從上圖咱們能夠發現,由於p-i+1是負數,因此上面全部條件都用不了,由於對i後面的字符沒有做任何的計算,但這個時候是絕對不能夠確定st[1]和st[i]是不一樣的(也就是最長公共前綴爲0),那麼咱們須要從st[1]和st[i]開始繼續判斷後面的字符是否相同。因而咱們把第二種狀況和第三種狀況概括爲一種狀況,由於二者都是要暴力處理未知的點,只是起始點不一樣

  這僅僅是求extend數組的證實,也僅僅是在同一個字符串裏的基本操做,接下來我就來說下擴展KMP(簡稱EXKMP)的實際用途。EXKMP主要是利用於解決處理兩個字符串的最長公共前綴長度,假如A是主串,B是副串,那麼這時咱們定義一個ex數組,ex[i]就表示A[i...Alen]和B[1...Blen]的最長公共前綴長度(這個概念須要好好注意)

  其實在處理二者的匹配時,只須要注意將A串中的子串轉移到B串中進行處理,那麼這樣咱們實際上在求ex數組時,操做仍與上面的步驟類似


參考代碼:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
char sa[1100000],sb[1100000];
int lena,lenb;
int p[1100000],ex[1100000];
//p數組是用來讓B串本身匹配本身的 
void exkmp()
{
    p[1]=lenb;
    int x=1;
    while(sb[x]==sb[x+1]&&x+1<=lenb) x++;//由於咱們p[1]是具備必定性,因此咱們不能直接用,因此要先暴力求出p[2] 
    p[2]=x-1;
    int k=2;
    for(int i=3;i<=lenb;i++)
    {
        int pp=k+p[k]-1,L=p[i-k+1];//pp其實是p 
        if(i+L<pp+1) p[i]=L;//i-k+L<pp-k+1化簡後i+L<pp 
        else
        {
            int j=pp-i+1;
            if(j<0) j=0;
            while(sb[j+1]==sb[i+j]&&i+j<=lenb) j++;
            p[i]=j;
            k=i;
        }
    }
    x=1;
    while(sa[x]==sb[x]&&x<=lenb) x++;//ex[1]並不具備必定性,因此咱們暴力求出ex[1] 
    ex[1]=x-1;
    k=1;
    for(int i=2;i<=lena;i++)
    {
        int pp=k+ex[k]-1,L=p[i-k+1];
        if(i+L<pp+1) ex[i]=L;
        else
        {
            int j=pp-i+1;
            if(j<0) j=0;
            while(sb[j+1]==sa[i+j]&&i+j<=lena&&j<=lenb) j++;
            ex[i]=j;
            k=i;
        }
    }
}
int main()
{
    scanf("%s%s",sa+1,sb+1);
    lena=strlen(sa+1);lenb=strlen(sb+1);
    exkmp();
    for(int i=1;i<lena;i++) printf("%d ",ex[i]);
    printf("%d\n",ex[lena]);
    return 0;
}
相關文章
相關標籤/搜索