KMP与扩展KMP

原文转自:http://www.cppblog.com/MatoNo1/archive/2011/04/17/144390.aspx

KMP:给出两个字符串A(称为模板串)和B(称为子串),长度分别为lenA和lenB,要求在线性时间内,对于每个A[i] (0<=i<lenA),求出A[i]往前和B的前缀匹配的最大匹配长度,记为ex[i](或者说,ex[i]为满足A[i- z+1..i]==B[0..z-1]的最大的z值)。KMP的主要目的是求B是不是A的子串,以及若是,B在A中所有出现的位置(当 ex[i]=lenB时)。
【算法】
设next[i]为满足B[i-z+1..i]==B[0..z-1]的最大的z值(也就是B的自身匹配)。设目前next[0..lenB-1]与ex[0..i-1]均已求出,要用它们来求ex[i]的值。

据ex的定义,有A[i-1-ex[i-1]+1..i-1]==B[0..ex[i-1]-1],这时,若有A[i]==B[ex[i-1]],则可以
直接得到ex[i]=ex[i-1]+1(因为i-1-ex[i-1]+1即i-ex[i-1],现在由于A[i]==B[ex[i-1]],可得
A[i-ex[i-1]..i]==B[0..ex[i-1]],即A[i-ex[i-1]+1-1..i]==B[0..ex[i-1]+1-1],所
以ex[i]=ex[i-1]+1)。若A[i]!=B[ex[i-1]]?
设j=next[ex[i-1]-1],
则根据next定义得B[ex[i-1]-j..ex[i-1]-1]==B[0..j-1],又因为A[i-ex[i-1]..i-
1]==B[0..ex[i-1]-1]得A[i-j..i-1]==B[ex[i-1]-j..ex[i-1]-1],这样有A[i-j..i-1]==B[0..j-1]
也就是此时只需再比较A[i]与B[j]的值是否相等即可,若相等,可得ex[i]=j+1,若仍不相等,则更新j为next[j-1],继续比较
A[i]与B[j]是否相等……直到A[i]与B[j]相等或直到j==0时,A[i]仍不等于B[j],此时ex[i]=0。边界:求ex[0]时,初
始j(用来代替ex[i-1])为0。
现在还有一个问题,如何求next?显然next就是以B自身为模板串,B为子串的“自身匹配”,用类似的办法即可,唯一不同的是next[0]=lenB可以直接得到,求next[1]时,初始j(代替next[i-1])为0。

 1     lenA = strlen(A); lenB = strlen(B);
 2     next[0] = lenB;
 3     int j = 0;
 4     re2(i, 1, lenB) {
 5         while (j && B[i] != B[j]) j = next[j - 1];
 6         if (B[i] == B[j]) j++;
 7         next[i] = j;
 8     }
 9     j = 0;
10     re(i, lenA) {
11         while (j && A[i] != B[j]) j = next[j - 1];
12         if (A[i] == B[j]) j++;
13         ex[i] = j;
14     }

核心代码

扩展KMP:给出模板串A和子串B,长度分别为lenA和lenB,要求在线性时间内,对于每个A[i](0<=i< lenA),求出A[i..lenA-1]与B的最长公共前缀长度,记为ex[i](或者说,ex[i]为满足A[i..i+z-1]==B[0..z- 1]的最大的z值)。扩展KMP可以用来解决很多字符串问题,如求一个字符串的最长回文子串和最长重复子串。
【算法】
设next[i]为满足B[i..i+z-1]==B[0..z-1]的最大的z值(也就是B的自身匹配)。设目前next[0..lenB-1]与ex[0..i-1]均已求出,要用它们来求ex[i]的值。

