DataStructure

KMP中的next数组

2019-08-06  本文已影响0人  雨落八千里

KMP算法首先要构造匹配子串的next数组。假设有两个字符串,一个是待匹配的字符串strText,一个是要查找的匹配子串strKey。现在我们要在strText中去查找是否包含strKey,用i来表示strText遍历到了哪个字符,用j来表示strKey匹配到了哪个字符。假如strText[i]!=strKey[j],那么strKey就要回到nxet[j]字符处。next[j] 中保存的是该失配字符的前一个字符在前面出现过的最近一次失配的字符后面的一个字符的位置
假如字符E失配,那它要返回字符3(D),有什么发现,E到D之间有ABC,D之前也有ABC。再假如字符6(C)失配,它返回字符2(C),发现6字符前有AB,字符2前也有AB。是不是失配前一个字符加上几个字符会等于这个字符的前缀呢!对的,next的数组就是记录j字符的前一个字符串的字符前缀与字符后缀最大相等长度。

next数组的构建


请仔细对比这两个图。
我们发现一个规律:
S[k] == S[j]时,
next[j+1] == next[j] + 1
其实这个是可以证明的:
因为在S[j]之前已经有S[0, k-1] == S[j-k , j-1]
(next[j] == k
这时候现有S[k] == S[j],我们是不是可以得到S[0 , k-1] + S[k] == S[j-k , j-1] + S[j]
即:S[0 , k] == S[j-k , j],即next[j+1] == k + 1 == next[j] + 1
void getnext(int len)
{
    int j=0;
    int k=-1;
    nxt[0]=-1;
    while(j<len)
    {
        if(k==-1||s[j]==s[k])
        {
            j++;
            k++;
            nxt[j]=k;
        }
        else
        {
            k=nxt[k];//窗口
        }
    }
}

哪next数组还有什么用呢?
next数组一直往前走,得到的所有前缀也是当前主串的后缀,当然了,也是当前主串的前缀。


如果到位置 i ,如果有 i%(i-next[i])==0 , 那说明字符串开始循环了,并且循环到 i-1 结束,为什么这样呢?
我们先假设到达位置 i-1 的时候,字符串循环了(到i-1完毕),那么如果到第i个字符的时候,失配了,根据next数组的求法,我们是不是得回溯?
然而回溯的话,由于字符串是循环的了(这个是假定的),next[i] 是不是指向上一个循环节的后面一个字符呢??是的,上一个循环节的末尾是 next[i]-1 ,然后现在循环节的末尾是 i-1 ,然么循环节的长度是多少呢?
所以,我们有 (i - 1) - ( next[i] - 1 ) = i - next[i] 就是循环节的长度(假设循环成立的条件下),但是我们怎么知道这个循环到底成立吗?
现在我们已经假设了 0————i-1 循环了,那么我们就一共有i 个字符了,如果有 i % ( i - next[i] ) == 0,总的字符数刚好是循环节的倍数,那么说明这个循环是成立的。当然,i%i=0,所以还要确保nxet[i]!=0;
  • 周期性字符串⇔ n % (n−next[n])==0 && next[n] != 0,循环节长度是n−next[n]
  • next数组往前跳的步长是一样的,除了最后一次。即i-next[i]

HDU-3746Cyclic Nacklace

将字符串弄成至少两个循环节(保证最后是循环节)至少要添加多少个字符。若本身有至少有两个循环节且最后是循环节结尾就输出0.

#include<bits/stdc++.h>
using namespace std;
const int M=101000;
char s[M];
int nxt[M];
void getnext(int len)
{
    int j=0;
    int k=-1;
    nxt[0]=-1;
    while(j<len)
    {
        if(k==-1||s[j]==s[k])
        {
            k++;
            j++;
            nxt[j]=k;
        }
        else
        {
            k=nxt[k];
        } 
    }
}
int main( )
{
    int t;
    scanf("%d",&t);
    getchar( );
    while(t--)
    {
        scanf("%s",s);
        int len=strlen(s);
        getnext(len);
        if(nxt[len]!=0)
        {
            int k=len-nxt[len];
            if(len%k==0&&len/k>=2)
            {
                printf("0\n");
            }
            else
            {
                printf("%d\n",k-len%k);
            }
            
        }
        else
        {
            printf("%d\n",len);
        }
        
    }
    return 0;
}

HDU-1358Period

输出以每个字符结尾能构成周期

#include<bits/stdc++.h>
using namespace std;
const int M= 1000000;
char s[M];
int nxt[M];
void getnext(int len)
{
    int j=0;
    int k=-1;
    nxt[0]=-1;
    while(j<len)
    {
        if(k==-1||s[j]==s[k])
        {
            j++;
            k++;
            nxt[j]=k;
        }
        else
        {
            k=nxt[k];
        }
    }
}
int main( )
{
    int n;
    int k=0;
    while(~scanf("%d",&n)&&n)
    {
        k++;
        getchar( );
        scanf("%s",s);
        getnext(n);
        printf("Test case #%d\n",k);
        for(int i=1;i<=n;i++)
        {
            if(i%(i-nxt[i])==0&&nxt[i]!=0)
            {
                printf("%d %d\n",i,i/(i-nxt[i]));
            }
        }
        printf("\n");
    }
    return 0;
}
上一篇下一篇

猜你喜欢

热点阅读