KMP算法
最近学习记录下,方便后面自己查看
一、什么是KMP
Knuth,Morris和Pratt,取了三位学者名字的首字母。 我何时有这么牛逼!!!哈哈!~~~
二、作用
字符串匹配
主要思想:
? 当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去匹配了
本算法难点:
? 利用前缀表 记录已经匹配的文本内容(这个前缀表真的不好理解!!我花了老半天的时间。。。。)
看下面的内容前:思考 下下面的两个问题
1.next数组 里的数字表示的是什么
2.为什么这么表示?
三、前缀表是什么东东?
在这里先卖个关子:next数组
我们在看书 或者写算法题 时,都会看到答案的解释中都有next数组 或者prefix table
解释
就是我们说的前缀表((⊙o⊙)…)
记录下标i 之前的(包括i)的字符串中,有多大长度的相同 前缀和后缀
仔细揣摩 相同二字含义!!!
作用
前缀表用来回退 的,记录模式串 与文本串(主串) 在不匹配的时候,模式串应该从哪开始重新匹配
实例演示:
需求: 要在主串【aabaabaafa】中查找 是否出现一个模式串【aabaaf】
在看下面过程,思考下主串 与模式串 的作用是什么
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWD0F0TI-1654617955803)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B21.gif)]
上图中的:
关键点 前缀表的aa 最长前缀匹配(就这一点我理解了半天!!)----后面我会说明
如果采用暴力解法:当主串的第六个字符b 和模式串的第六个字符f 不匹配时,就要重头来了
但是:
采用前缀表 :
? 1.这玩意儿就不会重头开始,
? 2.而是从上次 已经匹配 的内容开始匹配,
? 3.找到模式串 中的中第三个字符b 继续开始匹配(是不是懵逼 了!!哈哈哈!!硬着头皮往后看)
**心中的疑问?**凭啥在模式串的第三个 字符开始匹配
这就是我们所说的前缀表是如何记录 的呢?
前缀表 的任务:
-
当前的位置 匹配失败, -
找到之前已经匹配上的位置 -
在重新匹配, 就是某个字符失配时,前缀表 会告诉你下一步匹配中,模式串 应该跳到哪个位置(怎么跳,再往下读)
我们说了:
再刚刚的匹配的过程,在下标5 的大方遇到不匹配,模式串指向f :如下图所示
然后就莫名其妙的指向了下标2 (第三字符)
解释:划重点了哈!!
注意!(先看下)
**字符串的前缀 **是指不包含最后一个字符 的所有以第一个字符开头 的连续子串
后缀:不包含第一个字符 的所有以最后一个字符 结尾的连续子串
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-50Qzt6BB-1654617955804)(Untitled.assets/29950F6F.gif)]
下标5之前的这部分的字符串(就是aabaa )的最长相等的前缀 和后缀 字符串是子字符串aa
因为:找到了最长相等的前缀 和后缀 ,匹配失败 的位置是后缀子串 的后面,
所以:我们找到与其相同的前缀的后面重新 开始新匹配就可以了.(这句话好好咀嚼!!!精华)
还是懵逼??哈哈~
- 你仔细向下模式串的前五个
aabbaa 与主串中前五个aabaa 是一样的对吧? - 这是我在
模式串 找最长前缀 与后缀 相等的数,是不是就是变相的在主串的子串aabaa 中找?(目的找到首次尾部能和头部相同的位置) - 这时
aa 在主串中位置是不是就可确定了???
注意!(再瞅瞅!!)
**字符串的前缀 **是指不包含最后一个字符 的所有以第一个字符开头 的连续子串
后缀:不包含第一个字符 的所有以最后一个字符 结尾的连续子串
四、前缀表的计算
如图:
长度为1 的子字符串a ,最长相同前后缀的长度为0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R9wHcA8O-1654617955805)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B25.png)]
按照上面的方法寻找:(子字符串)
aa :最长相同的前后缀的长度为1 ----------------------------------长度为2aab :最长相同的前后缀的长度为0 ---------------------------------长度为3aaba :最长相同的前后缀的长度为1 --------------------------------长度为4aabaa :最长相同的前后缀的长度为2 ------------------------------长度为5aabaaf :最长相同的前后缀的长度为0 ----------------------------长度为6
对应的最长相同前后缀长度对应的前缀表的元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rHxPr8f-1654617955805)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B28.png)]
下图利用前缀表找到当字符不匹配时应该指针移动到什么位置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9bBVbvQ-1654617955806)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B22.gif)]
过程分析:
- 找到不匹配位置,我们要看他的前一个字符的前缀表数值是多少(这里是2)
- 找前缀表的前一个字符的原因:找到要找
前面字符串 的最长相同的前缀和后缀
五、前缀表与next 数组
其实就是一样的,只是具体的实现方式不一样罢了
next 数组:就是前缀表,只是很多实现将前缀表 进行统一的**减一(右移一位操作)**作为后面的next 数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6H88xp9F-1654617955807)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B24.gif)]
六、时间复杂度----O(n+m)
O(n+m)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8z5NnoQ1-1654617955807)(Untitled.assets/29C539D2.png)]
七、构造next 数组
定义函数:getNext()
函数参数为指向next的数组的指针和一个字符串
void getNext(int* next,const string& s)
构造``next数组就是计算 模式串`,过程步骤分析
1.初始化
int j=-1;
next[0]=j;
说明:
? j 初始化-1 的原因–前缀表的统一减一 操作,
? next[i] 表示i (包括i)之前的最长相等的前后缀长度
2.处理前后缀不相同的情况
//因为i=-1,右移一位操作,所以i 从1 开始
for(int i=1;i<s.size();i++)
如果s[i] 与s[j] 不相同,进行回退操作
next[j] 就是记录这``j`之间的子串相同前后缀的长度
若s[i] 与s[j+1] 不相同,找j+1 前一个元素在next 数组里的值(其实就是next[j])
while(j>=0&&s[i]!=s[j+1])
{
j=next[j];
}
3.处理前后缀相同的情况
若s[i] yus[j+1] 相同,那马旧同事向后移动i 和j 说明找相同的前后缀,
将``j`(前缀的长度)赋给next[i],记录相同的前后缀长度
if(s[i]==s[j+1])
{
j++;
}
next[i]=j;
整合代码,万朝归元
void getNext(int* next,const string& s)
{
int j=-1;
next[0]=j;
for(int i=1;i<s.size();i++){
while(j>=0&&s[i]!=s[j+1]){
j=next[j];
}
if(s[i]==s[j+1]){
j++;
}
next[i]=j;
}
}
代构造的next 数组动画演示图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nGoByR9Z-1654617955807)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B23.gif)]
待续
明天更!!!
|