关于KMP的理解

来源:互联网 发布:彩票网络代理怎么做 编辑:程序博客网 时间:2024/05/29 04:36

关于KMP的理解与说明

KMP主要是对字符串的处理,关于#include <string.h> 库函数中的strstr函数就是用KMP写出来的,但一般的编译器中没有这个函数,所以我们需要自己理解KMP的原理。这里可以介绍一下对KMP的简单看法,我花了两天的时间才看懂next数组的存储原理。

1.对于两个字符串的比对,拿个例子来说,查找a串中是否有b串的出现,出现的话输出首次出现的位置。之前的话就只能暴力求解了。用b串在a串进行逐一比对,如果相同则进行下一位的比较,不相同就回到b串的第一位重新比较,直到长度达到b串的长度时退出。

int find(char*s,char*p){int ls=strlen(s);int lp=strlen(p);int i=0;int j=0;while(i<ls&&j<lp){if(s[i]==p[j])//如果当前字符匹配成功(即S[i]?==?P[j]),则i++,j++{i++;j++;}else//如果失配(即S[i]!=P[j]),令i=i-(j-1),j=0{i=i-j+1;//回头开始比较的下一位 j=0;}}//匹配成功,返回模式串p在文本串s中的位置,否则返回-1if(j==pLen)return i-j;elsereturn -1;}

这种方法的时间复杂度太大,达到了O(m*n),其实在比对失败的时候返回初始位置的这一环节可以先找到模式串q的next数组(next数组在下文讲解),找到q串的公共前后缀的长度,在匹配失败的时候返回公共前后缀的下一位就可以直接进行比对了。这就是所说的KMP了。

int kmp(){get_next();int i=0;int j=0;int ls=strlen(s);int lp=strlen(p);while(i<l1){if(j==-1||s[i]==p[j])//因为在匹配失败的时候,j要返回next[j]的时候会有出现-1的情况{i++;j++;//相等则进行下一个字符的比对}else{j=next[j];//不相等的时候返回next[j]也就是模式串p}}if(j>lp)return i-j;elsereturn -1;}


2.在模式串p与文本串s的比对中,前面的字符都相同,找到不相同的一位的时候返回的不是刚开始比较位置的下一位,而是省去相同前后缀的长度。比如说文本串s='ABXABCDABXABA',而模式串p='ABXABA',在比较中s[0]与p[0]相同,找到下一位进行比对,然后发现s[1]与p[1]相同,则继续比对。然后第五个字符C与A不相同,这时候因为模式串p中A字符前面的串最大公共前后缀为2所以就直接省去相同前后缀的长度进行下一位的比对,意思就是ABXAB的前缀与后缀AB相同,所以就可以直接省去这个时间,进行下一位模式串中的字符X与文本串中的C进行比对,利用这个原理省去很多的时间,s串只需要一次的从开始到结束的读取就可以完成了,p串在比对不成功的时候返回相同前后缀的下一位直接就可以进行操作了。代码模板就是上面的KMP函数了,使用后时间复杂度就是O(n+m)比原始的暴力求解节省很多时间。


3.下面讲述一下next数组的由来,这个花费了两天时间才理解一些的我。其实原理和KMP的原理差不多,一个串中前后进行比对,相同的时候k++;j++;这个比较好理解,但是两个字符不同的时候返回k=next[k];比较难以理解,容易卡在这里的。

void get_next(){int k=-1;int j=0;int l=strlen(p);next[0]=-1;//这里我学的next数组是从-1开始的,也比较好理解,方便next数组的存储于理解,然后就是next每个位置存的值就是对应下标的字符前面串的最长公共前后缀 while(j<l){if(k==-1||p[k]==p[j])//k值是从-1开始的,就是方便next数组进入存储 , {k++;j++;next[j]=k;//两个字符相同的时候k代表的就是串读到第几位,也就是公共前后缀的长度 }elsek=next[k];//两个字符不同的时候k就会递归返回next[k]的位置,目的是为了找到之前的相同前后缀,节省时间与步骤 }}
解释一下,这里和KMP返回稍有些不同,因为它只是一个串的自身比对,原理差不多的。找到一个比较好的例子就是p='AAACDAAAAC',因为next数组存的是对应长度的最长公共前后缀,所以以-1开始的话,next[]存的就是-1 0 1 2 0 0 1 2 3 3,每一个next[j]存的就是字符p[j]之前串的最大公共前后缀的长度,然后在找到倒数第三个字符的时候是关键点,因为开头的AAA与连续的AAAA的前三个是相匹配的,读到第三个A的时候应该与前缀中的C进行比较因为不相同,所以下标k应该返回next[k]也就是next[2]也就是开头的第三个A,然后相同后进行下一位的比较。这样就不用那么复杂的从头再比较了,节省的地方就是开头AAA的前缀与第二次读到AAA的后缀相同的大小,可以想象一下。就是这个图片中p[k]与p[j]不相同的时候可以省去相同前后缀也就是蓝色的长度。

原创粉丝点击