字符串匹配KMP算法
2017-09-22 10:43
204 查看
1.解决的问题
假如有两个字符串,我们最有可能去寻找较短的一串是否在较长的一串中,也就是寻找子集的问题。例如:长串为A:“kmpalgorithm”,短串为B:“algo”。这里,我们称长串为“主串”,较短的为“模式串”。此时,我们用计算机的方式去思考如可解决上问题,显然暴力枚举是最简单的方式(我一直觉得当今算法带来的成本节省最迫切的是时间上的。):我们设置两层循环,外层用 i 来遍历主串字符,事实上用来确定子串是从哪里开始的(假设B是A的子串),所以这个变量需要记录,并且最坏情况下循环A.lenth()次,对于每一次循环 i ,我们对字串B用 j 来维护每一个字符,每次循环次数都是从(0~B.lenth()-1),每当发现 j > B.lenth(),则(0~B.lenth()-1)全部匹配完成,问题就解决了,否则 i 越界, j 越界都属于匹配不成功(B不是A的子串)。这样做的时间复杂度为O(n*m),如果数据量过大,计算机的计算时间增长过快,导致算法的不可用性。2.算法优点
KMP算法来自Knuth,Morris,Pratt三人共同提出(由三人名字首字母得来),此算法其对于任何模式和目标序列,都可以在线性时间内完成匹配查找。我们来回顾之前我们是怎么解决问题的,并且找出可能优化的步骤:首先对于主串的字符我们是不可避免的都要进行一次遍历,这是一种消除不确定的做法,否则你漏掉某一个可能起始的字符,你一定不太放心它是否就是答案,所以第一步我们不能动,保留枚举做法,但是对于模式串问题,朴素模式匹配(上面的做法的一个名字)依然采用暴力枚举来解决,看似也不可替代,事实上在模式串不停地向后位移过程中,非要每次位移一个单位才能消除不确定性么?如下例子:
首字符不匹配,向后位移模式串,直到首字符匹配:
此时首字符匹配,但问题是再向后匹配过程中到模式串的最后一位是D,而主串为空格:
此时,最关键的地方到了,我们要做的不是继续一格一格的位移模式串,而是实现跳跃,这次我们一次跳两格:
如上图,我们实现了跳两格操作,有同学问:你不是说为了防止遗漏不确定要枚举么?这里最大的优点在于就在于我敢拍着胸脯说:我这样做依然维护了避免不确定性这一原则,虽然发生了跳跃。我们回到位移之前,我们在字符D发生了不匹配现象,也就是说D之前的所有字符是成功匹配的,那么之前匹配好的串我们叫做F串,既然F串是成功的我们就要利用到这个信息,现在由于D的问题,我们不得不移动模式串,由于模式串的首字符或者说前缀字符是一定要与主串匹配,而我们已得到成功匹配的F串,也可以说在不得不位移的情况下,我们可以选择将前缀字符匹配到F中,并且是后缀优先匹配,因为我们要解决的是D的问题,这样就有了字符串前缀和后缀的概念。
我们用一个next[]的表来维护每个字符要移动时跳跃的步数,而这个步数就是最大模式串前缀和后缀重合的字符数。
接下来问题就快要解决了,我们知道了每个字符发生不匹配时需要跳几步,而且我都记录在了next[]数组里,用的时候查表即可。
接下来我们考虑如何生成next[],我们先把注意力集中到模式串,因为这个表是模式串的属性,于是问题转化为如何求解一个字符串的“部分匹配值”。
1.已知前一步计算时最大相同的前后缀长度为k(k>0),即P[0]···P[k-1];
2.此时比较第k项P[k]与P[q],如图1所示
3.如果P[K]等于P[q],那么很简单跳出while循环;
4.如果不等呢?那么我们应该利用已经得到的next[0]···next[k-1]来求P[0]···P[k-1]这个子串中最大相同前后缀,可能有同学要问了——为什么要求P[0]···P[k-1]的最大相同前后缀呢?是啊!为什么呢? 原因在于P[k]已经和P[q]失配了,而且P[q-k] ··· P[q-1]又与P[0] ···P[k-1]相同,看来P[0]···P[k-1]这么长的子串是用不了了,那么我要找个同样也是P[0]打头、P[k-1]结尾的子串即P[0]···Pj-1,看看它的下一项P[j]是否能和P[q]匹配。如图2所示
3.代码实现
#include<bits/stdc++.h> using namespace std; void make_next(string b, int next[], int len_b){ int i,k; next[0]=0; for(int i=1, k=0; i<len_b; i++){ if(b[i]==b[k]) k++; if(k&&b[i]!=b[k]) k=next[k-1]; next[i]=k; } cout<<"next_array:"; for (i = 0; i < b.length(); ++i) cout<<next[i]; cout<<endl; } void KMP(string a, string b){ int n = a.length(); int m = b.length(); int next[20]; make_next(b,next,m); for(int i=0, j=0; i<n; i++){ if(j&&a[i]!=b[j]) j=next[j-1]; if(a[i]==b[j]) j++; if(j==m) cout<<"Pattern occurs with shift:"<<i-m+1<<endl; } } int main(){ string a, b; cin>>a>>b; KMP(a,b); return 0; }
4.算法的优化
待续。。。相关文章推荐
- 字符串匹配的KMP算法
- 模式字符串匹配问题(KMP算法)
- KMP算法——字符串匹配
- 字符串匹配的KMP算法(转)
- POJ 3461 Oulipo(字符串匹配,KMP算法)
- KMP算法 字符串匹配
- 字符串匹配的KMP算法_阮一峰
- 字符串匹配之KMP算法
- C语言实现字符串匹配KMP算法
- 字符串匹配的KMP算法-16张图片看明白
- [算法与数据结构] - No.11 字符串匹配KMP算法
- C语言实现字符串匹配KMP算法
- 最长字符串匹配算法(KMP算法)
- 字符串匹配之KMP算法
- 字符串匹配的KMP算法
- 字符串匹配--KMP算法
- 字符串匹配的KMP算法
- sdut 2125串结构练习--字符串匹配【两种KMP算法】
- 字符串匹配的KMP算法
- 字符串匹配【KMP算法】