p为目前A串中匹配到的最远位置,k为让其匹配到最远位置的值(或者说,k是在0<=i0<i的所有i0值中,使i0+ex[i0]-1的值
最大的一个,p为这个最大值,即k+ex[k]-1),显然,p之后的所有位都是未知的,也就是目前还无法知道A[p+1..lenA-1]中的任何一位
和B的任何一位是否相等。
根据ex的定义可得,A[k..p]==B[0..p-k],因为i>k,所以又有
A[i..p]==B[i-k..p-k],设L=next[i-k],则根据next的定义有B[0..L-1]==B[i-k..i-k+L-1]。
考虑i-k+L-1与p-k的关系:
(1)i-k+L-1<p-k,即i+L<=p。这时,由
A[i..p]==B[i-k..p-k]可以得到A[i..i+L-1]==B[i-k..i-k+L-1],又因为B[0..L-1]==B[i-
k..i-k+L-1]所以A[i..i+L-1]==B[0..L-1],这就说明ex[i]>=L。又由于next的定义可得,A[i+L]必
然不等于B[L](否则A[i..i+L]==B[0..L],因为i+L<=p,所以A[i..i+L]==B[i-k..i-k+L],这样
B[0..L]==B[i-k..i-k+L],故next[i-k]的值应为L+1或更大),这样,可以直接得到ex[i]=L!
(2)i+k-
L+1>=p-k,即i+L>p。这时,首先可以知道A[i..p]和B[0..p-i]是相等的(因为A[i..p]==B[i-
k..p-k],而i+k-L+1>=p-k,由B[0..L-1]==B[i-k..i-k+L-1]可得B[0..p-i]==B[i-
k..p-k],即A[i..p]==B[0..p-i]),然后,对于A[p+1]和B[p-i+1]是否相等,目前是不知道的(因为前面已经说过,p
是目前A串中匹配到的最远位置,在p之后无法知道任何一位的匹配信息),因此,要从A[p+1]与B[p-i+1]开始往后继续匹配(设j为目前B的匹配
位置的下标,一开始j=p-i+1,每次比较A[i+j]与B[j]是否相等,直到不相等或者越界为止,此时的j值就是ex[i]的值)。在这种情况
下,p的值必然会得到延伸,因此更新k和p的值。
边界:ex[0]的值需要预先求出,然后将初始的k设为0,p设为ex[0]-1。
对于求next数组,也是“自身匹配”,类似KMP的方法处理即可。唯一的不同点也在边界上:可以直接知道next[0]=lenB,next[1]的值预先求出,然后初始k=1,p=ex[1]。


要严重注意的是,在上述的情况(2)中,本该从A[p+1]与B[p-i+1]开始匹配,但是,若p+1<i,也就是p-i+1<0(这种情
况是有可能发生的,当ex[i-1]=0,且前面的ex值都没有延伸到i及以后的时候)的话,需要将A、B的下标都加1(因为此时p必然等于i-2,如果
A、B的下标用两个变量x、y控制的话,x和y都要加1)!!

 1 lenA = strlen(A); lenB = strlen(B);
 2     next[0] = lenB; next[1] = lenB - 1;
 3     re(i, lenB-1) if (B[i] != B[i + 1]) {next[1] = i; break;}
 4     int j, k = 1, p, L;
 5     re2(i, 2, lenB) {
 6         p = k + next[k] - 1; L = next[i - k];
 7         if (i + L <= p) next[i] = L; else {
 8             j = p - i + 1;
 9             if (j < 0) j = 0;
10             while (i + j < lenB && B[i + j] == B[j]) j++;
11             next[i] = j; k = i;
12         }
13     }
14     int minlen = lenA <= lenB ? lenA : lenB; ex[0] = minlen;
15     re(i, minlen) if (A[i] != B[i]) {ex[0] = i; break;}
16     k = 0;
17     re2(i, 1, lenA) {
18         p = k + ex[k] - 1; L = next[i - k];
19         if (i + L <= p) ex[i] = L; else {
20             j = p - i + 1;
21             if (j < 0) j = 0;
22             while (i + j < lenA && j < lenB && A[i + j] == B[j]) j++;
23             ex[i] = j; k = i;
24         }
25     }

核心代码

【时间复杂度分析】
在KMP和扩展KMP中,不管是A串还是B串,其匹配位置都是单调递增的,故总时间复杂度是线性的,都为O(lenA + lenB)(只是扩展KMP比KMP的常数更大一些)。
【应用】
KMP和扩展KMP在解决字符串问题中有大用。很多看上去很猥琐的字符串问题,都可以归结到这两种算法之中。另外,这里的“字符串”可以延伸为一切类型的数组,而不仅仅是字符数组。

时间: 2024-11-07 08:03:16

KMP与扩展KMP的相关文章

KMP和扩展KMP【转】

