搞开发已经搞了几年了,回过头去看算法,还是在KMP这个算法上死了不少脑细胞。这个KMP算法和LZW压缩算法的思想有异曲同工之处,就是用已有的东西去推理出尽可能多的结论,这种算法往往不是特别好理解,因为有推理就会有证明,还有特殊情况或者优化,在这种变化下就会感到非常难受
KMP算法简而言之就是尽可能的减少BF中回溯的长度,从而减少不必要的执行,原理上来说就是A!=B,B!=C,=>A!=C这应该是最初提出这种算法的思想,这是能够减少回溯长度的最基本原理
以下代码中,Find_BF和Find_KMP应该是很容易看懂的,Find_KMP就是依据next数组对Find_BF的改进,而KMP算法中,最头疼的就是next数组的计算
首先借用别人画的一张图
首先,next数组的作用是,在不匹配时,j应该指向target的何处,根据图可以看出,若后缀与前缀有一部分相同,则可以省去前缀部分的回溯。若[0~K]是前缀,那么next[j]=K+1即可,那么重新整理思路,就是要在target中找出在各个位置不匹配时j的值,这个问题要仔细想想,如何在一个字符串中找出首尾相同的部分,前缀和后缀的长度是未知的,前缀后缀的长度该怎么确定?这就是计算next的一个核心思想,从前推到后,和LZW思想很相似,一个一个的向后推,next数组中记录的就是每个位置不匹配时该指向的位置
另外优化部分是由于比如target="aaaaaaa.b"这类大量重复字符下提出的,如果不优化,next就是-1 0 1 2 3 4.....然后1 2 3 4 他的字符都是a,此时回溯的效率非常低,因为这么多个a都要和已知的不相同的字符比较,由于字符是相同的,那么可以判定接下来的比较是不可能相等的,那么我在构造next时,判断如果字符相等,则用上一个位置中next存储的值。由于next构造是从前往后推出来的,所以可一次性回退,避免不必要的重复比较
#include<iostream>
using namespace std;
int Find_BF(char* source,char* target){
int j=0;
int t_len=strlen(target);
int s_len=strlen(source);
for(int i=0;i<s_len;i++){
if(source[i]==target[j]){
j++;//记录相同的长度,若长度与target相同,则表示已完全匹配
}
else if(j!=0){//回溯
i-=j;//将i回溯至source中target的第一个元素的位置(最近的),i++ 后将移向下一个位置进行比较
j=0;//将j归0,寻找source中下一个与target第一个元素相等的位置
}
if(j==t_len){
return i-(j-1);//i为在source中target最后一个元素的位置, i-(j-1) 表示找到的第一个元素的位置
}
}
return 0;
}
void GetNext(char* target,int*& next){
int t_len=strlen(target);
next=new int[t_len+1];
next[0]=-1;
int k=-1;
for(int i=0;i<t_len;){
if(k==-1||target[i]==target[k]){
++i;
++k;
if(target[k]==target[i]){//优化增添
next[i]=next[k];//优化增添
} //优化增添
else{//优化增添
next[i]=k;
}//优化增添
}
else{
k=next[k];
}
}
}
int Find_KMP(char* source,char* target,int* next){
int j=0;
int t_len=strlen(target);
int s_len=strlen(source);
for(int i=0;i<s_len;){
if(j==-1){//表示source[i]与target[0]不相同,右移i,进行下次比较
i++;
j=0;
}
if(source[i]==target[j]){
i++;//同时右移,进行下一次比较
j++;//记录相同的长度,若长度与target相同,则表示已完全匹配
}
else{//回溯
j=next[j];
}
if(j==t_len){
return i-1-(j-1);
}
}
return 0;
}
int main(){
char* S="asdadsadasdasavvbbcaababcasdcsadab";
char* T="ababc";
cout<<Find_BF(S,T)<<endl;
int* next;
GetNext(T,next);
cout<<Find_KMP(S,T,next)<<endl;
delete next;
return 0;
}