原文
代码预览
题目来源:P3375 【模板】KMP字符串匹配
#include <cstdio>
#include <cstring>
char p[1000005], t[1000005];
int len1, len2;
int next[1000005], next2[1000005];
void do_next()
{
next[0] = 0;
int i = 1;
int len = 0;
while (i < len2)
{
if (t[i] == t[len])
next[i++] = ++len;
else
{
if (len > 0)
len = next[len - 1];
else
next[i++] = len;
}
}
}
void kmp()
{
int i = 0, j = 0;
next2[0] = -1;
for (int i = 1; i < len2; i++)
next2[i] = next[i - 1];
while (i < len1)
{
if (j == len2 - 1 && p[i] == t[j])
{
printf("%d\n", i - j + 1);
j = next2[j];
if (j == -1)
j++;
}
if (p[i] == t[j])
{
j++;
i++;
}
else
{
j = next2[j];
if (j == -1)
{
i++;
j++;
}
}
}
}
int main()
{
scanf("%s%s", p, t);
len1 = strlen(p);
len2 = strlen(t);
do_next();
kmp();
for (int i = 0; i < len2; i++)
printf("%d ", next[i]);
return 0;
}
推导详解
假设我们的匹配字串为: P:ABABABCABAB T:ABABC
前后缀推导
首先我们需要请出一个炒鸡有名的表格!
子串长度 | 子串 | 前后缀长度(小于子串长度) |
---|---|---|
1 | A | 0 |
2 | AB | 0 |
3 | A B A | 1 |
4 | AB AB | 2 |
5 | ABABC | 0 |
我们从上到下把前后缀长度拿下来,就是next数组啦!
是不是非常简单!!
算法详解及代码实现
首先,我们需要一个变量len来表示此时的前后缀长度,以及变量i来对t字串进行遍历。 因为前后缀长度小于子串长度,所以我们的i直接从1开始,也就是:
i = 1;
len = 0;
接下来,我们来思考一个简单的问题。
当我们子串长度为3时,子串为A B A, 此时len = 1。这一步结束后,len++,len=2。
那么,当我们将子串长度增加至4时,我们如何判断能不能组成更长的(len为2的)前后缀呢? 只要判断增加的那一位(即当前子串最后一位,由i指向),与len指向的那一位是否相同即可。也就是只需要判断 ABA B 这一位与 A B AB 这一位是否相等即可。这里很明显是相等的,所以前后缀长度从1增加至2。
这一部分对应的代码为:
if (t[i] == t[len])
next[i++] = ++len;
(对应上方16-17行)
那如果不相同呢?比如当子串长度为5(ABABC)的时候? 此时的前后缀长度为0,因为突然多出了一个C,不管怎么弄都不可能匹配成功嘛。 所以咱一顺手就写上了:
len = 0;
我们看看最上面是怎么写的:
if (len > 0)
len = next[len - 1];
else
next[i++] = len;
(对应上方20-23行)
emmmmmm……相差好像有点大? 俗话说:事出反常必有妖。让我们来仔细研究下。
打个比方,我们此时的子串和next数组分别如下:
A B A B C A B A A
0 0 1 2 0 1 2 3 ?
此时len=3,而len指向的那一位是B,子串末位是A,两者不同,所以这里应该写0——诶诶诶慢着!好像有哪里不对!! 哦!托马斯·陈独秀先生,我的上帝!让我们仔细瞧瞧:这里的前后缀明明都是A啊!!!前后缀长度应该是1而不是0啊!!!! 因此,我们需要对算法进行修正,此时的前后缀长度,应为
len = next[len - 1]
许多教程将这一步称之为“回溯”。
这就完了?!不要忘了,len - 1可是下标,小心下标越界! 于是我们需要加一个判断以避免该情况的发生。
if (len > 0)
len = next[len - 1];
那万一这个时候len=0怎么办呢? 那不就说明这个时候的前后缀长度为0嘛!这一位的next就等于0啊! 最后别忘了i++!
next[i++] = len;
至此,整段next推导代码分析完毕,再回过头看看整段代码:
void do_next()
{
next[0] = 0;
int i = 1;
int len = 0;
while (i < len2)
{
if (t[i] == t[len])
next[i++] = ++len;
else
{
if (len > 0)
len = next[len - 1];
else
next[i++] = len;
}
}
}