1 从最简单的想法开始
现有两个字符串text
、pattern
,需要从text
中查找是否存在一个连续的字串和pattern
相等,如果有的话就返回第一个查找到的字串的起始位置。如果不考虑效率的话,这确实是一个非常简单的任务,一个最简单的想法是两层循环比对,如下图所示:
2 利用模式串自身的特点来优化
假如模式串有着这样一个特点,在下标属于[0, j)
的范围内有完全相等的前缀和后缀:
此时,在与text
比对时,如果在下标j
处发生失配,那么我们还需要从头开始比对吗?当然不需要,我们可以直接从下标t
开始与text
中失配处的字符进行比较,还是用图来说明:
为什么可以这么做,上图已经描述的很明白了,因为标记为黄色的部分是相等的,无需再重复比对。值得一提的是,下标j
之前可能存在多对长度相等的前缀和后缀,我们应该选择其中最长的那一对,这样不会错过任何可能的情况。
3 利用next数组优化串匹配
在上一节的例子中,在下标j
处发生失配,只需把记录模式串当前下标的变量的值由j
修改为t
然后继续比对。j
和t
之间是存在着联系的,下标j
唯一映射到一个值t
,这个值的含义是:在[0, j)
的范围内,长度为t
的前缀和等长的后缀完全相同。
既然j
对应t
,那么不难想到其它下标也对应着一个值,如果我们把这个对应关系全部找出来并保存在一个名为next
的数组中(next[j]的值为t
),那么这个数组可以用于优化串匹配算法,即在失配时将下标的值修改为以它为索引在next
数组中的值,如下面的代码片段所示:
/* 匹配 */
int i = 0, j = 0;
while ((i < text_len) && (j < pattern_len)) {
if ((j < 0) || (haystack[i] == needle[j]))
++i, ++j;
else
j = next[j];
}
4 构造next数组
那么问题来了,next
数组如何构建?我们可以从next[j]
和next[j+1]
之间的关系入手。如果pattern[t] == pattern[j]
则有下图:
不难看出,此时next[j+1] = next[j] + 1
。
如果pattern[t] != pattern[j]
呢?先不着急回答这个问题,我们先考虑next[t]
的值,不妨设其值为t'
,则说明在[0, t)
的范围内,长度为t'
的前缀和等长的后缀完全相同,有下图:
稍加推导即可得到下面的关系:
这时我们发现如果pattern[t'] == pattern[j]
,那么next[j+1] = next[t] + 1
,如果不能立马想到这一点,不妨看下图:
如果pattern[t'] != pattern[j]
呢?继续按照上面的思路看next[t']
,就像俄罗斯套娃那样。构建next
数组的代码如下:
/* 构建next数组 */
vector<int> next(pattern_len, -1);
for (int i = 0, index = -1; i < needle.size() - 1; ) {
if ((index < 0) || (needle[i] == needle[index]))
next[++i] = ++index;
else
index = next[index];
}
需要注意的是,next[0]
作为哨兵被初始化为-1
,把实际不存在的pattern[-1]
假想为一个通配符,这样在逻辑上是合理的,可以用一个实际的例子来体会。
5 完整的程序
借助leetcode的28号题的测试用例,以下用KMP算法实现字符串匹配的程序可以通过所有用例:
int strStr(string haystack, string needle) {
if (needle.empty())
return 0;
int text_len = haystack.size(), pattern_len = needle.size();
/* 构建next数组 */
vector<int> next(pattern_len, -1);
for (int i = 0, index = -1; i < needle.size() - 1; ) {
if ((index < 0) || (needle[i] == needle[index]))
next[++i] = ++index;
else
index = next[index];
}
/* 匹配 */
int i = 0, j = 0;
while ((i < text_len) && (j < pattern_len)) {
if ((j < 0) || (haystack[i] == needle[j]))
++i, ++j;
else
j = next[j];
}
return (j == needle.size()) ? (i - j) : -1;
}
6 参考文献
[1] 邓俊辉老师的数据结构课程