KMP算法简要概述
KMP缘由
在现有程序中,字符串当之无愧是门庭要塞,而字符串匹配也使用频率也相当的高,以往的
做法是使用暴力破解,通过枚举所有字符串匹配状态进行判定,但那也造成时间复杂度
O(n*m)相当高,为此提出KMP算法,有效降低时间复杂度至O(n+m)
暴力算法:
#include<bits/stdc++.h>
using namespace std;
void Massacre(){
char str[1024],match_str[1024];
int size_str,size_match_str;
scanf("%s\n%s",str,match_str);
size_str = strlen(str);
match_str[1024] = strlen(match_str);
for(int i=0;i<size_str;i++){
for(int j=0;j<size_match_str;j++){
if(str[i] == str[j]){
++i;
++j;
}else{
i = i-j+1;
j = 0;
}
}
}
}
int main(){
Massacre();
return 0;
}
暴力算法每次匹配失败都将 i 和 j 进行回溯,j 回溯至0,i 回溯至 i-j+1位,则最坏情况为:主串循环m次,每次循环遍历n次模式串,故时间复杂度O(n*m),空间复杂度O(1)。
KMP思路
可想而知,暴力法时间复杂度较大原因在于需要循环遍历m次模式串,有什么办法能避免这m次循环呢?
为此我们可以联想如果一个模式串如图1 abd 每个字符各不相同,那么当其中某个字符失配时,前面n位匹配字符将不能在i+n的范围再度匹配 ,如何利用这特殊条件简化算法就是KMP的关键
既然要求前n位模式串不同能够跳过i+n位,即跳过i+n位后便是当前i需要回溯的距离,这样不就保证 i 能够不回溯而只回溯 j,那么问题便转化为如何让前n位模式串不相同?
这里我们便需要利用到数学的换元思想,当在一个集合内存在我们不想要看到的某些数值,我们能够将它重新归纳一个子集合,并附上代号x。联想字符串要想前面n个数值不相同,那么我们便将前n个数值中前缀和后缀相同的字符归纳为一个子集合(声明下,因为是从模式串头部开始匹配,所以该子集合一定是从头开始的x个字符),那么当这个子集合最大,我们是否便可认为该字符串后缀x前无重复字符。
既然x前的字符不重复,那我们直接令j回溯到x字符串那位置,主串i不就无需回溯,然后问题进一步变成 讨论如何让j回溯到x?
这样问题不就明朗了,给x个标记,表明前缀x和后缀x距离,让j回溯这段距离不久可以了,这就是next[]数组的由来
#include<bits/stdc++.h>
using namespace std;
int main(){
int next[100]; //next数组表示x尾元素到模式串头部的距离
int backPot = -1,loopIndex = 0;
char match_str[100] = {0};
scanf("%s",match_str);
while(match_str[loopIndex] != '\0'){
if(backPot==-1 || match_str[backPot] == match_str[loopIndex]){
++backPot;
++loopIndex;
next[loopIndex] = backPot;
}else{
backPot = next[backPot];
}
}
}
首先backPot表示回溯点的位置,loopIndex用来遍历整个模式串,当如果backPot为模式串头部或者存在前后缀字符相等,就将其+1,并记入到 next[]数组中,如果不匹配,证明当前字符不在重复,backPot回溯到前缀x点重新匹配。
next[]求出,那么KMP算法也就算完成了,如下代码:
#include<bits/stdc++.h>
using namespace std;
void KMP_(){
int next[100];
int backPot = -1,loopIndex = 0;
char str[100] = {0},match_str[100] = {0};
int i,j;
scanf("%s\n%s",str,match_str);
next[0] = -1;
//寻找模式串前后缀子串的匹配next域
while(match_str[loopIndex] != '\0'){
if(backPot==-1 || match_str[backPot] == match_str[loopIndex]){
++backPot;
++loopIndex;
next[loopIndex] = backPot;
}else{
backPot = next[backPot];
}
}
for(i=0,j=0;str[i]!='\0'&&match_str[j]!='\0';){
if(j == -1 || str[i] == match_str[j]){
++i;
++j;
}
//优化算法,如果前缀子串的后一个字符仍不相等,则继续向前回溯
else if(str[i] != match_str[next[j]] && j != 0){
j = next[next[j]];
}
else{
j = next[j];
}
}
int loc = i-strlen(match_str);
char ans[100] = {0};
strncpy(ans,str+loc,strlen(match_str));
printf("%s location in %d",ans,loc+1);
}
int main(){
KMP_();
return 0;
}
输入:
abcdadealkagoasda
ago
输出:
ago location in 11
以上内容表述可能有些抽象,可能也存在错误,有何误点还请大家指教,谢谢