KMP算法的理解和实现
2017-03-16 11:48
344 查看
想学会“看毛片”算法,今天和同学捣鼓了半天,终于理解并自己实现了它。主要参考的博客是http://blog.csdn.net/yutianzuijin/article/details/11954939/ 。由于自己实现了一遍KMP代码,为了加深印象和加强理解,写篇博客记录一下,欢迎讨论。
首先解释一下几个基本的名词:
比如:
KMP算法的主要目的是判断模式串是否存在于主串中,若存在,返回匹配位置的下标
KMP算法与朴素的字符串匹配算法的最大区别就是:
朴素的字符串匹配算法是每次失配后将模式串向右移一个字符,继续从模式串首开始比较,这样做缺点是没有利用已经匹配的字符的信息,降低了效率;
KMP算法利用了已经匹配了的字符的信息,一次失配后尽可能的右移更多的字符,但又保证不会漏掉可能的匹配串,从而在保证精确的前提下提高了效率。
举个例子:
经过观察和思考,KMP的发明者们发现,对于每一个已经匹配的长度(如上例中已经匹配了3个字符),都可以计算出一个“最大右移值”,且这个“最大右移值”仅与已经匹配的字符串有关(注意到上面的例子中我计算右移值为3位仅仅用到了’abc’这个已经匹配的字符串)
所以,对于每一个给定的模式串,我们都可以对它的每一个长度的匹配长度计算相应的“最大右移值”,在匹配主串时,对于每一个匹配长度,都按这个提前计算出的“最大右移值”进行右移,这就是KMP算法的基本思路。
那么,根据已经匹配的字符串,如何计算出所谓的
4000
“最大右移长度”呢?
这里引入“前缀”和“后缀”的概念:
当一个字符串的前缀和后缀完全相同时,我们记相同部分的长度为len,当len最大时,用这个字符串的长度减去l,就是我们的”最大右移值”
在确保自己理解了上面那句话之后,再看下面的内容就比较容易了。
KMP算法使用一个next数组来存储上面提到的len。
注意!注意!注意!重要的事情:
next数组的下标表示的是已经匹配的字符长度,是长度!由于匹配长度为0时KMP和朴素匹配方法的处理方法是一样的),所以next数组的下标从1开始。而匹配串的下标是从0开始的。
这点很重要,我和同学看了好多博客,发现看得越多越晕,很大的原因就是很多网上的代码没有交代清楚next数组的下标和匹配串下标的区别,导致代码总是看不懂….下面贴代码的时候我还会强调这个问题。
我们知道,对于每一个匹配长度,都可以计算出一个len值,比如:
的next数组中的值如下,下标从1开始:
那么,如何用代码计算next数组呢?
下面是我写的代码:
从代码中可以看到,next[i+1]是可以通过next[i]迭代计算得到的,这么做的原理,篇首提到的那个参考链接中讲的很清楚,这里不再赘述。
next数组计算结束了,我们开始使用它来高效地检测字串,下面是我的测试代码:
首先解释一下几个基本的名词:
1.模式串:用于匹配的子串 2.主串:可能包含模式串的字符串 3.失配:匹配失败
比如:
串A:"abcdefgabcde" 串B:"defg" 判断串A中是否包含串B,则串B为模式串,串A为主串
KMP算法的主要目的是判断模式串是否存在于主串中,若存在,返回匹配位置的下标
KMP算法与朴素的字符串匹配算法的最大区别就是:
朴素的字符串匹配算法是每次失配后将模式串向右移一个字符,继续从模式串首开始比较,这样做缺点是没有利用已经匹配的字符的信息,降低了效率;
KMP算法利用了已经匹配了的字符的信息,一次失配后尽可能的右移更多的字符,但又保证不会漏掉可能的匹配串,从而在保证精确的前提下提高了效率。
举个例子:
主串A: "abccdabcbc" 匹配串B:"abcd" 朴素匹配方法: 前三次比较"abc"都匹配成功,第四次由于"c"和"d"不相同,所以失配; 这时将子串右移一位,继续比较: abccdabcbc abcd 发现第一位"a"和"b"失配,于是继续右移: abccdabcbc abcd ..... KMP的匹配方法: 同样前三次比较发现"abc"匹配成功,第四位"d"失配; 这时KMP算法将会利用“已经匹配了abc这3个字符”这个信息,显然 a|bc| |a|bc |ab|c ab|c| 都是不匹配的,所以这时可以直接将模式串右移3位,从一开始失配时的这样: abccdabcbc abcd 变成这样: abccdabcbc abcd
经过观察和思考,KMP的发明者们发现,对于每一个已经匹配的长度(如上例中已经匹配了3个字符),都可以计算出一个“最大右移值”,且这个“最大右移值”仅与已经匹配的字符串有关(注意到上面的例子中我计算右移值为3位仅仅用到了’abc’这个已经匹配的字符串)
所以,对于每一个给定的模式串,我们都可以对它的每一个长度的匹配长度计算相应的“最大右移值”,在匹配主串时,对于每一个匹配长度,都按这个提前计算出的“最大右移值”进行右移,这就是KMP算法的基本思路。
那么,根据已经匹配的字符串,如何计算出所谓的
4000
“最大右移长度”呢?
这里引入“前缀”和“后缀”的概念:
还是上面的例子,“abc”是“abcd”的一个前缀,“ab”也是“abc”的一个前缀,“a”也是... 而“bcd”、“cd”、“d”都是“abcd”的后缀 但是"abcd"不是"abcd"的前缀和后缀,因为这样的前后缀对KMP算法是没有意义的。
当一个字符串的前缀和后缀完全相同时,我们记相同部分的长度为len,当len最大时,用这个字符串的长度减去l,就是我们的”最大右移值”
在确保自己理解了上面那句话之后,再看下面的内容就比较容易了。
KMP算法使用一个next数组来存储上面提到的len。
注意!注意!注意!重要的事情:
next数组的下标表示的是已经匹配的字符长度,是长度!由于匹配长度为0时KMP和朴素匹配方法的处理方法是一样的),所以next数组的下标从1开始。而匹配串的下标是从0开始的。
这点很重要,我和同学看了好多博客,发现看得越多越晕,很大的原因就是很多网上的代码没有交代清楚next数组的下标和匹配串下标的区别,导致代码总是看不懂….下面贴代码的时候我还会强调这个问题。
我们知道,对于每一个匹配长度,都可以计算出一个len值,比如:
模式串"abcbcabc"
的next数组中的值如下,下标从1开始:
next数组中len的值:[x,0,0,0,0,0,1,2,3] 对应next下标值 : 0,1,2,3,4,5,6,7,8
那么,如何用代码计算next数组呢?
下面是我写的代码:
#pragma once #include<stdlib.h> #include<iostream> using namespace std; #define MAXSIZE 100 #define PSIZE 9 void calcNext(int * next,char*pattern,int psize){ //参数解析:next为存放计算之后的next数组 //pattern为模式串 //psize为模式串长度 next[1] = 0;//匹配长度为1时,len必为0 for (int i = 1; i < psize; i++){ int n = next[i]; while (n != 0 && pattern != pattern[i]){ //这里就体现了上文说的下标问题。注意这里做的工作是: //比较模式串的第next[i] + 1个元素和模式串的第i + 1个元素是否相等 //next[i]==0时,则比较pattern的第一个元素和模式串的第i + 1个元素 //不相等则比较模式串的第next[next[i]]+1个元素和第i+1个元素是否相等 //如此迭代直到两者相等或next 为0 n = next ; } if (n == 0){ if (pattern == pattern[i]) next[i + 1] = 1; //如果只有模式串的第一个元素和第i+1个元素相同,则next[i+1] = 1. else next[i + 1] = 0; } else next[i + 1] = n + 1; } }
从代码中可以看到,next[i+1]是可以通过next[i]迭代计算得到的,这么做的原理,篇首提到的那个参考链接中讲的很清楚,这里不再赘述。
next数组计算结束了,我们开始使用它来高效地检测字串,下面是我的测试代码:
int main(){ char pattern[PSIZE + 1] = { 0 }; int next[PSIZE + 1] = { 0 };//next[0]不用 char buffer[MAXSIZE] = { 0 }; int j = 0; cout << "Input the pattern string :(less than 9)" << endl; cin >> pattern; cout << "Input the string to match substring:" << pattern << endl; cin >> buffer; //计算next calcNext(next, pattern, PSIZE); //开始匹配,匹配方法和计算next数组神似,大家好好体会 for (int i = 0; i < strlen(buffer); i++){ while (j > 0 && pattern[j] != buffer[i]) j = next[j]; if (pattern[j] && buffer[i] == pattern[j]){ j++; } if(!pattern[j]){ cout << "find substring at index: " << i - strlen(pattern) + 1 << endl; j = next[j]; } } cout << "match over" << endl; system("pause"); return 0; }
相关文章推荐
- kmp算法的理解与实现
- 对KMP的理解,以及kmp算法java版本实现
- 【转】kmp算法的理解与实现
- 实现KMP算法的几种理解方法
- KMP算法代码实现和优化(不太能理解具体的过程和该算法思想)
- 字符串匹配:KMP算法的实现以及理解
- KMP算法中next数组的理解与算法的实现(java语言)
- KMP算法的理解,伪代码,c代码实现
- kmp算法的理解与实现
- 理解和实现KMP算法
- 逐步理解KMP算法C++完整实现
- (转)KMP算法实现。超级赞!见过的最容易理解的
- kmp算法的理解与实现
- KMP算法的理解与实现
- KMP算法理解与实现
- KMP算法理解与next数组的实现
- Java 泛型的理解与等价实现
- VC中利用多线程技术实现线程之间的通信(一)---理解线程
- 关于KMP算法的一点个人理解
- KMP算法Java实现