找工作练手--KMP算法理解

22 篇文章 0 订阅

KMP算法讲解的实在太多了,各位作者为了它的严谨性,不惜花大量篇幅证明算法的正确性,也就用了很多公式去推导。曾经看了严蔚敏和唐宁九两位老师的,写的不错,花了不少时间。找工作在即,又花了点时间温习此算法,感觉比之前有了更多的感悟。

此算法的精髓在于运用匹配字符串P的右移省却了源字符串S的回溯过程,为什么右移以及右移多少位,这是该算法最关键的地方。为什么要右移,这个应该是显而易见的,匹配的过程就是P不断右移的过程;右移多少位,这应该是所有初学KMP算法的同学最困扰的地方了。

我的感悟就是:假设P=P1 P2 P3 .... Pi.....Pn在Pi处与S不匹配,此时P1 P2 P3 ... Pi-1都是已经匹配的,要算P要右移多少位,就是要算出P1 P2  P3 .... Pi-1中最大的K,把P1 P2  P3 ... Pi-1分成了两个字符串:P1 P2 .... Pk-1 和 Pi-k+1 Pi-k+2 ... Pi-1,使得这两个字符串正好匹配。

举例说明:

S = “acabaabaabcacx”  P = "abaabcac"

第一趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P               a      b       

此时在i = 2时 pi = b != c。此时按照上面我的所述,P1...Pi = a。而此时a仅仅是一个字符的字符串,无法拆分成两个,所以对于此时的右移比较特殊,直接右移一位即可。

第二趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P                         a     

此时在i = 1处就不匹配了,i之前没有匹配字符串,所以特殊情况,直接右移一位。

第三趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P                                  a         b       a        a       b        c 

此时在i = 6时不匹配,i之前有“abaab”是已经匹配的。这时的右移按照我上面所说要找到一个最大的K值把“abaab”划分成匹配的两个字符串。很容易看到此时K = 3,划分成的两个字符串分别是: P1 P2   =  “ab” 和 P4P5 = “ab” 。

第四趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P                                                                a        b       a        a          b        c 

第四趟即为第三趟右移三位后的匹配过程,很清楚地看到此时匹配成功了。

右移多少位就是求next[i]的值,相信大家已经很清楚的知道为什么右移以及右移多少位是如何来的,也就是最大值K是怎么产生的。

详细的证明部分还请大家去看相关书籍吧,理解了上面说的这些,再去看严,唐二人的书,应该就很快了。

下面附上代码:

#include "string.h"				// 串类

// KMP匹配算法
void getNext(const String &P, int next[])
// 操作结果: 求模式串P的next数组的元素值
{
	next[0] = -1;				// 由next[0] = -1开始进行递推
	int j = 0, k = -1;			// next[j] = k成立的初始情况
	while (j < P.Length() - 1)		
	{	// 数组next的下标范围为0 ~ P.Length() - 1, 通过递推方式求得next[j+1]的值
		if (k == -1)
		{	// k == -1只在j == 0时发生
			next[j+1]=0;		// next[j+1]=next[1] = 0
			j=1; k = 0;			// 由于已求得next[1] = 0,所以j = 1, k = 0
		}
		else if (P[k] == P[j])
		{	// 此时next[j+1] = next[j]+1
			next[j+1]=k+1;		// 由于P[k] == P[j],所以next[j+1] = next[j]+1 = k + 1
			j++; k++;			// 由于已求得next[j+1]=k+1,所以j更新为++j,k更新为++k
		}
		else
		{	// P[k]与P[j]不匹配
			k = next[k];		// 寻求新的匹配字符
		}
	}
}


int KMPIndexHelp(const String &T, const String &P, int pos, int next[])
// 操作结果: 通过next数组查找模式串P第一次在目标串T中从第pos个字符开始出现的位置
{
	int i = pos, j = 0;			// i为目标串T中的当前字符位置,j为模式串P的当前字符位置
	while (i < T.Length() && j < P.Length())
	{	
		if (j == -1)
		{	// 此时表明P中任何字符都不再与T[i]进行比较,下次P[0]与T[i+1]开始进行比较
			i++; j = 0;
		}
		else if (P[j] == T[i])
		{	// P[j]与T[i]匹配
			i++; j++;			// 模式串P与目标串T的当前位置向后移
		}
		else
		{	// P[j]与T[i]不匹配
			j = next[j];		// 寻找新的模式串P的匹配字符位置
		}
	}

	if (j < P.Length()) return -1;			// 匹配失败
	else return i - j;						// 匹配成功
}

int KMPIndex(const String &T, const String &P, int pos = 0)
// 操作结果: 查找模式串P第一次在目标串T中从第pos个字符开始出现的位置
{
	int *next = new int[P.Length()];		// 为数组next分配空间
	getNext(P, next);						// 求模式串P的next数组的元素值
	int result = KMPIndexHelp(T, P, pos, next);	
		// 返回模式串P第一次在目标串T中从第pos个字符开始出现的位置
	delete []next;							// 释next所占用的存储空间
	return result;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值