KMP模板,最小循環節

(能夠轉載,但請註明出處!)html

下面是有關學習KMP的參考網站算法

http://blog.csdn.net/yaochunnian/article/details/7059486數組

http://blog.csdn.net/v_JULY_v/article/details/6111565ide

http://blog.csdn.net/v_JULY_v/article/details/6545192函數

http://blog.csdn.net/oneil_sally/article/details/3440784學習

http://billhoo.blog.51cto.com/2337751/411486優化

 

先說說next數組的含義:網站

next[i]就是前面長度爲i的字符串前綴和後綴相等的最大長度,也即索引爲i的字符失配時的前綴函數。spa

下面幾個版本的next函數,除了next[0]不一樣外(版本一中爲-1,版本二中爲0),其他無差異.net

 

一:KMP算法模板

版本一:

//求str對應的next數組
void getNext(char const* str, int len)
{
    int i = 0;
    next[i] = -1;
    int j = -1;
    while( i < len )
    {
        if( j == -1 || str[i] == str[j] )   //循環的if部分
        {
            ++i;
            ++j;
            //修正的地方就發生下面這4行
            if( str[i] != str[j] ) //++i,++j以後,再次判斷ptrn[i]與ptrn[j]的關係
                next[i] = j;      //以前的錯誤解法就在於整個判斷只有這一句。
            else
                next[i] = next[j];  //這裏實際上是優化了後的,也能夠還是next[i]=j
            //當str[i]==str[j]時,若是str[i]匹配失敗,那麼換成str[j]確定也匹配失敗,
            //因此不是令next[i]=j,而是next[i] = next[j],跳過了第j個字符,
            //即省去了沒必要要的比較
            //非優化前的next[i]表示前i個字符中前綴與後綴相同的最大長度
        }
        else                                 //循環的else部分
            j = next[j];
    }
}

//在目標字符串target中,字符str出現的個數
//n爲target字符串的長度,m爲str字符串的長度
int kmp_match(char *target,int n,char *str,int m){
    int i=0,j=0;  //i爲target中字符的下標,j爲str中字符的下標
    int cnt=0;   //統計str字符串在target字符串中出現的次數
    while(i<=n-1){
        if(j<0||target[i]==str[j]){
            i++;
            j++;
        }
        else{
            j=next[j]; //當j=0的時候,suffix[0]=-1,這樣j就會小於0,因此一開始有判斷j是否小於0
        }

        //str在target中找到匹配
        if(j==m){
            cnt++;
            j=next[j];
        }
    }
    return cnt;
}
//在目標字符串target中,若存在str字符串,返回匹配成功的第一個字符的位置
int kmp_search(char *target,int n,char *str,int m){
    int i=0,j=0;  //i爲target中字符的下標,j爲str中字符的下標
    int cnt=0;   //統計str字符串在target字符串中出現的次數
    while(i<n && j<m){
        if(j<0||target[i]==str[j]){
            i++;
            j++;
        }
        else{
            j=suffix[j]; //當j=0的時候,suffix[0]=-1,這樣j就會小於0,因此一開始有判斷j是否小於0
        }
    }
    if(j>=m)
        return i-m;
    else
        return -1;
}
View Code

版本二(算法導論):

//這裏的next和前面同樣,next[i]就是前面長度爲i的字符串前綴和後綴相等的長度,
//即索引爲i的字符失配時的前綴函數
void getNext(char *str,int m){
    memset(next,0,sizeof(next));
    next[1]=0;
    int k=0;
    for(int i=2;i<=m;i++){
        while(k>0 && str[k]!=str[i-1])
            k=next[k];
        if(str[k]==str[i-1])
            k++;
        next[i]=k;
    }
}
//n爲target字符串的長度,m爲str字符串的長度,統計str在target中出現的個數
int match(char *target,int n,char * str,int m){
    int k=0,cnt=0;
    for(int i=0;i<n;i++){
        while(k>0 && str[k]!=target[i])
            k=next[k];
        if(str[k]==target[i])
            k++;
        if(k==m){
            cnt++;
            k=next[k];
        }
    }
    return cnt;
}

//n爲target字符串的長度,m爲str字符串的長度
//若存在str字符串,返回匹配成功的第一個字符的位置
int match(char *target,int n,char * str,int m){
    int k=0,cnt=0;
    for(int i=0;i<n;i++){
        while(k>0 && str[k]!=target[i])
            k=next[k];
        if(str[k]==target[i])
            k++;
        if(k==m){
            return i-m+1;
        }
    }
    return -1;
}
View Code

某大神的模板(其實和算法導論同樣):

