简介
给定长度为n的文本串S
和长度为m的模式串T
,定义extend[i]为S[i…n-1]与T[0…m-1]的最长公共前缀长度,求extend[i]。当extend[x]==m时,则可知文本串S
中包含模式串T
,并且首位置为x,而这正是KMP算法处理的模式匹配问题。相较于KMP算法,扩展KMP算法能找到文本串S
中所有模式串T
的匹配,更一般地,可以知道文本串S
中以每个字符开始的后缀与模式串T
的最长公共前缀长度,时间复杂度O(n+m)。
扩展KMP
假设遍历到i时,已经求出extend[0…i-1]的值。我们记录下遍历过程中匹配成功的字符的最远位置为p,并且这次匹配的起始位置为a。换句话说,计算到x时,匹配成功的最远位置是x+extend[x]-1,p就是x=0…i-1得到的最大值,a就是对应的x。
设辅助数组next[i]表示T[i…m-1]和T[0…m-1]的最长公共前缀长度(注意这里与KMP算法中的next数组含义不同)。令len=next[i-a],讨论以下两种情况:
(1) i + len-1 < p
由next数组定义可知 T[0…len-1] == T[i-a…i-a+len-1],并且由extend数组定义可知 S[a…p] == T[0…p-a],得到 S[i…i+len-1] == T[i-a…i-a+len-1] ,所以 S[i…i+len-1] == T[0…len-1]。并且 S[i+len-1…p] != T[len-1…i-a],不然违背了next数组最长公共前缀长度的定义。于是,无需任何比较就可以得出extend[i] = len。
(2) i + len-1 >= p
我们可以看到,匹配最远的位置只到p,对于p之后的匹配结果是未知的,所以我们需要继续匹配S[p…n-1]和T[p-i…m-1]。匹配完之后,还需要更新a和p。
以上就是扩展KMP的主要思想,遍历即可,下面给出字符串下标从0开始的一种exKMP的实现,注意为了实现方便,代码中的p是上面分析的p+1,所以判断的时候是i+next[i-a]。而且同时用了变量j跟踪p在文本串S
与模式串T
中的对应位置,注意遍历文本串S
时,i在递增,j需要递减。
1void exKMP(char* s, char* t, int n, int m)
2{
3 int a, p;
4 for (int i=0, j=-1; i<n; i++, j--) //j即等于p与i的距离,其作用是判断i是否大于p(如果j<0,则i大于p)
5 {
6 if (j<0 || i+nxt[i-a] >= p)
7 {
8 if (j<0) p = i, j = 0; //如果i大于p
9 while (p<n && j<m && s[p]==t[j]) p++, j++;
10 extend[i] = j, a = i;
11 } else
12 extend[i] = nxt[i-a];
13 }
14}
next数组
根据next数组定义,next[i]表示T[i…n]和T[0…n]的最长公共前缀的长度,其实就相当于模式串T
与自身的匹配,不过这里next数组是从1开始的,next[0]匹配的肯定是整个串的长度m。有兴趣的可以试试写到一个函数中去,下面模板中给出分离的实现。再次强调C++中next
是关键字(迭代器的一个函数),可以换个名字避免 CE
。
模板
1char s[mxn], t[mxn];
2int nxt[mxn], extend[mxn];
3
4void getnxt(char* t, int m)
5{
6 int a, p; nxt[0] = m;
7 for (int i=1, j=-1; i<m; i++, j--)
8 {
9 if (j<0 || i+nxt[i-a] >= p)
10 {
11 if (j<0) p = i, j = 0;
12 while (p<m && t[p]==t[j]) p++, j++;
13 nxt[i] = j, a = i;
14 } else
15 nxt[i] = nxt[i-a];
16 }
17}
18
19void exKMP(char* s, char* t, int n, int m)
20{
21 int a, p;
22 for (int i=0, j=-1; i<n; i++, j--) //j即等于p与i的距离,其作用是判断i是否大于p(如果j<0,则i大于p)
23 {
24 if (j<0 || i+nxt[i-a] >= p)
25 {
26 if (j<0) p = i, j = 0; //如果i大于p
27 while (p<n && j<m && s[p]==t[j]) p++, j++;
28 extend[i] = j, a = i;
29 } else
30 extend[i] = nxt[i-a];
31 }
32}
33
34
35int main()
36{
37 scanf("%s%s", s, t);
38 getnxt(t, strlen(t));
39 exKMP(s, t, strlen(s), strlen(t));
40
41 for(int i=0; i<strlen(t); i++)
42 printf("%d ", nxt[i]);
43 printf("\n");
44
45 for(int i=0; i<strlen(s); i++)
46 printf("%d ", extend[i]);
47 printf("\n");
48
49 return 0;
50}