字符串匹配算法——KMP算法浅浅析

1 从最简单的想法开始

现有两个字符串textpattern,需要从text中查找是否存在一个连续的字串和pattern相等,如果有的话就返回第一个查找到的字串的起始位置。如果不考虑效率的话,这确实是一个非常简单的任务,一个最简单的想法是两层循环比对,如下图所示:
在这里插入图片描述

2 利用模式串自身的特点来优化

假如模式串有着这样一个特点,在下标属于[0, j)的范围内有完全相等的前缀和后缀:
在这里插入图片描述
此时,在与text比对时,如果在下标j处发生失配,那么我们还需要从头开始比对吗?当然不需要,我们可以直接从下标t开始与text中失配处的字符进行比较,还是用图来说明:
在这里插入图片描述
为什么可以这么做,上图已经描述的很明白了,因为标记为黄色的部分是相等的,无需再重复比对。值得一提的是,下标j之前可能存在多对长度相等的前缀和后缀,我们应该选择其中最长的那一对,这样不会错过任何可能的情况。

3 利用next数组优化串匹配

在上一节的例子中,在下标j处发生失配,只需把记录模式串当前下标的变量的值由j修改为t然后继续比对。jt之间是存在着联系的,下标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] 邓俊辉老师的数据结构课程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章