#define KMP_GO(X) while(k>0 && P[k]!=X[i]) k=next[k];if(P[k]==X[i])k++
//求字符串P在T中出現的次數
int kmp_match(char*T,char*P){
    int n,m,next[10010],i,k,c;
    n=strlen(T);m=strlen(P);
    next[1]=k=0;
    for(i=1;i<m;i++){
        KMP_GO(P);
        next[i+1]=k;//這裏i表示的是字符的索引,對應的長度i+1
    }
    k=c=0;
    for(i=0;i<n;i++){
        KMP_GO(T);
        if(k==m){
            c++;
            k=next[k];
        }
    }
    return c;
}
View Code

 

二:KMP最小循環節、循環週期:

定理:假設S的長度爲len,則S存在最小循環節,循環節的長度L爲len-next[len],子串爲S[0…len-next[len]-1]。

(1)若是len能夠被len - next[len]整除,則代表字符串S能夠徹底由循環節循環組成,循環週期T=len/L。

(2)若是不能,說明還須要再添加幾個字母才能補全。須要補的個數是循環個數L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。

 

 

理解該定理,首先要理解next數組的含義:next[i]表示前面長度爲i的子串中,前綴和後綴相等的最大長度。

如:abcdabc

index

0

1

2

3

4

5

6

7

char

a

b

c

d

a

b

C

 

next

-1

0

0

0

0

1

2

3

 

如對於a,ab,abc,abcd,很明顯,前綴和後綴相同的長度爲0

  對於長度爲5的子串abcda,前綴的a和後綴的a相同,長度爲1

  對於長度爲6的子串abcdab,前綴的ab和後綴的ab相同,長度爲2

接下來舉幾個例子來講明最小循環節和循環週期:

爲方便說明,先設字符串的長度爲len,循環子串的長度爲L

1.

s0s1s2s3s4s5 ,next[6]=3

即s0s1s2=s3s4s5

很明顯可知:循環子串爲s0s1s2,L=len-next[6]=3,且能被len整除。

 

2.

s0s1s2s3s4s5s6s7 ,next[8]=6

此時len-next[8]=2 ,即L=2

由s0s1s2s3s4s5=s2s3s4s5s6s7

可知s0s1=s2s3,s2s3=s4s5,s4s5=s6s7

顯然s0s1爲循環子串

 

3.

s0s1s2s3s4s5s6 ,next[7]=4

此時len-next[7]=3,即L=3

由s0s1s2s3=s3s4s5s6

可知s0s1=s3s4,s2s3=s5s6

從而可知s0s1s2=s3s4s5,s0=s3=s6

即若是再添加3-4%3=2個字母(s1s2),那麼獲得的字符串就能夠由s0s1s2循環3次組成

 

這個定理能夠這麼理解:

http://www.cnblogs.com/oyking/p/3536817.html

對於一個字符串,如abcd abcd abcd,由長度爲4的字符串abcd重複3次獲得,那麼必然有原字符串的前八位等於後八位。

也就是說,對於某個字符串S,長度爲len,由長度爲L的字符串s重複R次獲得,當R≥2時必然有S[0..len-L-1]=S[L..len-1],字符串下標從0開始

那麼對於KMP算法來講,就有next[len]=len-L。此時L確定已是最小的了(由於next的值是前綴和後綴相等的最大長度,即len-L是最大的,那麼在len已經肯定的狀況下,L是最小的)。

 

若是必定仔細證實的話,請看下面:

(參考來自:http://www.cnblogs.com/wuyiqi/archive/2012/01/06/2314078.html,有所改動)

 k    m   x     j     i

由上,next【i】=j,兩段紅色的字符串相等(兩個字符串徹底相等),s[k....j]==s[m....i]

設s[x...j]=s[j....i](xj=ji)

則可得,如下簡寫字符串表達方式

kj=kx+xj;

mi=mj+ji;

由於xj=ji,因此kx=mj,以下圖所示

 k   m     a    x    j     i

設s[a…x]=s[x..j](ax=xj)

又由xj=ji,可知ax=xj=ji

即s[a…i]是由s[a…x]循環3次得來的。

並且看到沒,此時又重複上述的模型,s[k…x]=s[m…j],能夠一直遞推下去

最後能夠就能夠遞推出文章開頭所說的定理了。

 

最後再舉兩個相關例子

abdabdab  len:8 next[8]:5

最小循環節長度:3(即abd)   須要補的個數是1  d

ababa  len:5 next[5]:3

最小循環節長度:2(即ab)    須要補的個數是1  b

相關文章
相關標籤/搜索