KMP简单字符串匹配算法TOC讲解,逻辑清晰
1:背景TOC
给定一个基本字符串(用S替换)和一个模式字符串(用P替换),需要找到P的出现.在S位置,即字符串匹配问题。今天我们将介绍解决这个问题的常用算法之一,Knuth-莫里斯-普拉特算法(简称KMP)。这个算法是由高德纳(唐纳德·欧文·Knuth)和沃恩·普拉特于1974年创建的。同年,詹姆斯·H·莫里斯也开发了该算法,最终于1977年三人发表。
在继续之前,有必要介绍一下以下两点:后缀和后缀。
从上图中,“前缀”是指除最后一个字符之外的所有字符串开头; “后缀”是指字符串尾部除去第一个字符的组合。
2:Naive字符串匹配算法TOC
当我们第一次遇到字符串匹配问题时,我们脑子里的第一反应一定是连接一个简单的字符串(强绑定),即去遍历S的属性各至此。该字符与 P 进行比较,如果发出所有匹配项,则这将是结果;代码如下:
/* 字符串下标始于0 */
int NaiveStringSearch(string S, string P)
{
int i = 0; //S的下标
int j = 0; //P的下标
int s_len = S.size();
int p_len = P.size();
while (i < s_len && j < p_len)
{
if (S[i] == P[j]) //若相等,都前进一步
{
i++;
j++;
}
else //不相等
{
i = i - j + 1;
j = 0;
}
}
if (j == p_len) //匹配成功
return i - j;
return -1;
}
上述算法的时间复杂度为O(nm),其中n为S的长度,m为P的长度。这次是很难满足我们的需求。咱们进入正题:时间复杂度为θ(n+m)的KMP算法。
三:KMP TOC字符串匹配算法
3.1 TOC流算法
(1)
DA 首先,对字符串ABC DAB的第一个字符ABCBCA“BD”的第一个字符进行比较。由于 B 和 A 不匹配,因此模式字符串返回到一个位置。
(2)
由于 B 和 A 不匹配,因此模式字符串反转。
(3)
如此,直到主字符串与模式字符串的第一个字符相同。
(4)
再对比一下主绳和花绳的以下特征,还是一样的。
(5)
直到主字符串中出现与模式字符串的匹配字符不同的字符。
(6)
此时,最自然的行为就是将整个模式字符串移动到一个位置,并从头开始逐一进行比较。虽然这是可能的,但是效率非常低,因为“位置搜索”必须移动到已经比较过的位置并再次比较。
(7)
基本事实是,当空格不匹配 D 时,你知道前六个字符是“ABCDAB”。 KMP算法的思想是尝试利用这些已知信息,而不是将“搜索点”移回比较位置,而是继续移动它,从而提高其性能。
(8)
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
模型字符串 | A | B | C | D | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
D D | '\0' | 下一个[j] | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
如何做到这一点?您可以为模式字符串设置跳转列 int next [ ]
。稍后将展示如何计算这个数字。关于它的使用,您需要了解的内容很好。
(9)
当已知空格和 D 不匹配时,前六个字符“ABCDAB”匹配。根据跳转顺序可以看出,不匹配的旁边的D的值为2,因此下一个从脚本位置2开始匹配。
(10)。 A、这里的next值为-1,表示模式串的第一个字符没有匹配到,所以直接返回一个位置。
(12)
逐步比较,直至发现C和D不匹配,因此下一步从索引2开始匹配。
(13)
逐级比较,直到发现模式串的最后一位数字匹配良好,则搜索完成。
3.2 next TOC的计算方法
next 数组解法是基于“前缀”和“后缀”,即next [j]
与
0]相同... P[j-1]相同后缀中最长的(请先跳过j等于0的情况,下面会有解释)。我们仍以上表为例。为了方便阅读,我将其复制如下。
(1) j=0,对于模式字符串的第一个字符,它与 组合下一个[0]=-1; 我很困惑吧? 。 。 下面详细分析这个事实: | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
next[j] | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
以中的上表为例♽ 如果采用以 › 为例。 j=5 匹配失败。根据3.2中的代码,现在应该取出j=1
中的字符继续比较,但这两个位置上的字符是相同的,都是 B
。既然是一样的,那不就没有什么比较的意义了吗?我已经在 3.2 中对此进行了解释。这是因为 KMP 并不完美。那么我们该如何重写代码来解决这个问题呢?这很简单。
/* P为模式串,下标从0开始 */
void GetNextval(string P, int nextval[])
{
int p_len = P.size();
int i = 0; //P的下标
int j = -1; //相同前后缀的长度
nextval[0] = -1;
while (i < p_len)
{
if (j == -1 || P[i] == P[j])
{
i++;
j++;
if (P[i] != P[j])
nextval[i] = j;
else
nextval[i] = nextval[j]; //既然相同就继续往前找前缀
}
else
j = nextval[j];
}
}
还想提醒读者,KMP算法完全分为KMP算法(未优化版本)和KMP算法(优化版本),所以请读者在描述KMP算法时告知其版本。 ,由于两者在某些情况下有很大不同,我们在这里简单讨论一下。
KMP 算法(非优化版本): 下一个序列表示最长等效后缀的长度。我们不仅可以使用以下方法来解决模式字符串匹配问题,还可以解决相似字符串、重复查询等问题。这些类型的问题你都可以在大型OJ中找到,所以我在这里不再赘述。
KMP算法(优化版):在代码中易于识别(也改名为nextval)。接下来优化的表示相同的后缀长度,但是不一定是最长的(我个人称之为“最佳等价后缀”)。现在,我们可以使用以下优化的版本来更快地响应与模式字符串相关的问题(与非优化版本相比)。但优化后的版本并不能解决上面提到的字符串重复问题。
因此,使用哪个版本取决于您实际处理的实际问题。
参考文献:
[1] 严伟民.数据结构(C版)
[2]阮一峰.字符串匹配的KMP算法
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。