这种东西基本上在纸上自己推导一下就能做出来XD 转发注明出处 KMP:给出两个字符串A(称为模板串)和B(称为子串),长度分别为lenA和lenB,要求在线性时间内,对于每个A[i] (0<=i<lenA),求出A[i]往前和B的前缀匹配的最大匹配长度,记为ex[i](或者说,ex[i]为满足A[i- z+1..i]==B[0..z-1]的最大的z值).KMP的主要目的是求B是不是A的子串,以及若是,B在A中所有出现的位置(当 ex[i]=lenB时).[算法]设next[i]为满足B[i-z

KMP与扩展KMP初探

KMP KMP算法主要用于字符串匹配中的单串匹配 next函数:表示当前字符失配时,应从模式串的第几位开始匹配(越大越好).即模式串的前缀与以t[i]为结尾的后缀的最长相同部分的长度. 代码如下(pascal) var s,t:string; next,ans:array[0..100] of longint; i,j:longint; begin readln(s); readln(t); next[1]:=0; {构造next} j:=0; for i:=2 to length(t) do

KMP、扩展KMP、Manacher模板

推导过程推荐看这篇: KMP模板: 1 void init(){ 2 int j = nex[0] = -1, i = 0; 3 int len = strlen(str); 4 while(i < len){ 5 if(j == -1 || str[i] == str[j]){ 6 nex[++i] = ++j; 7 }else j = nex[j]; 8 } 9 } 10 int KMP(){ 11 int i = 0, j = 0, sum = 0; 12 int len = strlen

KMP、扩展KMP、MANACHER

一.KMP 作用:用于在一个文本串S内查找一个模式串P出现的位置 string str = "bacbababadababacambabacaddababacasdsd"; string ptr = "ababaca"; 如上图,可得在第10与26处包含ptr数组: 暴力做法:暴力for,碰到不一样的直接返回,从后一个开始继续for,最差能到O(n * m) KMP 做法: 主要的思路是跳,比如你一开始从上面例子里的bacbababadababacambabacad

字符串专题一:KMP与扩展KMP

KMP算法主要用于解决单模式串的匹配问题,即:给定主串s和模式串p,问p是否是s的子串(len(s)<=N, len(p)<=M). 先考虑最朴素的算法,即枚举s中的起点i,检查s[i..i+M-1]是否等于p,这样的时间复杂度为O(NM). 分析一下为什么这样的算法效率低(建议读者手动画个图):设指针i和j分别指向s和p中的字符,不妨假定s[0..k-1]和p[0..k-1]已经匹配上了,而s[k]!=p[k](k<M),这说明s[0..M-1]已经不能匹配上p了,根据朴素算法,指针i

HDU 4333 Revolving Digits 扩展KMP

链接:http://acm.hdu.edu.cn/showproblem.php?pid=4333 题意:给以数字字符串,移动最后若干位到最前边,统计得到的数字有多少比原来大,有多少和原来相同,有多少比原来的小. 思路:拓展KMP中的next数组标记的是子串和母串的公共前缀的长度,要将字符串长度变成原来二倍,这样如果变换后不是完全相同的数字也即公共前缀长度大于等于字符串长度,那么字母串公共前缀的下一位的大小比较就是题目所要求的比较.由于相同的数字串只算一次,则只要统计比较第一个"循环节"

扩展KMP复习小记

简介 KMP大家都耳熟能详,扩展KMP只是一个扩展版而已,字面意思啦! 我记得以前打过这个复习小记的,但是不知为何失踪了. KMP与扩展KMP的对比 KMP的next[i]表示从1到i的字符串s,前缀和后缀的最长重叠长度. EXKMP的next[i]表示从1到i的字符串s,和从i到n的字符串st的最长重叠长度. 也就是说KMP是向前的匹配,EXKMP是向后匹配. 扩展KMP问题是KMP问题的补充和加难. 具体内容 重要数组 给定母串S,和子串T.定义n=|S|, m=|T|. extend[i]

【扩展kmp+最小循环节】HDU 4333 Revolving Digits

http://acm.hdu.edu.cn/showproblem.php?pid=4333 [题意] 给定一个数字<=10^100000,每次将该数的第一位放到放到最后一位,求所有组成的不同的数比原数小的个数,相等的个数,大的个数 [思路] 这个数很大,用字符串处理 比较两个字符串的大小,一位一位很耗时,可以求出最长公共前缀,只比较最长公共前缀后一位 每次将数的最后一位放到最后一位,如abcd变成dabc,cdab,bcda,相当于abcdabcd各个后缀的前四位 这样就变成了求abcdabc

acdream1116 Gao the string!(扩展KMP)

今天是字符串填坑的一天,首先填的第一个坑是扩展KMP.总结一下KMP和扩展KMP的区别. 在这里s是主串,t是模式串. KMP可以求出的是以s[i]为结尾的串和 t前缀匹配的最长的长度.假如这个长度是L的话,则: s[i-L+1...i]=t[0...L] 而所谓的失配指针f[i]指的就是当前i点失配时要匹配的长度,实际是用t文本串去匹配t. 扩展KMP则是以s[i]为起始的串和 t前缀匹配的最长的长度. 假如这个长度的话,则: s[i..i+L-1]=t[0...L] 扩展KMP里的nxt数组