谈谈自己对KMP算法的见解

开门见山,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)。它长这样的:

谈谈自己对KMP算法的见解_第1张图片

表中前两行应该不用解释,最后一行value是字符串"abababca"截止当前index的子串取其前缀集合和后缀集合中最大的相同元素长度。例:index = 3,子串为"abab" ,前缀集合为{"a","ab","aba"},后缀集合为{"b","ab","bab"}。按规则value取"ab"的长度为2。这里注意前缀集合不包括最后一个元素,后缀集合不包括第一个元素

解(1):

next数组的值就是将上面的value值往右挪一位,为了编程方便把next[0]赋值为-1。看看添加next后的表格吧:

谈谈自己对KMP算法的见解_第2张图片

举栗子:index=5,它的next值就是index=4时的pmt值(value值)。所以:next数组中的值保存的就是截止它----前面字符串的前后缀集合中---最长的相同字符串长度值。

为什么会想到前缀集合和后缀集合中的相同元素?

谈谈自己对KMP算法的见解_第3张图片

看图,KMP算法在匹配过程中匹配失败时,图中j要回退的位置就是next[6] = 4,这就是前缀后缀的在用在这里的好处,因为已经比较过了,所以有些没必要的比较就不需要再进行了。

解(2):

next值中的元素求解过程:

求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前位置向后一位的next值就是匹配成功的字符串的长度。(计算next值下标从0开始,匹配从1开始匹配,相差一位)

谈谈自己对KMP算法的见解_第4张图片

谈谈自己对KMP算法的见解_第5张图片

谈谈自己对KMP算法的见解_第6张图片 谈谈自己对KMP算法的见解_第7张图片 谈谈自己对KMP算法的见解_第8张图片

 来上代码:

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数组本身也是一种字符串匹配的过程。当匹配不成功的时候回退,找最长前后缀,但是这样也会匹配不成功,原因如下,先上图:

谈谈自己对KMP算法的见解_第9张图片

解(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<

 

 

你可能感兴趣的:(算法)