kmp算法原理及实现

字符串匹配问题:其主要功能给定两个字符串T和f(字串),长度分别为n和m,判断f是否在T中出现,如果出现则返回出现的位置。给定两个字符串T和f(字串),长度分别为n和m,判断f是否在T中出现,如果出现则返回出现的位置。如:有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”?

朴素字符串匹配算法:遍历T的每一个位置,然后从该位置开始和f进行匹配,但是这种方法的复杂度是O(nm)。这种算法没有利用匹配过的信息,对于字串来说每次都从头开始比较,而对于主串来说,经常需要重复比较之前已经检测过的字符,因而速度很慢。模式匹配是一个常见的应用问题,用的广了,就有人想法去优化了。Rabin-Karp算法、有限自动机等等,前仆后继,最终出现了KMP(Knuth-Morris-Pratt)算法。
kmp算法被称为“看毛片”算法
《kmp算法原理及实现》,是一个效率非常高的字符串匹配算法。

kmp算法通过一个O(m)的预处理来构建一个字串f的前缀数组(即计算字符串f每一个位置的字符串的前缀和后缀公共部分的最大长度,不包括字符串本身,否则最大长度始终是字符串本身),接下来的匹配过程会不断地使用到该前缀数组(而且对于主串T只需遍历一次),使匹配的复杂度降为O(n+m)。

构建前缀数组:

规定:next[0]=next[1]=0;

next[i]就是前缀数组,下面通过1个例子来看如何构造前缀数组。
例子1:cacca有5个前缀,求出其对应的next数组。
前缀2为ca,显然首尾没有相同的字符,next[2] = 0
前缀3为cac,显然首尾有共同的字符c,故next[3] = 1
前缀4为cacc,首尾有共同的字符c,故next[4] = 1
前缀5为cacca,首尾有共同的字符ca,故next[5] = 2

在本例中对于字串“ABCDABD”的前缀数组如下:

i1234567
f(i)ABCDABD
next[i]0000120

匹配过程:

 1.

《kmp算法原理及实现》

  首先,字符串”BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索词”ABCDABD”的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

  2.

《kmp算法原理及实现》

  因为B与A不匹配,搜索词再往后移。

  3.

《kmp算法原理及实现》

  就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

  4.

《kmp算法原理及实现》

  接着比较字符串和搜索词的下一个字符,还是相同。

  5.

《kmp算法原理及实现》

  直到字符串有一个字符,与搜索词对应的字符不相同为止。

  6.

      朴素字符串匹配过程:将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把”搜索位置”移到已经比较过的位置,重比一遍。

                                              《kmp算法原理及实现》

       而一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是”ABCDAB”。

       KMP算法的想法:设法利用这个已知信息,不要把”搜索位置”移回已经比较过的位置,继续把它向后移,这样就提高了效率。而这时候就要利用到上面所产       生的前缀数组了,查表可知前面六个字符是”ABCDAB”的next[6]=2;因而直接将字串匹配位置移动6-2=4个位置;

      即如下图:

                                          《kmp算法原理及实现》

                                    《kmp算法原理及实现》

      移动位置=已匹配的字符数 – 对应的部分匹配值

  7.

《kmp算法原理及实现》

  因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2(”AB”),对应的”部分匹配值”为0。所以,移动位数 = 2 – 0,结果         为 2,于是将搜索词向后移2位。

  8.

《kmp算法原理及实现》

  因为空格与A不匹配,继续后移一位。

  9.

《kmp算法原理及实现》

  逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 – 2,继续将搜索词向后移动4位。

  10.

《kmp算法原理及实现》

  逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 – 0,再将搜索词向后      移动7位,这里就不再重复了。

实现如下:

#include "iostream"
using namespace std;
void compute_prefix(int *next, char *p)
{
	int  i, n, k;

	n = strlen(p);
	next[1] = next[0] = 0;
	k = 0;      /* 第i次迭代开始之前,k表示next[i-1]的值 */
	for (i = 2; i <= n; i++) {
		for (; k != 0 && p[k] != p[i - 1]; k = next[k]);
		if (p[k] == p[i - 1]) k++;
		next[i] = k;
	}
}
void kmp_match(char *text, char *p, int *next)
{
	int     m, n, s, q;

	m = strlen(p);
	n = strlen(text);
	q = s = 0;  /* q表示上一次迭代匹配了多少个字符,
				s表示这次迭代从text的哪个字符开始比较 */
	while (s < n) {
		for (q = next[q]; q < m && p[q] == text[s]; q++, s++);
		if (q == 0) s++;
		else if (q == m) {
			printf("pattern occurs with shift %d\n", s - m);
		}
	}
}
int main()
{
	int     next[101], n;
	char    *p = "ababababca";
	char    *text = "ababababcadababababcadababababcadababababca";

	compute_prefix(next, p);
	kmp_match(text, p, next);
	cin.get();
	return 0;

}


这里需要强调一下,KMP算法的仅当模式与主串之间存在很多部分匹配情况下才能体现它的优势,部分匹配时KMP的i不需要回溯,否则和朴素模式匹配没有什么差别。

  

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/u012216287/article/details/48858433
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