zoukankan      html  css  js  c++  java
  • kmp总结及其应用

    kmp含义

      克努斯-莫里斯-普拉特算法,一种字符串查找算法。

      字符串算法主要是用于主串 S( s1,s2,s3,...,sn ), 模式串T( t1,t2,...,tm ), 之间的匹配问题. 

      相对与模式匹配O(n^2)而言: 当 Si != Tj  失配时, 主串下标i不回溯, 而是将模式串下标j回溯到合适的地方,再继续比较 Tj ,Si.

    时间复杂度极端情况是 O(N*M), 但是一般情况下总能保证O(N+M).

      假定串 S( i-j+1, i ) 与 模式串 T( 1, j ) 匹配时, Si != Tj 不匹配,此时需j最短回溯到 k,

      则存在  T(1,k-1) = T( j-k+1, j-1 ), 此时 k = next[j], 再令 Si 与 Tk 比较.

      则我们得出 next[] 的定义:

        next[i] =  0,  当 i = 0

        next[i] =  Max{ k | 1 < k < j, 且 T(1,k-1) = T(j-k+1,j-1),当此集合不空时 }

        next[i] =  1, 其它情况.

     1 int kmp( char *S, char *T ){ // 主串S,模式串T, 下标皆从1开始.
     2     int la = strlen(S), lb = strlen(T);
     3     int i = 1, j = 1;
     4     while( i <= la && j <= lb ){
     5         if( j == 0 || S[i] == T[j] ) i++, j++;
     6         else    j = next[j]; //模式串向前滑动到 nxt[j]位置,继续比较
     7     }
     8     if( j > lb ) return i-j; //匹配成功,返回最初匹配点
     9     return -1; //匹配失败
    10 }

    next数组 

      next函数,表示对于模式串而言,其最长的前缀与后缀相同的长度.

      有定义知道 next[1] = 0;

      设 next[j] = k, 这表明在模式串中存在下列关系

        T( 1, k-1 ) = T( j-k+1, j-1 )

      此时 next[ j+1 ]的取值有两种情况:

        1. 当 T[k] == T[j] 时, 此时有  T( 1,k ) = T( j-k+1, j ), 则此时  next[ j+1 ] = next[j] + 1

        2. 但 T[k] == T[j] 时, 此时可把求 next函数值的问题看作是一个模式匹配的问题.整个模式串既是主串又是模式串.

    按照前面主串与模式串匹配的思路, 则当 T[k] != T[j] 时, 应将模式串下标 k滑动到 next[k]时, 再与 T[j] 比较, 

        最终可能出现两种情况:

            1. 匹配到, 此时 next[ j+1 ] = next[ k` ] + 1;

            2. 一直无法匹配则最后会得到, next[ j+1 ] = 1.

     1 void GetNext( char *T, int *nxt ){
     2     int len = strlen(T);
     3     int i = 0, j = 1;
     4     nxt[1] = 0;
     5     while( j <= len ){
     6         if( i == 0 || T[i] == T[j] )
     7             nxt[ ++j ] = ++i;
     8         else i = nxt[i];
     9     }
    10 }      

      

    应用模型

      1. 模式串是否在主串中出现.    

         poj 3080 Blue Jeans   

        枚举其中一个串的主串,然后与其他串进行KMP匹配即可. 此题细节处理使用了STL.string.substr( 起点l, 数量num ).  

    View Code
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    
    char str[15][100];
    int n, next[100];
    string res;
    bool flag;
    
    void GetNxt( string T, int *nxt, int len ){
        int i = 0, j = 1;
        while( j <= len ){
            if( i == 0 || T[i-1] == T[j-1] ) 
                nxt[++j] = ++i;
            else    i = nxt[i];
        }
    }
    bool kmp(char *S, string T){
        int la = strlen(S), lb = T.size();
        int i = 1, j = 1;
        GetNxt( T, next, lb );
        while( i <= la && j <= lb ){
            if( j == 0 || S[i-1] == T[j-1] ) i++, j++;
            else j = next[j];
            if( j > lb ) return true;
        }
        return false;
    }
    void solve(){
        flag = false;    
        string st = str[0], tmp;    
        for(int L = 60; L >= 3; L--){
            for(int i = 0; i+L <= 60; i++){    
                tmp = st.substr(i,L);
                bool a = true;    
                for(int k = 1; k < n && a; k++)
                    if(  kmp( str[k], tmp ) == false ) a = false;
                if( a == true ){
                    if( flag == false ) flag = true, res = tmp;
                    if( res > tmp ) res = tmp;
                }    
            }
            if( flag ) return;    
        }
    }
    int main(){
        int T;    
        scanf("%d", &T);
        while( T-- ){
            scanf("%d", &n );
            for(int i = 0; i < n; i++)
                scanf("%s", str[i] );
            solve();
            if( flag == false ) puts("no significant commonalities");
            else printf("%s\n", res.c_str() );
        }
        return 0;
    }

          poj 3450 Corporate Identity

        同上题差不多.但是这题 N达到了4000,串长度为200, 暴力肯定不行,二分枚举长度,然后进行匹配. 

    View Code
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<string>
    #include<algorithm>
    using namespace std;
    const int N = 4010;
    
    char str[N][210];
    int n, next[N], Len[N];
    string res, st;
    bool flag;
    
    void GetNxt(string T,int *nxt, int len){
        int i = 0, j = 1; nxt[1] = 0;
        while( j <= len ){
            if( i == 0 || T[i-1]==T[j-1] ) 
                nxt[++j] = ++i;
            else i = nxt[i];
        }
    } 
    bool kmp( char *S, string T, int la, int lb ){ 
        int i = 1, j = 1; GetNxt(T,next,lb);
        while( i <= la && j <= lb ){
            if( j == 0 || S[i-1] == T[j-1] ) i++, j++;
            else    j = next[j];
            if( j > lb ) return true;    
        }
        return false;
    }
    bool find( int L ){
        string st = str[0],tmp;    
        for(int i = 0; i+L <= Len[0]; i++){
            tmp = st.substr( i, L );
            bool f = true;    
            for(int k = 1; k < n && f; k++)
                if( kmp( str[k], tmp, Len[k], L ) == false ) f = false; 
            if( f ) return true;
        }    
        return false;
    }
    void solve(){
        flag = false;
        int l = 0, r = Len[0], maxlen = -1;
        while( l < r ){    
            int m = (r+l)>>1;
            if( find(m) ) maxlen = m, l = m+1;
            else r = m;    
        
        }    
        if( maxlen != -1 ){
            string tmp, st = str[0]; l = maxlen;
            for(int i = 0; i+l <= Len[0]; i++){
                tmp = st.substr( i, l );
                bool f = true;
                for(int k = 1; k < n && f; k++)
                    if( !kmp( str[k], tmp, Len[k], l) ) f = false;
                if( f ){
                    if(flag ==false) flag=true, res = tmp;
                    if( res > tmp ) res = tmp;
                }
            }
        }
    }
    int main(){
        while( scanf("%d", &n), n ){
            for(int i = 0; i < n; i++){
                scanf("%s", str[i] ); Len[i] = strlen(str[i]);
            }
            solve();
            if( flag ) printf("%s\n", res.c_str() );
            else puts("IDENTITY LOST");
        }    
        return 0;
    }

        poj 1226 Substrings     

        本质还是一样求模式串在主串中是否出现. 拿一个串从大到小暴力分解子串. 与其他原串与inverse串匹配.

    View Code
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<string>
    #include<algorithm>
    using namespace std;
    
    const int N = 110;
    
    char str[120][N];
    string bap[120];
    int n, m, minlen;
    int Len[120], next[120];
    
    void GetNxt(const char *T, int len){
        int i = 1, j = 0; next[1] = 0;
        while( i <= len ){
            if( j == 0 || T[i-1]==T[j-1] )
                next[++i] = ++j;
            else j = next[j];
        } 
    } 
    bool kmp(const char *S,int la,const char *T,int lb){
        int i = 1, j = 1; GetNxt(T,lb);
        while( i<=la && j<=lb ){
            if( j == 0 || S[i-1] == T[j-1] ) i++,j++;
            else    j = next[j];
            if( j > lb ) return true;    
        }
        return false;
    }
    int solve(){
        string st = str[0];
        for(int L = minlen; L >= 1; L-- ){
            for(int i = 0; i+L <= Len[0]; i++){
                bool find = true;
                string tmp = st.substr( i, L );
                for(int j = 1; j < n && find; j++){
                    if( !kmp(str[j],Len[j],tmp.c_str(),L) && !kmp(bap[j].c_str(),Len[j],tmp.c_str(),L) )
                        find = false;
                }
                if(find) return L;    
            } 
        }
        return 0;
    }
    int main(){
        int T;
        scanf("%d", &T);
        while( T-- ){
            scanf("%d", &n);
            scanf("%s", str[0] );    
            minlen = (Len[0]=strlen(str[0]));    
            for(int i = 1; i < n; i++){
                scanf("%s", str[i] );        
                bap[i] = str[i];
                Len[i] = strlen(str[i]);    
                minlen = min( minlen, Len[i] );    
                reverse( bap[i].begin(), bap[i].end() );
            }
            int d = solve();
            printf("%d\n", d );
        }
        return 0;
    }

         poj 2541 Binary Witch

        这一题还是暴力过去的.不过据说有 dp(i,j)的状态压缩, 字符逆序处理,然后KMP.string.substr挺管用..

    View Code
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #include<string>
    using namespace std;
    const int N = (int)1e6+1100;
    
    char str[N];
    int n, m;
    int next[N];
    
    void GetNxt(string T,int *nxt, int len){
        int i = 1, j = 0; nxt[1] = 0;
        while( i <= len ){
            if( j == 0 || T[i-1]==T[j-1] )
                nxt[++i] = ++j;
            else j = nxt[j];
        }
    }
    int kmp(string S, int la, string T, int lb){
        int i = 1, j = 1; GetNxt(T,next,lb);
        while( i <= la && j <= lb ){
            if( j == 0 || S[i-1] == T[j-1] )
                i++, j++;
            else j = next[j];
            if( j > lb ) return i-j;    
        }
        return -1;    
    } 
    int main(){
        while( scanf("%d%d", &n,&m) != EOF){
            scanf("%s", str);
            int start = n;    
            for(int i = 0; i < m; i++){
                string s = str;                
                reverse( s.begin(), s.end() );    
                bool find = false;    
                for(int L = min(13,n); L >= 1 && !find; L-- ){
                    int la = n-1, lb = L;    
                    string s1 = s.substr(1,la), t1 = s.substr(0,lb);
                //    printf("s1 = %s, t1 = %s\n", s1.c_str(), t1.c_str() );    
                    int d = kmp( s1, la, t1, lb );
                    if( d != -1 ) find = true, str[n++] = s[d];    
                }    
                if( find == false ) str[n++] = '0';
                str[n] = '\0';    
                //printf("str = %s\n", str);    
            }    
            for(int i = start; i < n; i++) printf("%c",str[i]);    
        }    
        return 0;
    }

      2. 模式串在主串中的出现次数.   

        poj 3461 Oulipo 

        因为next函数值意义为最长的前缀与后缀相同长度. 当模式串Tj与主串Si 在 (i,j)匹配完成,此时下一个可能出现的匹配的起始位置为 (i+1,lenS) , 若我们使主串下标i回溯时,则会使时间复杂度达到O(N*M), 因为是要找与模式串相同的. 则我们只需要令j = next[j], 此时 T( 1, nxt[j]-1 ) = S( i-nxt[j]+1, i-1 ) , 表示其最长的前缀和后缀,此时i就无需回溯,然后继续匹配.统计次数即可.

        核心点是主串下标不回溯, 并利用 next函数意义(最长的相同前缀和后缀)

    View Code
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    
    const int N = (int)1e6+10;
    
    char s1[N], s2[10010];
    int next[10010];
    
    void GetNxt( char *T, int *nxt, int len ){
        int i = 0, j = 1; nxt[1] = 0;
        while( j <= len ){
            if( i == 0 || T[i-1] == T[j-1] )
                nxt[++j] = ++i;
            else i = nxt[i];
        }
    }
    int kmp( char *S, char *T ){
        int la = strlen(S), lb = strlen(T), cnt = 0;
        GetNxt( T, next, lb );
        int i = 1, j = 1;
        while( i <= la && j <= lb ){
            if( j == 0 || S[i-1] == T[j-1] )
                i++, j++;
            else j = next[j];
            if( j > lb ) cnt++, j = next[j];    
        }
        return cnt;
    }
    
    int main(){
        int T;
        scanf("%d", &T);
        while( T-- ){
            scanf("%s", s2);
            scanf("%s", s1);
            printf("%d\n", kmp( s1, s2 ) );
        }
        return 0;
    }

        poj 3167 Cow Patterns  有点难度.

        这题是求一模式串 与 主串的相对大小匹配,所有位置. 

        如果给我们的是绝对大小,那么我们就能用 poj 3461的解法,每次匹配到了再令j = next[j] 即可,得出所有匹配位置.

    而对于相对大小,我们需要使用到一个结论: 

        两个偏序序列, 对于其每一位, 其前面比起小的数量,和与其相等的数量, 都相等, 则两个偏序序列相同. (小,和等于都一样,则大于也一样- -..)

    利用这个结论,我们就可以判定快速判定两个偏序序列是否相同.  从宏观的角度上看,  还是一样对 模式串求个next函数,然后再对 模式串与主串kmp匹配.

        这里比较特殊的地方, 就在于, 两个值的比较,  根据定义, (1,k) = (i-k+1,i) 时,  next[ i+1 ] = k+1 .  模式串中的总是用的前缀,而主串中一直用的后缀.

    那么我们就可以预处理出 模式串的 m1(小于数量), m2(等于数量),  对于主串则使用 树状数组来维护, 当失配时,则为维护树状数组.具体如下.

        若当前模式串 T(1,j) 与主串S( i-j+1, i ) 比较时,  Tj != Si,  此时失配, 需要令 j = next[j] 再进行匹配. 模式串我们预处理了前缀.可以O(1)得出.无需处理.

    而,对于主串而言,  前面的树状数组中存放的元素是, ( i-j+1, i ),  当令 j = next[j], 再与 Si比较时, 此时 树状数组中 应该存放序列  S( i-next[j]+1, i ) , 那么我们就

    需要手动的删除掉 S( i+j-1, i-next[j] ) 这一段.   对于模式串自身求next函数,操作一样.

    View Code
    //poj 3167 kmp + binary index tree
    //yefeng1627
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    const int N = (int)1e5+10;
    const int K = (int)3e4+10;
    
    int a[N], b[K], c[30];
    int nxt[K], m1[K], m2[K];
    int n, k, S;
    int cnt, res[N];
    
    void add(int x,int v){
        while(x<30) c[x]+=v, x+=(x&(-x));
    }
    int sum(int x){
        int res = 0;
        while(x>=1) res += c[x],x-=(x&(-x));
        return res;
    }
    void GetNxt(){
        memset( c, 0, sizeof(c));
        int i = 1, j = 0; nxt[1] = 0;
        while( i <= k ){
        //    printf("i:%d,j:%d b-1=%d, b=%d\n", i,j, sum(b[i]-1),sum(b[i]) );    
            if( j == 0 || (sum(b[i]-1)==m1[j]&&sum(b[i])==m2[j]) )
            {    nxt[++i] = ++j; if(i<=k) add(b[i],1); }
            else{
                for(int x = i-j+1; x <= i-nxt[j]; x++) add(b[x],-1);
                j = nxt[j];
            }        
        }
        //printf("k = %d, i = %d\n", k, i );    
        //for(i = 1; i <= k+1; i++)
        //    printf("%d ", nxt[i] ); puts("");
    }
    void kmp(){
        cnt = 0; GetNxt();
        int i = 1, j = 1;
        memset(c,0,sizeof(c));    
        add(a[1],1);
        while( i<=n && j<=k ){
            if( j == 0 || (sum(a[i]-1)==m1[j]&&sum(a[i])==m2[j]) ){
                ++i,++j; if(i<=n) add(a[i],1);
            }    
            else{
                for(int x = i-j+1; x <= i-nxt[j]; x++) add(a[x],-1);
                j = nxt[j];
            }
            if( j > k ){
        //        printf("i = %d, k = %d\n", i, k);    
                res[cnt++] = i-k;
                for(int x = i-j+1; x <= i-nxt[j]; x++) add(a[x],-1);
                j = nxt[j];
            }    
        }
    }
    int main(){
        while( scanf("%d%d%d", &n,&k,&S) != EOF){
            for(int i = 1; i <= n; i++) scanf("%d", &a[i] );
            memset(c,0,sizeof(c));
            for(int i = 1; i <= k; i++){
                scanf("%d", &b[i] );
                add( b[i], 1 );
                m1[i] = sum(b[i]-1),m2[i] = sum(b[i]);    
            //    printf("i:%d, m1 = %d, m2 = %d\n", i, m1[i], m2[i] );    
            }    
            kmp();
            printf("%d\n", cnt );    
            for(int i = 0; i < cnt; i++)            
                printf("%d\n", res[i] );
        }        
        return 0;
    }

       3. 求循环节长度 / 最小覆盖子串长度    图形介绍 http://blog.csdn.net/fjsd155/article/details/6866991

        poj 2406 Power Strings

        kmp的nxt函数过程,会将模式串一个周期一个周期的构造,  对于 (i+1) - nxt[ i+1 ]  (因为我们是通过 Ti与Tj 得到nxt[i+1]的),

        即是其周期长度, 当 目前总长度 i % { (i+1)-nxt[i+1] } = 0, 时, 则意味着最后一个周期构造完成,  否则 i % { (i+1)-nxt[i+1] }表示目前最后一个周期串已构造出了多少个.

        poj 2185 Milking Grid  有点难度,且题意不是很好懂.

        这题所指的最小覆盖长度,其实就是最小循环周期长度.当然并非是完成循环,换句话说是 单元串a,重复k次可以覆盖str, 其中streln(a*k) >= strlen(str),

        并且我们知道 N-next(N)是最小覆盖长度, 之后的 j = next( next(N) )逐渐增大, 解决此题的思路是:

        首先处理宽度width, 寻找所有行都有的最小覆盖宽度 w`, 极端情况是 c. 因为每个串都能覆盖本身.

        之后在将 r长度为c的串(1,c). 截断成  r个长度为width的串(1,width), 然后对这c个串进行一个HASH值.得到一个数组key[C].

        然后对这个数组求一个next函数, 高度 high 即为 C - next(C),   

    View Code
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    
    const int N = (int)1e6+10;
    char s[N];
    int nxt[N];
    
    int main(){
        while( scanf("%s", s) != EOF ){
            if( s[0] == '.' ) break;    
            int len = strlen(s);
            int i = 0, j = 1; nxt[1] = 0;
            while( j <= len ){
                if( i == 0 || s[i-1] == s[j-1] )
                    nxt[++j] = ++i;
                else i = nxt[i];
            }    
            if( len%(len+1-nxt[len+1]) ) puts("1");
            else printf("%d\n", len/(len+1-nxt[len+1]) );
        }    
        return 0;
    }

         poj 1961 Period

         对于模式串本身求next函数值时,其实其是一个一个周期在构造串, N-next(N)表示串的循环周期, 而N%(N-next(N))即为最后一个周期已构造串的数量.

    View Code
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<string.h>
    #include<algorithm>
    using namespace std;
    
    const int N = (int)1e6+10;
    int n, nxt[N];
    char s[N];
    
    int main(){
        int Case = 1;
        while( scanf("%d",&n), n ){
            scanf("%s", s);
            int len = strlen(s);
            int i = 1, j = 0; nxt[1] = 0;
            while( i <= len ){
                if( j == 0 || s[i-1] == s[j-1] )
                    nxt[++i] = ++j;
                else j = nxt[j];
            } 
            printf("Test case #%d\n", Case++);    
            for(int i = 2; i <= len; i++){
                if( (i%(i+1-nxt[i+1])==0) && (nxt[i+1]>1) )
                    printf("%d %d\n", i, i/(i+1-nxt[i+1]) );        
            }    
            puts("");    
        }
        return 0;
    }

       4. 求串的 前缀最大长度, 且其前缀与后缀相同.  (最大前缀与后缀)

        poj 2752 Seek the Name

        对于串T(1,i), 我们考虑 next函数定义

        next[ i ] = Max{ k | 1 < k < i  && T( 1,k-1 ) = T( i-k+1, i-1 ) 且集合不为空,   } ,  则可以知道,  

    字串(1,next[ i-1 ]) 即为串 T(1,i-1) 的最大前缀与后缀.  此时 再考虑串 [1,next[i-1] ]的最大前缀与后缀,

    如此反复,直到 i = 0 结束. 因为定义 k < i,  其实其本身 (1,i)也是其最大前缀和后缀. 逆序输出即可.

        再重复说明下, kmp的next函数值是通过比较 Ti 与 Tj , 若 Ti = Tj ,则 next[ j+1 ] = i+1, 所以,

    我们要获取i位置的最后匹配位置,则需要用next[ i+1 ], 因为其包含了 Ti = T[ next[i+1] - 1 ]. 

    View Code
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    const int N = 400010;
    char s[N];
    int res[N], nxt[N];
    
    int main(){
        while( scanf("%s", s) != EOF){
            int len = strlen(s);
            int i = 0, j = 1; nxt[1] = 0;
            while( j <= len ){
                if( i == 0 || s[i-1] == s[j-1] ) 
                    nxt[++j] = ++i;
                else i = nxt[i];
            }
            int cnt = 0, x = nxt[len+1];
            res[cnt++] = len;
            while( x > 1 ) { res[cnt++] = x-1; x = nxt[x]; } 
            for(int i = cnt-1; i >= 0; i--)
                printf( i == 0 ? "%d" : "%d ", res[i] ); puts("");    
        }    
        return 0;
    }

      

      

  • 相关阅读:
    jq的stop
    mouseover,mouseout与mouseenter,mouseleave
    jq的load
    KeyUp 和KeyDown 、KeyPress之间的区别
    jq的error
    $(function() {....}) ,(function($){...})(jQuery)
    delegate事件委托
    将项目提交到git
    linux下安装jenkins
    手写简单的linkedlist
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/3050027.html
Copyright © 2011-2022 走看看