通用算法 - [字符串] - KMP算法

1、KMP算法介绍

KMP算法是一种改进的字符串匹配算法。KMP算法主要是通过消除主串指针的回溯以达到快速匹配的目的。具体实现就是生成一个next数组,next数组本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。

2、KMP算法流程

2.1 算法整体流程

忽略next数组的具体生成过程,我们先来看kmp算法的整体流程:

文本串S和模式串P进行匹配:
假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置 ;
(1)如果j == -1,或者当前字符匹配成功(即S[i]
== P[j]),都令i++,j++;继续匹配下一个字符;
(2)如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。
如果j == S.length,则说明匹配成功,S中与P匹配的子串的起始位置为i - S.length;
否则未匹配成功。

注意,步骤(2)意味着失败时,模式串P相对于文本串S向右移动了j - next [j] 位。 换言之,当匹配失败时,模式串向右移动的位数为:失败字符所在位置 - 失败字符对应的next 值,即移动的实际位数为:j - next[j],且此值大于等于1。

2.2 next数组的意义

上一小节我们提到了next数组,那么next数组中各值的含义究竟是什么呢?

实际上,next 数组各值的含义是:
k=next[j],代表模式串P第j个字符之前的子串p = P[0,…,j-1]中,最开头的k个字符和结尾的k个字符是一样的,即p[0,…,k-1] == p[j-k,…,j-1]。

举个例子,假设P=“ABCDABD”,next[6] = 2,则表示子串p=“ABCDAB”中,开头的两个字符和结尾的两个字符是一样的,都是“AB”。

next数组表示,在某个字符匹配失败时,该字符对应位置的next 值会告诉你下一步匹配中,模式串应该跳到哪个位置(跳到next [j] 的位置)。如果next [j] 等于0或-1,则跳到模式串的开头字符,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某个字符,而不是跳到开头,且具体跳过了k 个字符。

继续拿之前的例子来说,当S[10]跟P[6]匹配失败时,KMP不是跟暴力匹配那样简单的把模式串右移一位,而是执行步骤(2):“如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]”,即j 从6变到2(后面我们将求得P[6],即字符D对应的next 值为2),所以相当于模式串向右移动的位数为j - next[j](j - next[j] =6-2 = 4)。
在这里插入图片描述
向右移动4位后,S[10]跟P[2]继续匹配。为什么要向右移动4位呢,因为移动4位后,模式串中又有个“AB”可以继续跟S[8]S[9]对应着,从而不用让i 回溯。相当于在除去字符D的模式串子串中寻找相同的前缀和后缀,然后根据前缀后缀求出next 数组,最后基于next 数组进行匹配。
在这里插入图片描述
了解了next的具体含义之后,对于给定的模式串“ABCDABD”,可求得它的next 数组如下:
在这里插入图片描述

3、KMP算法的代码实现

(1)next的求解方法:

void getnext(int next[], string p){
	int k = -1;
	int j = 0;
	next[0] = -1;
	int plen = p.length();
	while(j < plen - 1){
		if(k == -1 || p[j] == p[k]){
			k++;
			j++;
			next[j] = k;
		}
		else{
			k = next[k];
		}
	}
}

(2) KMP算法:

int KMP(string s,string p){
	int plen = p.length();
	int slen = s.length();
	int next[plen];
	getnext(next,p);
	int i = 0,j = 0;
	while(i < slen && j < plen){
		if(j == -1 || s[i] == p[j]){
			i++;
			j++;
		}
		else{
			j = next[j];
		}
	}
	if(j >= plen) {
		return i - plen;
	}
	else{
		return -1;
	}	
}

(3) 改进后的 next 求解方法
先来看一下上面算法存在的缺陷:
在这里插入图片描述
显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]

所以下一步我们应该是把j移动到第1个元素咯:
在这里插入图片描述

不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。

显然,发生问题的原因在于p[j] == p[next[j]]。

所以我们需要添加一个判断:

void getnext(int next[], string p){
	int k = -1;
	int j = 0;
	next[0] = -1;
	int plen = p.length();
	while(j < plen - 1){
		if(k == -1 || p[j] == p[k]){
			k++;
			j++;
			if(p[j] == p[k]){
				next[j]= next[k];
			}
			else{
				next[j] = k;
			}
		}
		else{
			k = next[k];
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章