码迷,mamicode.com
首页 > 编程语言 > 详细

kmp算法

时间:2015-11-22 00:22:24      阅读:200      评论:0      收藏:0      [点我收藏+]

标签:

kmp

为了实现复杂度低的字符串匹配算法,将依次顺序的扫描算法O(n*m)的复杂度降到O(n+m) 的算法就有了kmp(knut-Morris-Pratt算法)。

字符串匹配,简单的来说就是在母串S中寻找是否含有模式串T,这种字符串匹配是计算机的基本任务之一。

kmp算法不易理解,网上有很多解释,读起来都很难以理解,看到了一个很好地总结http://kb.cnblogs.com/page/176818/  现在真正的看懂理解了这种算法,下面在结合这篇博文的基础上进一步讲解一下基本kmp实现过程中的代码如何理解,和kmp的优化如何实现。

首先简单介绍一下博文中的对kmp的理解:

博文中讲解的顺序查找的算法和优化思想不再赘述,对博文中的部分匹配值得理解做详细描述,引入next数组的概念,要理解

1. 正确理解前缀和后缀的意思,eg: ABCD的前缀分别是A, AB , ABC , ABCD

后缀分别是 D , CD , BCD , ABCD。注意,千万不可以把后缀理解成从后往前读的字符子串,这里明确一点:前缀一定是要包含当前字母前面的所有的字符,后缀一定要包含当前字符后面的所有的字符,这样对于next数组后面的解释才能正确理解其在整个算法中的作用。

2. 从next数组的定义上来理解:next[i]表示的是i位置前的串中,所有前缀和后缀中最长共有元素的长度,也可以说是共有元素长度中的最大值,引用博文中也有具体举例。(注意:同引用博文不同的是这里我们为了方便代码书写,所有的串下标默认从0开始编号,所以请仔细理解公共长度,这样方便与第二种next的用法上的理解对应)

在理解了前缀和后缀后进一步的解释后,next数组为什么要这么定义,首先,简单的理解就是i位置前的next[i]个字符和从开头数的next[i]个字符是一样的,那么匹配的时候如果是从i 处失配了,那么说明前面的所有部分都是已经匹配好的,那么现在如果前面的next[i]个字符和最后匹配好的字符是完全一样的就可以直接将这next[i]个放到这后面next[i]个位置上,即从第next[i]个位置从新开始匹配,那么就不需要移动母串的指针,直接将子串滑动到第next[i]的位置。下面看一个例子

母串      ABCABCFGABABD

模式串   ABCABD

标红地方失配了,而失配处前面的AB是已经匹配好的,根据前面的讲解,模式串应该滑动到下面的位置

母串      ABCABCFGABABD

模式串   AAAABCABD

2. 有了这些知识我们来理解next数组的另一种理解,即next数组在算法中具体应用的理解:next[i]表示在i处失配话将

当失配的时候模式串要定位在模式串的第next[i]的位置上,这里要注意到上面提到的理解公共长度的部分,公共长度,由于数组标号是从0开始的所以失配位置要是想从新定位,看上面的例子可以理解应该是定位在相同部分的后一个位置,而后一个位置的下标刚好是公共长度(自己仔细思考,不再赘述)

那么,问题来了,怎样求next呢?(大致思路就是如何求最大的前缀和后缀的共有元素长)

我们先来给出代码,然后再一点点理解

 1 int kmp(char* s , char* t)
 2 {
 3     int len1 , len2;
 4     len1 = strlen(s);
 5     len2 = strlen(t);
 6     int i , j = 0 , tm = next[0] = -1;
 7     //求next数组
 8     while(j<len2-1){
 9         if(tm<0||t[j]==t[tm])
10             next[++j] = ++tm;
11         else tm = next[tm];
12     }
13     //匹配
14     for( i=j=0 ; i < len1 && j < len2)
15     {
16         if(j<0||s[i]==t[j]) i++,j++;
17         else j = next[j];
18     }
19     return i-j;
20 }

 可以看到kmp算法的代码很短,我们先来分析求next数组的部分

 

1 //求next数组
2     while(j<len2-1){//依次求出模式串中每一个位置上的next值,循环j-2次即可,因为每次j是先++后处理的,相当于这个循环求出了下标从1到n-1位置的next值,下标为0的已经初始化时候定义过了
3         if(tm<0||t[j]==t[tm])//tm<0是为了让刚开始进入循环的时候不出错
4             next[++j] = ++tm;
5         else tm = next[tm];
6     }

