KMP算法的学习经验
(欢迎指正错误, 欢迎喷)
- 什么是kmp(完)
- kmp的额外知识(完)
- 暴力匹配的缺点,和代码实现(完)
- next[]数组的预先知识,了解前后缀,相同前后缀。(完)
- kmp的关键next[]数组, next的实现。(完)
- next数组中k = next[k]的理解。
- kmp的优化(待续。。)
一.什么是kmp
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。
————来自百度百科
简单的说 :
1.kmp是一种字符串匹配算法。
2.它的优点就是能通过 next[]数组(一个记忆数组),减少匹配的次数, 从而节省时间。
3.关键是next[] 这个记忆数组。
二.kmp的额外知识
由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)
并且kmp被很多人叫做,看(k)毛(m)片(p)算法。
三.普通暴力匹配
//ptr[] 匹配串, str[] 主串
1.暴力匹配
我们的普通暴力匹配是 匹配串ptr 的字符一个一个和 主串匹配。
如果一个字符匹配成功:ptr的下一个字符继续匹配,
如果失败 :str[]回溯到开始匹配的位置(i = i - j, <-下面有讲解),然后再以 下一个位置为开始, 再次重新进行字符串匹配。
暴力匹配代码:
int plen = strlen(ptr);//匹配串的长度
int slen = strlen(str);//主串的长度
int i, j = 0; //i 和j 分别代表 当前 str 和ptr 所匹配的字符下标
int flag = 0;//如果为 1 表示 主串中存在 匹配串。
for(i = 0;i < slen;i++)
{
if(str[i] == ptr[j])//如果匹配成果 继续下一个匹配
{
j++;
}
else//如果 不相等
{
i = i- j;//主串回溯, i回到匹配前的位置
j = 0;//匹配串 要从头开始
}
if(j == plen)// 主串中存在客串 匹配串成功
{
flag = 1;
break;//结束
}
}
以上是暴力匹配 的代码, 这也能解决字符串匹配的问题(这种方法没有一点错误), 然而你用这种方法去解题,很多题只能送你个:
acmer的你,看到这就是很尴尬了。
为什么呢?
因为你暴力匹配太盲目了, 而kmp确实聪明的 kmp的j知道往哪跳。
四. 相同前后缀的讲解:
1.什么是前后缀。
如:ABCD这个字符串
A
AB
ABC
就是他的前缀。
BCD
_CD
__ D
就是它的后缀。
·下划线只是营造效果, 没有意义。
2.相同前后缀:
如:ABCAB这个字符串
AB(前缀) AB(后缀) 就是相似前后缀(而且AB是最长的相似前后缀)。
五.kmp的next数组
kmp 通过next数组, 保留了ptr(匹配串)以每个字符结尾的子串的 最长相同前后缀的长度。通过next 主串就不用回溯, 只需ptr(匹配串)来回的跳(有想法的跳 o(* ̄︶ ̄ *)o)就行。
{
如:1. 主串ABCABCABD 客串ABCABD
2.当匹配到:
ABCABCABD
ABCABD 与其失配。
但是我们已经匹配了ABCABCABD 这个AB 我们已经知道他匹配,
所以根据ptr的 前后缀相似的性质(ABCABD), 直接跳到相似前缀的哪里 接着往后匹配。
所以就要知道next数组怎么获得。
}
那么接下来就看个有图的栗子(例子):
主串:ABABAC
匹配串: ABAC
A | B | A | B | A | C |
---|
A | B | A | C |
---|
/***next数组的获取*/
int Get_next()
{
int plen = strlen(ptr);
next[0] = -1;
int k = -1;//表示上一次匹配的最长前后缀长度
for(i = 0; i < plen; i++)
{
if(k == -1 || ptr[i] == ptr[k])// ptr[i]是后缀的对后一个字符, ptr[k]是前缀的最后一个字符。 k = -1是以第一个字符结尾的子字符串没有前后缀,因此就要有个操作, 解决 第一个字符串就匹配失败的情况。
{
k++;//如果本次匹配成功 , 长度加一
next[i+1] = k;//数组储存
}
else//不成功
{
i--;//主串不动
k = next[k];//长度缩减再次匹配, 对于k为什么等于next[k] ,后续会有讲解。
}
}
}
/***kmp 匹配过程****/
int kmp(char str[], char ptr[])
{
int flag = 0; // flag = 1 表示 主串中含有 匹配串
int slen = strlen(str);
int plen = strlen(ptr);
int i, j = 0;
for(i = 0;i < slen;i++)
{
if(j == -1||str[i] == ptr[j])
//j == -1是:当第一个字符串就不匹配时, 整个匹配串要整体往右移一个。
{
j++;
}
else
{
i--;
j = next[j];
//匹配失败 j 跳到一上一个字母结尾的前缀后面+1处,
}
if(j == plen)
{
flag = 1;
break;
}
}
return flag;
}
六. k = next[k]的理解:
其他大佬对k = next的理解入口->
本人的讲解:
当匹配失败时 主串 i会停留不动,
k 会都等于 next[k]。
一下图片是 AAACAAAA和它next[]数组情况正确
接下来是过程
深入了解kmp 请看大牛的讲解->