KMP算法总结(纯算法,为优化,没有学应用)
来源:互联网 发布:手机摄像头监控软件 编辑:程序博客网 时间:2024/06/05 03:00
先引入大神通俗易懂的博客内容
KMP 算法,俗称“看毛片”算法,是字符串匹配中的很强大的一个算法,不过,对于初学者来说,要弄懂它确实不易。整个寒假,因为家里没有网,为了理解这个算法,那可是花了九牛二虎之力!不过,现在我基本上对这个算法理解算是比较透彻了!特写此文与大家分享分享!
我个人总结了, KMP 算法之所以难懂,很大一部分原因是很多实现的方法在一些细节的差异。怎么说呢,举我寒假学习的例子吧,我是看了一种方法后,似懂非懂,然后去看另外的方法,就全都乱了!体现在几个方面: next 数组,有的叫做“失配函数”,其实是一个东西; next 数组中,有的是以下标为 0 开始的,有的是以 1 开始的;KMP 主算法中,当发生失配时,取的 next 数组的值也不一样!就这样,各说各的,乱的很!
所以,在阐述我的理解之前,我有必要说明一下,我是用 next 数组的, next 数组是以下标 0 开始的!还有,我不会在一些基础的概念上浪费太多,所以你在看这篇文章时必须要懂得一些基本的概念,例如 “ 朴素字符串匹配 ”“ 前缀 ” , “ 后缀 ” 等!还有就是,这篇文章的每一个字都是我辛辛苦苦码出来的,图也是我自己画的!如果要转载,请注明出处!好了,开始吧!
假设在我们的匹配过程中出现了这一种情况:
根据 KMP 算法,在该失配位会调用该位的 next 数组的值!在这里有必要来说一下 next 数组的作用!说的太繁琐怕你听不懂,让我用一句话来说明:
返回失配位之前的最长公共前后缀!
什么是最长公共前后缀:
好,不管你懂不懂这句话,我下面的文字和图应该会让你懂这句话的意思以及作用的!
首先,我们取之前已经匹配的部分(即蓝色的那部分!)
我们在上面说到 next 数组的作用时,说到 “ 最长公共前后缀 ” ,体现到图中就是这个样子!
接下来,就是最重要的了!
没错,这个就是 next 数组的作用了 :
返回当前的最长公共前后缀长度,假设为 len 。因为数组是由 0 开始的,所以 next数组让第 len 位与主串匹配就是拿最长前缀之后的第 1 位与失配位重新匹配,避免匹配串从头开始!如下图所示!
(重新匹配刚才的失配位!)
如果都说成这样你都不明白,那么你真的得重新理解什么是 KMP 算法了!
接下来最重要的,也是 KMP 算法的核心所在,就是 next 数组的求解!不过,在这里我找到了一个全新的理解方法!如果你懂的上面我写的的,那么下面的内容你只需稍微思考一下就行了!
跟刚才一样,我用一句话来阐述一下 next 数组的求解方法,其实也就是两个字:
继承
a 、当前面字符的前一个字符的对称程度为 0 的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是 0 ,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如 agcta 这个里面 t 的是 0 ,那么后面的 a 的对称程度只需要看它是不是等于第一个字符 a 了。
b 、按照这个推理,我们就可以总结一个规律,不仅前面是 0 呀,如果前面一个字符的 next 值是 1 ,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是 1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是 2 了。有两个字符对称了。比如上面 agctag ,倒数第二个 a 的 next 是 1 ,说明它和第一个 a 对称了,接着我们就把最后一个 g 与第二个 g 比较,又相等,自然对称成都就累加了,就是 2 了。
c 、按照上面的推理,如果一直相等,就一直累加,可以一直推啊,推到这里应该一点难度都没有吧,如果你觉得有难度说明我写的太失败了。
当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。
如果蓝色的部分相同,则当前 next 数组的值为上一个 next 的值加一,如果不相同,就是我们下面要说的!
如果不相同,用一句话来说,就是:
从前面来找子前后缀
1 、如果要存在对称性,那么对称程度肯定比前面这个的对称程度小,所以要找个更小的对称,这个不用解释了吧,如果大那么就继承前面的对称性了。
2 、要找更小的对称,必然在对称内部还存在子对称,而且这个必须紧接着在子对称之后。
如果看不懂,那么看一下图吧!
好了,我已经把该说的尽可能以最浅显的话和最直接的图展示出来了,如果还是不懂,那我真的没有办法了!
针对KMP算法我在添加一下我自己人为非常重要的认识
1.KMP的核心在于移位代替回溯,我们通过查找出最长的公共前后缀,从而确定了可以最大效率简化我们的时间复杂度的移位的最大长度
先附图在附代码(求next数组的)解释:
void makeNext(const char P[],int next[]){ int q,k;//q:模版字符串下标;k:最大前后缀长度 int m = strlen(P);//模版字符串长度 next[0] = 0;//模版字符串的第一个字符的最大前后缀长度为0 for (q = 1,k = 0; q < m; ++q)//for循环,从第二个字符开始,依次计算每一个字符对应的next值 { while(k > 0 && P[q] != P[k])//递归的求出P[0]···P[q]的最大的相同的前后缀长度k k = next[k-1]; //不理解没关系看下面的分析,这个while循环是整段代码的精髓所在,确实不好理解 if (P[q] == P[k])//如果相等,那么最大相同前后缀长度加1 { k++; } next[q] = k; }}
下面我们再来讲解一下利用next数组的KMP算法部分:
先上代码:
#include<stdio.h>#include<string.h>void makeNext(const char P[],int next[]){ int q,k; int m = strlen(P); next[0] = 0; for (q = 1,k = 0; q < m; ++q) { while(k > 0 && P[q] != P[k]) k = next[k-1]; if (P[q] == P[k]) { k++; } next[q] = k; }}int kmp(const char T[],const char P[],int next[]){ int n,m; int i,q; n = strlen(T); m = strlen(P); makeNext(P,next); for (i = 0,q = 0; i < n; ++i) { while(q > 0 && P[q] != T[i]) //这里我们采取的是移动模式串的策略,可能看不出来,这需要我们画图来看 q = next[q-1]; if (P[q] == T[i]) { q++; } if (q == m) { printf("Pattern occurs with shift:%d\n",(i-m+1)); } } }int main(){ int i; int next[20]={0}; char T[] = "ababxbababcadfdsss"; char P[] = "abcdabd"; printf("%s\n",T); printf("%s\n",P ); // makeNext(P,next); kmp(T,P,next); for (i = 0; i < strlen(P); ++i) { printf("%d ",next[i]); } printf("\n"); return 0;}
以上就是我对KMP算法核心的了解
附上自己封装的KMP算法的代码如下:
#include"iostream"#include"cstdio"#include"cstdlib"#include"cstring"#define N 100using namespace std;template<typename T> class kmp;template<typename T> istream& operator>>(istream&,kmp<T>&);template<typename T> ostream& operator<<(ostream&,kmp<T>&);template<typename T>class kmp{public:kmp(){memset(next,0,sizeof(next));memset(pattern,0,sizeof(pattern));memset(mother,0,sizeof(mother));num=plength=mlength=fpos=0;} friend istream& operator>><>(istream&,kmp<T>&);friend ostream& operator<<<>(ostream&,kmp<T>&);void getnextone(); //未优化的void find();void count();private:T pattern[N];int plength; T mother[N];int mlength;int next[N];int num; //母串中包含的个数 int fpos;};template<typename T>istream& operator>>(istream& in,kmp<T>& k){cout<<"请输入母串的长度"<<endl;cin>>k.mlength;cout<<"请输入母串"<<endl;for(int i=0;i<k.mlength;i++) cin>>k.mother[i];cout<<"请输入模式串的长度"<<endl;cin>>k.plength;cout<<"请输入模式串"<<endl;for(int i=0;i<k.plength;i++) cin>>k.pattern[i];return in;}template<typename T>ostream& operator<<(ostream& out,kmp<T>& k){cout<<"next数组的内容如下,以供查错"<<endl;for(int i=0;i<k.plength;i++) cout<<k.next[i]<<' ';cout<<endl; cout<<"母串中包含的传的个数是"<<k.num<<endl; cout<<"第一次出现模式串的位置是"<<k.fpos<<endl;return out;}template<typename T>void kmp<T>::getnextone(){//next[0]=0,因为0号位置没有前缀和后缀 int k=0; //目前最长公共前后缀的长度int q=1; //q记录目前扫描的的位置 for(;q<plength;q++) //永远记住,k代表的是长度,实际上的区间位置是0--k-1适合和额前缀 {while(k>0&&pattern[k]!=pattern[q]) k=next[k-1]; //算法中描述的部分 if(pattern[k]==pattern[q]) k++; //再次匹配,我们扩充最长公共前后缀 next[q]=k; }}template<typename T>void kmp<T>::find(){int i=0;int j=0;getnexttwo();for(;i<mlength;i++){while(j>0&&pattern[j]!=mother[i]) j=next[j-1];if(pattern[j]==mother[i]) j++;if(j==plength){fpos=i-plength+1; //j-fpos+1=k.plengthcout<<"我们找到了匹配的模式串,第一次出现的位置在"<<fpos<<endl; return ;}}cout<<"母串中不存在匹配的模式串"<<endl;return ;}int main(){kmp<int> my;cin>>my;my.find();cout<<my;return 0;}
- KMP算法总结(纯算法,为优化,没有学应用)
- kmp算法理解(纯收集)
- KMP算法总结(转)
- KMP算法及其优化算法
- KMP算法及其优化
- KMP算法优化
- KMP算法的优化
- KMP算法及其优化
- kmp算法的优化
- kmp算法的应用
- KMP算法应用
- KMP算法及其应用
- 字符串查找(KMP算法及其优化)
- C++实现KMP算法(优化版)
- KMP算法总结
- kmp算法总结
- KMP算法总结
- KMP算法总结
- leetcode(274):H-Index
- 冒泡排序初次建表比较和交换次数分析
- 【myeclipse】怎样把myeclipse的自动验证和自动构建都关掉
- UBUNTU 一些错误解决
- JAVA jvm的内存
- KMP算法总结(纯算法,为优化,没有学应用)
- SO_REUSEADDR和SO_REUSEPORT的区别
- 【myeclipse】显示插入的图形文件存储位置
- 基于AngularJS+NodeJS+Bootstrap+SpringMVC构建项目(1)
- 10G RAC VIP漂移后客户端的连接
- 41个Web开发者必须收藏的JavaScript实用技巧
- QTableView
- 翻转子串
- 在npm发布module