开门见山,KMP算法是用来解决一个字符串在另外一个字符串中是否存在,如果存在,则返回它在该串的位置。
看过一些讲解KMP算法的,讲的都比较晦涩,难懂。一次偶然看了知乎上的一篇对KMP的讲解:https://www.zhihu.com/question/21923021/answer/281346746 讲的还不错,拿来学习学习。
对于初次学习KMP算法的困惑难点如下:
(1)next数组是什么鬼?为什么会想到前缀集合和后缀集合中的相同元素?
(2)如何给next数组中元素赋值?
(3)next数组代码中回溯那块为什么要那样回溯?
(4)为什么要改进KMP算法的next数组?
当然在学习KMP算法之前需要知道朴素的模式匹配算法,没错就是暴力求解,死磕。主串:"huanhuanhuan",模式字符串:"huanhabc",当遍历到主串'u'和模式串'a'时发现不匹配,然后主串回退到起点,模式串回退到第二个字符,继续.........时间复杂度太高了O(m*n)。
接下来KMP闪亮登场,它可以大大避免重复遍历的情况。能把时间复杂度优化到O(m+n)。来,我们开始解答上面提到的四点困惑。下面就是小学生开始解答日常家庭作业了:
首先我看了看题目,发现要想解释next数组必须得用到PMT表(Partial Match Table)。它长这样的:
表中前两行应该不用解释,最后一行value是字符串"abababca"截止当前index的子串取其前缀集合和后缀集合中最大的相同元素长度。例:index = 3,子串为"abab" ,前缀集合为{"a","ab","aba"},后缀集合为{"b","ab","bab"}。按规则value取"ab"的长度为2。这里注意前缀集合不包括最后一个元素,后缀集合不包括第一个元素。
解(1):
next数组的值就是将上面的value值往右挪一位,为了编程方便把next[0]赋值为-1。看看添加next后的表格吧:
举栗子:index=5,它的next值就是index=4时的pmt值(value值)。所以:next数组中的值保存的就是截止它----前面字符串的前后缀集合中---最长的相同字符串长度值。
为什么会想到前缀集合和后缀集合中的相同元素?
看图,KMP算法在匹配过程中匹配失败时,图中j要回退的位置就是next[6] = 4,这就是前缀后缀的在用在这里的好处,因为已经比较过了,所以有些没必要的比较就不需要再进行了。
解(2):
next值中的元素求解过程:
求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前位置向后一位的next值就是匹配成功的字符串的长度。(计算next值下标从0开始,匹配从1开始匹配,相差一位)
来上代码:
void getNext(const char *ptr,int len,int *next)
{
if(NULL == ptr || NULL == next)
{
return ;
}
int i = 0;
int j = -1;
next[0] = -1;
while(i
解(3):
回溯,next数组中保存的是就是匹配到的最长前后缀,求next数组本身也是一种字符串匹配的过程。当匹配不成功的时候回退,找最长前后缀,但是这样也会匹配不成功,原因如下,先上图:
解(4):
从图中可以看出来现在的next算法还是存在重复的回溯过程,它在每次回溯的过程中都会进行没必要的比较,何不直接让其进行有效的比较,改进的算法如下:
void getNext(const char *ptr,int len,int *next)
{
if(NULL == ptr || NULL == next)
{
return ;
}
int i = 0;
int j = -1;
next[0] = -1;
while(i
其实就是在给next数组赋值的时候多加了个判断,如果i位字符与它的next值指向的j位字符相等,则使i位的next值和j位的next值相等。如果不等就是它自己i为的next值。
至此4个困惑都解答完毕,最后附上完整的KMP算法代码:
int KMP(const char*str,const char*ptr,int*next)
{
if(NULL==str || NULL == ptr)
{
return -1;
}
int i=0,j=0;
int len1 = strlen(str);
int len2 = strlen(ptr);
while(i< len1 && j< len2)
{
if(str[i] == ptr[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j==len2) return i-j;
else return -1;
}
int main()
{
char *str = "ababababca";
char *ptr = "abababca";
int *next = new int[strlen(ptr)];
getNext(ptr,strlen(ptr),next);
int index = KMP(str,ptr,next);
cout<