朴素串匹配算法说明
串匹配算法最常用的情形是从一篇文档中查找指定文本。需要查找的文本叫做模式串,需要从中查找模式串的串暂且叫做查找串吧。
为了更好理解KMP算法,我们先这样看待一下朴素匹配算法吧。朴素串匹配算法是这样的,当模式串的某一位置失配时将失配位置的上一位置与查找串的该位置对齐再从头开始比较模式串的每一个位置。如下图所示。
KMP串匹配算法解析
KMP串匹配算法是Knuth-Morris-Pratt算法的简称,KMP算法的思想就是当模式串的某一位置失配时,能不能将更前面的位置与查找串的该位置对齐,并且直接从该位置开始比较。按照这个思路走,问题叫变成了:当模式串的某一位置失配时要找到一个更前面的位置与查找串的该位置对齐。模式串的某个位置失配时的这个更前面的位置就叫做回溯位,通常用next表示,它的计算公式是:
next[i]= { 0; 当 i = 1
k; 对于串P,存在1 <= k < 使得 P1..Pk-1 == Pi-k+1..Pi-1
1; 其他情况 }
这个公式对应的串的下标是从1开始的。这个公式只说明:模式串中某一位置(不包含此位置)之前部分具有首尾相同的子串(即自匹配,比如ABCABA最后一个A之前头部和尾部都包含了子串AB)时,如果该位置失配可以直接将头部子串的下一个位置和该处对齐(比如模式串ABCABA在最后一个A处失配可以直接滑动模式串将C对齐原来最后那个A对齐的位置),这样可以去掉模式串在某位置失配时该位置之前的子串在朴素匹配算法中存在的冗余比较(如果用朴素匹配算法,需要将模式串ABCABA移动三次才能使得C对齐原来最后那个A对齐的位置)。模式串中某一位置(不包含此位置)之前部分不具有首尾相同的子串时,在该位置失配时可以直接让模式串的开始位置对齐该位置。如下图。
这里只给出了算法的说明,但是如何能够证明算法是正确的呢?这个说麻烦也麻烦,说简单也简单。为什么麻烦呢?因为我没办法用形式化的语言给出证明过程,就像数学里面的证明过程一样。其实自己通过形象思维演示一下串匹配的滑动过程就能够相信这个算法肯定是正确的。我也懒得给出证明过程。
接下来给出KMP算法的完整代码。
#include <iostream> #include <iomanip> #include <vector> #include <string> #include <cstdlib> using namespace std; void get_next(const string & M,vector<int> & next); int KMP_match(const string & S,const string & M,int pos); int main( ) { string S="abcdefghabcdefghhiijiklmabc"; string T="hhiij"; int pos =KMP_match(S,T,3); cout<<"\n"<<pos<<endl; system("pause"); return 0; } void get_next(const string & M,vector<int> & next) { //按模式串生成vector<int> next(M.size(),-1); //这里的串的第1个元素下标是0 int i = -1, j = 0; int M_len = M.size()-1; do { if((i < 0) || (M[i] == M[j])) { i++; j++; next[j] = i; } else i = next[i]; cout<<"i="<<right<<setw(3)<<i <<" j="<<right<<setw(3)<<j <<" next["<<j<<"] ="<<right<<setw(3)<<next[j]<<endl; }while( j < M_len); } int KMP_match(const string & S,const string & M,int pos) { int j = pos, i = 0;//这里的串的第1个元素下标是0 int S_len = S.size(); int M_len = M.size(); if((S_len-pos) < M_len) return -1; vector<int> next(M.size(),-1); get_next(M,next); while (i<M_len && j<S_len) { if (i < 0 || S[j]==M[i]) { ++i; ++j; } else i = next[i];//j不变,i跳动 } if (i == M_len) return j-i;//匹配成功 else return -1; }