博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
KMP模板,最小循环节
阅读量:4939 次
发布时间:2019-06-11

本文共 4500 字,大约阅读时间需要 15 分钟。

(可以转载,但请注明出处!)

下面是有关学习KMP的参考网站

 

先说说next数组的含义:

next[i]就是前面长度为i的字符串前缀和后缀相等的最大长度,也即索引为i的字符失配时的前缀函数。

下面几个版本的next函数,除了next[0]不同外(版本一中为-1,版本二中为0),其余无差别

 

一: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
=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
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
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
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次组成

 

这个定理可以这么理解:

对于一个字符串,如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是最小的)。

 

如果一定仔细证明的话,请看下面:

(参考来自:,有所改动)

 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

转载于:https://www.cnblogs.com/chenxiwenruo/p/3546457.html

你可能感兴趣的文章
Eclipse CDT 出现 launch failed Binary not found
查看>>
apache jmeter
查看>>
Linux 基本命令
查看>>
RedHat7.0 网络源的配置
查看>>
(Mark)JS中关于闭包
查看>>
流程结构图
查看>>
ios端web app在键盘升起后缩小view防止界面仍可上下滑动
查看>>
从service弹出系统级自定义提示框,可在任意页面弹出
查看>>
Bootstrap简单介绍
查看>>
字典序最小问题
查看>>
iOS Touch ID 身份认证
查看>>
springboot 注解笔记
查看>>
图解HTTP---------------------------------------4
查看>>
hibernate实体类配置文件问题(字段使用默认值)
查看>>
rsync+inotify脚本
查看>>
LeetCode 860.柠檬水找零(C++)
查看>>
文件上传
查看>>
(Problem 92)Square digit chains
查看>>
HDU 2612 Find a way BFS,防止超时是关键
查看>>
0809
查看>>