KMP算法的学习经验

KMP算法的学习经验

(欢迎指正错误, 欢迎喷)

  1. 什么是kmp(完)
  2. kmp的额外知识(完)
  3. 暴力匹配的缺点,和代码实现(完)
  4. next[]数组的预先知识,了解前后缀,相同前后缀。(完)
  5. kmp的关键next[]数组, next的实现。(完)
  6. next数组中k = next[k]的理解。
  7. 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 请看大牛的讲解->

posted @ 2019-02-24 16:18  Probie_Tao  阅读(319)  评论(0编辑  收藏  举报