测试(可输出)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<string>
 4 using namespace std;
 5 int next[1500];
 6 int i , j = 0;
 7 int tm = next[0] = -1;
 8 int get_next(char* t)
 9 {
10     int len = strlen(t);
11     while(j<len-1){
12         if(tm<0||t[j]==t[tm])//tm<0是为了当找不到任何前缀等于后缀,而且不是字符串第一个字符的时候next值是0
13             next[++j] =++tm;//j和tm先加1再赋值,保证每次比较的字符串都不包含最后一位
14         else tm = next[tm];//如果这一位适配了,那么说明失配的字符前面的是已经匹配好的,然后要找失配位置的前面的字符串的next,即失配前的字符串中前缀等于后缀的长度
15     }
16 }
17 int main()
18 {
19     char s[10];
20     scanf("%s",s);
21     get_next(s);
22     for(int i= 0 ;i < strlen(s);i++)
23         printf("%d ",next[i]);
24     puts("");
25     return 0;
26 }

现在来分析一下求next主要代码

if(tm<0||s[j]==s[tm]) {j++,tm++;  next[j]==tm;}

else tm = next[tm];

举例来说:其实可以将求next的过程看成是第二次的kmp匹配过程。

  abcabfabcabg...

开始j定位到abcabfabcabg...

tm = -1,然后j和tm先++,然后就next[1] = 0——相应的对应b前面的字符串的公共前后缀长度为0,注意这里next[0]= -1是初始化了的;

然后比较abcabfabcabg...发现不匹配,然后让tm = next[tm],这里不易理解这个语句的用途,下面一直分析,分析到

当要比较abcabfabcabg...发现不匹配,这时候tm = 5 ; j = 11;发现不匹配,由于next的连续性,所以从第一个字符开始到f的所有字符,和从g前面数相同数目的字符肯定是匹配好的,这里要仔细理解next数组的含义,即前缀和后缀的公共最大长度,所以这里找f前面的字符的next数组,这里表示的是f前面的

abcabfabcabg...由于g前面的和f前面的是已经匹配好的所以粉色的表示f的已匹配的前缀,即长度为next[5]的长度,肯定等于黄色的ab(根据next定义)也定于绿色的ab(由于g和f之前是匹配好的)那么就相当与是ab在f和g之前就不用再考虑了,下次比较的时候就是g和橘黄色的c开始比较了

那么问题自然来了,有人会问如果串时这样的呢。。。。。注释1;

abcabfabcaeg...那么我们要想其实在绿色的部分已经失配了,即当tm = 4 ; j = 10 ; 的时候就已经失配了,那么tm 要跳到next[4] = 1 ,指向黄色位置

然后下次是tm = 1和j = 10 比较,即每次都是tm 向前跳而j 不变,然后发现tm = 1和tm = 10仍然不一样,所以跳到tm = next[tm] = 0 ,仍然失配,然后跳到tm = -1 ;

所以串在匹配的时候从abcabfabcabg...只要是一失配就向前跳一直跳到-1或者是满足注释1的地方,所以,只要是tm和j比较的时候一定是可以保证tm前面到串开始 和 从j前面的相同个数的字符是完全相同的。

现在就可以理解了next的数组了,如果不能理解,请再仔细阅读一遍,仔细思考。

 

下面我们来说一下一个简单的优化

假设在模式串的第i个位置失配,并且t[i] = t[next[i]], 那么令j= next[j]时依然失配,所以此时优化方法是令next[j] = next[next[j]].代码如下

 1 int kmp(char* s , char* t)
 2 {
 3     int len1 , len2;
 4     len1 = strlen(s);
 5     len2 = strlen(t);
 6     int i , j = 0 , tm = next[0] = -1;//初始化为-1为了,当第一个都不匹配的
 7     //求next数组
 8     while(j<len2-1){//依次求出模式串中每一个位置上的next值,循环j-2次即可,因为每次j是先++后处理的,相当于这个循环求出了下标从1到n-1位置的next值,下标为0的已经初始化时候定义过了
 9         if(tm<0||t[j]==t[tm]){//tm<0是为了让刚开始进入循环的时候不出错
10             ++j;++tm;
11             if(next[j]==next[tm]) tm = next[tm];//优化
12             next[j] = tm;
13         }
14         else tm = next[tm];
15     }
16     //匹配
17     for( i=j=0 ; i < len1 && j < len2)
18     {
19         if(j<0||s[i]==t[j]) i++,j++;
20         else j = next[j];
21     }
22     return i-j;
23 }
 1 //求next数组优化代码
 2  8     while(j<len2-1){//依次求出模式串中每一个位置上的next值,循环j-2次即可,因为每次j是先++后处理的,相当于这个循环求出了下标从1到n-1位置的next值,下标为0的已经初始化时候定义过了
 3  9         if(tm<0||t[j]==t[tm]){//tm<0是为了让刚开始进入循环的时候不出错
 4 10             ++j;++tm;
 5 11             if(next[j]==next[tm]) tm = next[tm];//优化
 6 12             next[j] = tm;
 7 13         }
 8 14         else tm = next[tm];
 9 15     }
10 16     //匹配

 

kmp算法

标签:

原文地址:http://www.cnblogs.com/shanyr/p/4975952.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!