KMP模式匹配算法

**注意:**本文金仅供笔者复习使用,不负责讲解。

盯着KMP算法看了四个小时左右,终于彻底明白何为KMP算法了。
所谓KMP算法有个大名鼎鼎的特点,那便是通过消除主串下标的回溯,极大的避免了不必要的重复比较,从而大大提高算法的时间效率

朴素匹配模式算法

说到KMP,我们不得不说提朴实模式匹配算法,只要这种对比才能显现KMP算法的优越性。
朴素匹配模式算法,就是所谓暴力枚举,对所以的可能性都进行比较,从而得出我们期望的结果。这种算法,非常好理解,非常符合正常人思维,我们就不解释了,直接上代码:

int Idex(const char* S, size_t slen, const char* T, size_t tlen)
{
	int s_index = 0;	//主串S的下标
	int t_index = 0;		//模式串T的下标

	while (s_index < slen && t_index < tlen)
	{
		if (S[s_index] == T[t_index])	//模式串与主串对于下标字符匹配成功
		{
			++s_index;
			++t_index;
		}
		else  //匹配失败
		{
			s_index = s_index - t_index + 1; //主串下标进行回溯
			t_index = 0;	//重置模式串下标
		}

	}

	//检查循环音何种条件退出循环,只要当t_index < tlen不成立才说明匹配成功。
	if (t_index == tlen)
		return s_index - t_index;
	else
		return -1;
}

很明显,朴素匹配模式算法是在是太直接了,简直不带怪我的,因此才叫朴素模式匹配算法。还有些叫幼稚匹配模式算法,意味愚蠢的算法。

KMP模式匹配算法

KMP模式匹配 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位研究并发表的算法(其中Morris独立研究,另外两人合作研究),称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。
那么对比朴素匹配模式算法,KMP算法优势何在?与朴实模式匹配算法想比,KMP算法通过消除了主串指针的回溯,避免了许多重复比较,以提高了算法效率。KMP算法是如何做到的呢?说简单也简单,说难也难,但现在我看来,KMP算法的思想其实很好理解,只要理解了KMP算法思想那就不难了。
KMP算法的研究者发现,一旦我们发现主串与子串的比较一旦发生不匹配时,那么我们就能够得知,只要不匹配位置之前有子串(我们称为F,F有可能不存在),那么这些字符一定是和主串对应位置的字符匹配的。我们观察F串后可以得到一个规律,我们依据这个规律使模式串下标回溯到某个特定位置,而不必改使主串下标回溯就能够接着继续比较。这使得我们一旦发生不匹配我们只需要调整,模式串下标的位置,而不使主串回溯,避免了许多重复比较。从中我亦可以得知,既然我们根据模式串的某个位置之前的子串F来得出规律的使我们得知模式串下标回溯的值,那么我们当然就可以把模式串每个不匹配位置的模式串下标回溯值的信息给提取出来,存到一个名为next的数组中。那么假设我们已经取得了next数组,下面我们改写朴实模式匹配算法:

int Idex(const char* S, size_t slen, const char* T, size_t tlen, int next[])
{
	int s_index = 0;	//主串S的下标
	int t_index = 0;		//模式串T的下标

	while (s_index < slen && t_index < tlen)
	{
		if (S[s_index] == T[t_index] || t_index == 0)	//模式串与主串对于下标字符匹配成功.新增条件t_index == 0
		{
			++s_index;
			++t_index;
		}
		else  //匹配失败
		{
			//s_index = s_index - t_index + 1; 主串下标回溯取消
			t_index = next[t_index];	//根据next数组信息重置模式串下标
		}

	}

	//检查循环音何种条件退出循环,只要当t_index < tlen不成立才说明匹配成功。
	if (t_index == tlen)
		return s_index - t_index;
	else
		return -1;
}

我们只是小小的改动了上面的代码却造成了效率的巨大提升,不得不说算法真的很神奇。如果KMP算法怎么容易理解,我想也不会那多人困扰了,KMP算法最最核心的部分,就是如何根据特定模式串提取相应的next数组信息,这才是KMP算法最难以理解的部分。
前面我们已经说过,next说着存储的是模式串各个位置与主串比较不匹配时对于的模式串j应该回溯的值。
做法就是寻找每个位置的F(匹配失败位置之前的子串)串中最大公共前后缀这个为什么得是最大公共前后缀,我觉得这才是最难以说清的部分,我悲观的认为,一般人简直无法通过语言理解这种行为,我是通过看动画和自己画图,模拟一定步骤然后盯着看那几步中的规律,但我很难描述清楚,那种感觉,我只能说,懂了就是懂了。但是这部分代码仍然有一个非常难懂的地方,这个地方用到了KMP算法模式串回溯,整个KMP算法最难理解的地方。为什么说KMP算法高明?那就是因为**模式串中最大公共前后缀的长度的记录仍然不是暴力的扫描,而是利用next已知的next[j]数组不断利用并推导mext[j+1]**为什么能这样?那是因为我们寻找最大公共前后缀本质上同模式串和主串匹配寻找完全等同!只不过这里是特殊的主串和模式串,模式串主串等价!而,我们规定next[0]的值为0(-1也是同样的,-1是为了方便对于下标),代码如下:

void get_next(const char* T, size_t tlen, int* next)
{
	int j = 0;
	int k = -1;
	next[0] = -1;
	while (j < tlen - 1)	//反正数组下标越界
	{
		if (k == -1 || T[j] == T[k])	//k = -1 是找不到公共前后缀的情况,相当于next[j] = 1
		{
			++j, ++k;
			next[j] = k;
		}
		else //不匹配的情况
		{
			k = next[k];	//利用回溯思想,因为我们已经找到了0-j的next数组的值,而t<j,显然我们必定找到1-tnext数组的值
							
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值