KMP算法个人理解

最近在看算法,看到KMP算法,其实有点绕,不是太好理解,就介绍一下自己对于这个算法的理解以及实现。

KMP算法思想

    Knuth-Morris-Pratt算法(简称KMP),是由D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的一个改进算法。KMP算法是模式匹配中的经典算法,和BF算法(又称蛮力匹配算法,需要通过不断回溯依次移动进行模式串的匹配)相比,KMP算法的不同点是消除BF算法中主串S指针回溯的情况,从而完成串的模式匹配,这样的结果使得算法的时间复杂度为O(n+m)。

KMP算法描述

    KMP算法每当一趟匹配过程中出现字符比较不等时,主串S中的i指针不需回溯,而是利用已经得到的“部分匹配”结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。

下面主要介绍KMP算法的主要核心其实就是next数组和nextval数组的求解,然后就可以顺理成章的完成匹配任务。

next算法

    先定义一下主串为“S1S2...Sn”,模式串为“T1T2...Tm”    

    next算法主要就是来确定当匹配过程中产生“失配”时,模式串“向右滑动”可滑动的距离有多远,也就是说,当主串中字符与模式串中字符“失配”时,主串中字符应与模式串中的哪个字符再进行比较?

    然后说一下next算法中涉及到的重要的“前缀”和“后缀”的概念

    "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

以"ABCDABD"为例,

 - "A"的前缀和后缀都为空集,共有元素的长度为0;

 - "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

 - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

 - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

 - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

 - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

 - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

    说一下自己对于next数组的理解,构建数组的过程其实就是,以前一个字符Tj为基础,next[j] = k, 这表明在模式串中存在‘T1T2...Tk-1’=‘Tj-k+1Tj-k+2...Tj-1’,也就是说在j-1个字符中,前k-1个字符(前缀)和后k-1个字符(后缀)相等,如果增加一个Tj字符,求next[j+1]的值,可以分为两种情况:

  (1)如TTk, 则表明在模式串中T1T2...Tk’=‘Tj-k+1Tj-k+2...Tj’,也就是代表着加入一个字符后,前k个字符和后k个字符是相等的,这个时候只需要next[j+1] = next[j] + 1 就可以了。

    (2)如果Tj ≠ Tk, 则表明在模式串中T1T2...TkTj-k+1Tj-k+2...Tj’。此时可以把求next函数值看做是一个模式匹配的问题,整个模式串既是主串又是模式串。我们匹配不到k个值,完全可以通过之前保存的前k个值的next[]的值去找小于k个值k’(1<k’<k<j的前缀和后缀的匹配,也就是要通过next[k]的值去进行判断。

    ① 如果Tj = Tk’, 则next[j+1] = next[k] + 1

   ② 如果Tj  Tk’, k = next[k], 然后一直重复下去,若指导最后j=0时都一直比较不成功,则next[j] = 1。

    上代码:

void Get_Next(SString T, int next[])
{
	int k = 0, j = 1;
	next[1] = 0;
	while(j < T.length)
	{
		if (k == 0 || T.ch[j] == T.ch[k])
		{
			++j;
			++k;
			next[j] = k;
		}
		else
			k = next[k];
	}
}

nextval算法

    nextval算法思想比较简单,就是当遇到类似于“AAAAB”这样的匹配串时,前面4个字符是相同的,如果通过next值进行移动的话比较耗时,完全可以通过直接向右滑动4个字符的位置直接进行比较最快。

算法思想

    计算第j个字符的nextval值时,要看第j个字符是否和第j个字符的next值指向的字符相等。若相等,则nextval[j] = nextval[next[j]];否则,nextval[j] = next[j]。(可以理解nextval就是对next做了进一步的优化,因为nextval是在next数组的基础上计算的)

上代码:

void Get_NextVal(SString T, int next[], int nextval[])
{
	int j = 2, k = 0;
	Get_Next(T, next);
	nextval[1] = 0;
	while(j <= T.length)
	{
		k = next[j];
		if (T.ch[j] == T.ch[k])
			nextval[j] = nextval[k];
		else
			nextval[j] = next[j];
		j++;
	}
}

KMP匹配算法

最后把KMP最终匹配的算法的代码附上:

int Index_KMP(SString S, int pos, SString T)
{
	int i = pos, j = 1;
	while (i <= S.length && j <= T.length)
	{
		if (j == 0 || S.ch[i] == T.ch[j])
		{
			++i;
			++j;
		}
		else
			j = next[j];   // 模式串向右滑动
	}
	if (j > T.length)
		return i - T.length; // 匹配成功时,返回匹配起始位置
	else
		return 0;
}

KMP算法分析

    KMP算法是在已知模式串的next或nextval的基础上执行的,如果不知道它们二者之一,则没有办法使用KMP算法。虽然有next和nextval之分,但它们表示的意义和作用完全一样,因此在已知next或nextval进行匹配时,匹配算法不变。

    通常,模式串的长度m比主串的长度n要小很多,且计算next或nextval函数的时间复杂度为O(m)。因此,对于整个匹配算法来说,所增加的计算next或nextval是值得的。

    BF算法的时间复杂度为O(n*m),但是实际执行近似于O(n+m),因此至今仍被采用。KMP算法仅当模式串与主串之间存在许多“部分匹配”的情况下,才会比BF算法快。KMP算法的最大特点是主串的指针不需要回溯,整个匹配过程中,主串仅需从头到尾扫描一次,对于处理从外设输入的庞大文件很有效,可以边读边匹配。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值