图解KMP算法!

故事的开头是这样的呢,TOM给出了一串长长长长长的没有间隔的英文,要求JERRY从中找出自己名字,并记录下自己名字的第一个字母在该长长长的英文中的位置。如图:
在这里插入图片描述很明显,第一次匹配失败,那就向右移动。
在这里插入图片描述
还是匹配失败,那怎么办,继续重复这个动作咯,功夫不负有心人,最终在第七次匹配成功,VICTORY!!!
在这里插入图片描述

其实这样的做法是最简单也是最朴素的,简称暴力破解算法(Brute Force),也称为幼稚BF算法,虽然幼稚但实现起来还是挺有讲究的,看看我怎么实现的。

#include <iostream>
#include <cstring>

using namespace std;

int BF_Solution(const char* str, const char* pattern)
{
	int ret = -1;
	int s = strlen(str);
	int p = strlen(pattern);
	int len = s - p + 1;	// 比较的最多次数,越界了那继续比较就没有意义了
	
	for(int i = 0; (i < len) && (ret < 0); ++ i)	// 找到第一个匹配的就结束
	{
		bool flag = true;	// 标记变量,只要有一个不匹配,就开始新一轮循环
		for(int j = 0; (j < p) && flag; ++ j)
		{
			flag = flag && (str[i + j] == pattern[j]);
		}
		//只有当模式串中所有的字符都匹配成功,标记变量才会为真
		
		ret = flag ? i : -1;
	}
	
	return ret;
}

// 使用一下呗

int main(int argc, const char* argv[])
{
	cout << " JERRY's index is : " << BF_search("TOMANDJERRYSPAKE", "JERRY") + 1 << endl;
	// 为什么要加一?因为我们的下标是从零开始的
	// 所以对应于现实生活,那就得加一
	return 0;
}

在这里插入图片描述
看看输出结果是不是7啊。是的呢!这叫字符串的模式匹配!

哎啊,好像跑题了,我们的目标是看毛片啊,不,是KMP算法,上述的BF算法确确实实能够求出模式串在主串中第一次出现的下标,但是一旦有字符匹配失败就往后移动一个位置,效率很低很低。比如下图:

在这里插入图片描述

于是,为了提高效率,计算机的科学家埋头苦干日夜操劳,最终有了惊人的发现----字符串的模式匹配中某个字符匹配失败后所要移动的位数与主串无关,而仅仅与模式串有关。

说来话长,先来解释何为字符串的前缀和后缀,前缀----除了最后一个字符,所有的头部组合。后缀----除了第一个字符,所有的尾部组合。仅凭我一面之词,很难说清楚,还是看图吧!
在这里插入图片描述

相信现在能明白前后缀了。知道前缀后缀又有何用??? 细心的你一定能发现,前缀和后缀第一行以及第二行是一模一样的,这时你已经来到KMP算法的核心了。模式串匹配失败需要移动的位数就跟前后缀交集元素有关,并且是与交集元素中的最长元素有关,对应上面的图就是 a c ,计算机科学家发现的规律就是:
当第 n (n > 0)个字符匹配失败,模式串需要移动的位数等于 = 已经匹配成功的字符数(n - 1) - 已匹配字符串的前后缀最长交集元素的长度(next)。看文字多少有点抽象,上图!
在这里插入图片描述

在这里插入图片描述

移动的位数多了,那匹配的速度就快了。

如果我们把模式串的所有头部组合的子串的前后缀交集最长元素的长度求出来了,那么KMP算法就差不多完成了。将求出来的长度依次放到一个数组里,这个数组江湖人称next 数组或者PMT(Partial Matched Table)数组。现在的首要问题就是来求 next 数组了。在用代码求解 next 数组前,图解一下,相信用眼睛就能看出最长交集元素的长度!

在这里插入图片描述
在这里插入图片描述

是不是觉得第一个字符匹配失败没有列出来,并且还有一个蓝色的箭头是神马?? 确实是的,不过请看下面的解释!
来看一系列的图:
在这里插入图片描述
在这里插入图片描述

为什么第一个字符匹配失败就只能右移一位呢??第一个字符 a 匹配失败,他前面就只有空气了,哪来的交集元素!第一个字符匹配失败没有任何特殊的线索,就像大夏天在公交站台等公交的时候错过了一辆公交,木有任何办法!!只能绝望地等下一辆。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

看了那么多的图,再好好看一下代码就应该可以理解了!

下面就是编程实现了!

#include <iostream>
#include <cstring>	// 包含求字符串长度的函数

using namespace std;

int* get_next_array(const char* str)
{
	int len = strlen(str);
	int* next = new int[len];	// 申请一片堆空间内存
	int longest = 0;		// 最长交集元素的长度
	next[0] = 0;			// 初始值为零

	for(int i = 1; i < len; ++ i)	// 从1 开始
	{	// 主要是不等的时候需要从next数组中找一个值来尝试一下
		// 如果 longest == 0 ,直接比较首尾元素,即下面的 if 语句
		while( (longest > 0) && (str[longest] != str[i]) )
		{
			longest = next[longest - 1];
		}
		
		if( str[longest] == str[i] )	// 相等的时候就好解决,
		{
			++longest;		// longest直接加一
		}
		
		next[i] = longest;		// 更新next数组
	}
	
	return next;
}
#include <iostream>

int main(int argc, const char* argv[])
{
	int* next = get_next_array("abcabc");
	
	for(int i = 0; i < 6; ++ i)
	{
		cout << p[i] << " ";
	}
	
	return 0;
}

在这里插入图片描述
输出和人工智能算出来的一样哦。

。。。费了大力气搞定KMP 算法的核心步骤,现在就来实现字符串的查找函数。也就是KMP算法啦。

我分两次来实现,为什么这样呢?第一次实现的是没有任何障碍的,一次就能匹配成功。非常好理解 ,顺利无阻!!!

#include <iostream>
#include <cstring>

int KMP(const char* str, const char* pattern)	// 在没有next数组的情况
{
	int ret = -1;
	int sl = strlen(str);
	int pl = strlen(pattern);
	
	for(int i = 0, j = 0; i < sl; ++ i)	// i 变量遍历主串 str
	{
		if( str[i] == pattern[j] )	// 每一个字符都匹配
		{
			++ j;			// j 遍历模式串 pattern
		}
		
		if( j == pl )			// 当 j 已经等于模式串得长度,那就匹配成功了
		{
			ret = i - pl + 1;
		}
	}
}

那如果不顺利呢。。。next数组闪耀登场,看看怎么使用next数组吧!!!

#include <iostream>
#include <cstring>

int KMP(const char* str, const char* pattern)
{
	int ret = -1;
	int sl = strlen(str);
	int pl = strlen(pattern);
	int* next = get_next_array(pattern);

	for(int i = 0; i < sl; ++ i)
	{
		while( (j > 0) && (str[i] != pattern[j]) )
		{
			j = next[j - 1]; 	// 让模式串的指针回退
		}				// j 如果等于 0,循环回到最初状态
						// 所以 j > 0 	
		if( str[i] == pattern[j] )
		{
			++ j;
		}
		
		if( j == pl )
		{
			ret = i - pl + 1;
			break;			//	找到了就可以退出循环了
		}
	}
}

行不行的啊??行不行拿来试试就知道啦!

#include <iostream>

using namespace std;

int main(int argc, const char* argv[])
{
	cout << " JERRY's index is : " << KMP("TOMANDJERRY", "JERRY") << endl;
	
	return 0;
}

在这里插入图片描述

输出对吗?思考一下咯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值