首页 技术 正文
技术 2022年11月8日
0 收藏 986 点赞 2,047 浏览 3241 个字

讲KMP算法,离不开BF,实际上,KMP就是BF升级版,主要流程和BF一样

不同是在匹配失败时能利用子串的特征减少回溯,利用根据子串特征生成的Next数组来减少

<( ̄︶ ̄)↗[GO!]

!!!所有数组下标都是从0开始

1. 先看看BF算法(暴力破解)

int Brute_force_1(const char *S, const char *T)
{
if (!S || !T)
return -1;
int lenS = strlen(S);
int lenT = strlen(T);
int i = 0;//主串下标索引
int j = 0;//子串下标索引
while(i < lenS && j < lenT)
{
if (S[i] == T[j])//如果相等一直继续往下匹配
++i,++j;
else//不相等i和j开始回溯
{
i = i-j+1;
j = 0;
}
}
if (j == lenT)
return i - j;
return -1;
}

​BF算法有几种不同实现,但最终思想都是一样的,以下就是另一个BF实现

int Brute_force_2(const char *S, const char *T)
{
if (!S || !T)
return -1;
int lenS = strlen(S);
int lenT = strlen(T);
for (int i = 0; i <= lenS - lenT; ++i)
{
int k = i, j = 0;
while (k < lenS && j < lenT && S[k] == T[j])
{
++j;
++k;
}
if (j == lenT)
return i; //说明匹配到了
}
return -1;
}

​你完全可以根据自己的理解写出BF算法,但在这里,为了BF和KMP统一,我们还是采用第一种实现,即容易看出回溯操作的实现

2. Next[]数组

​事实上,书上的next数组生成算法是经过优化后的算法,比较难懂,但你完全可以按照自己的理解做一个

注意:Next[]数组只是在KMP中字符串匹配失败时使用的

void GetNext(int Next[], char *str)
{
assert(str!=NULL);
int len = strlen(str);
if(len>1)Next[0]=0;
//其实Next[0]等于0或者等于-1效果没什么影响,
//因为在KMP中不匹配时判断是不是第一个字符不匹配用用的是j==0;-----if (j==0||Next[j]==0),
if(len>2)Next[1]=0;
//Next[]等于0时说明需要讲i回溯到子串头的下一个位置(i=i-j+1);
//此时j也回到子串头位置(j=0)
for(int i=2;i<len;++i)
{
for(int j=i-1;j>0;--j)
{
if(!strncmp(&str[0],&str[i-j],j))
{
Next[i]=j;break;//找到最大重复子子串(子串中的子串)
//Next[]为其他值则i不变,讲j回溯到Next[j]的位置(j=Next[j])
}
else Next[i]=0;
}
}
}

​这个时间复杂度要比书上的方法高很多,但好理解,真实的反映了Next数组的本质。

3. KMP

int KMP(const char *S, const char *T, const int *Next)
{
if (!S || !T||!Next)
return -1;
int lenS = strlen(S);
int lenT = strlen(T);
int i = 0;//主串下标索引
int j = 0;//子串下标索引
while(i < lenS && j < lenT)
{
if (S[i] == T[j]) ++i,++j;//若相等则继续匹配下一个字符
else//不相等则回溯
{
//(当j==0时,即第一个字符不匹配,和Next[j]==0时事实上与BF算法相同)
if (j==0||Next[j]==0)
{
i = i-j+1;
j = 0;
}
else j = Next[j];//主串i位置不变,讲子串下标索引挪到Next[j]的位置
}
}
if (j == lenT)
return i - j;
return -1;
}

​这个回溯时的操作实际上是把两种情况合成一种,拆开后就是下面的,就是生成next数组那块三种情况

while (i < lenS && j < lenT)
{
if (S[i] == T[j])
++i, ++j;
else
{
if (j == 0)
{
++i; //等价于i = i-0+1;j本身就等于0
}
else if (Next[j] == 0)
{
i = i - j + 1;
j = 0;
}
else
{
j = Next[j];
}
}
}

扩展

​Next数组有进一步改进的可能,如果发生失配,失配点子串字符若与回溯到的字符相同,则再次匹配肯定失败,所以改进的Next数组进一步处理了这种情况,消除了回溯

void GetNext_pro(int Next[], const char *str)
{
assert(str!=NULL);
int len = strlen(str);
if(len>1)Next[0]=-1;
//其实Next[0]等于0或者等于-1效果没什么影响,
//因为在KMP中不匹配时判断是不是第一个字符不匹配用用的是j==0;-----if (j==0||Next[j]==0),
if(len>2)Next[1]=0;
//Next[]等于0时说明需要讲i回溯到子串头的下一个位置(i=i-j+1);
//此时j也回到子串头位置(j=0)
for(int i=2;i<len;++i)
{
for(int j=i-1;j>0;--j)
{
if(!strncmp(&str[0],&str[i-j],j))
{
if(str[i]==str[j])
Next[i]==Next[j];
else
Next[i]=j;
break;//找到最大重复子子串(子串中的子串)
//Next[]为其他值则i不变,讲j回溯到Next[j]的位置(j=Next[j])
}
else Next[i]=0;
}
}
}

测试代码

int KMP(const char *S, const char *T)
{
if (!S || !T)
return -1;
int Next[MAXSIZE] = {0};
GetNext(Next,T);
print_arr(Next, strlen(T));
GetNext_pro(Next,T);
print_arr(Next, strlen(T)); int lenS = strlen(S);
int lenT = strlen(T);
int i = 0;//主串下标索引
int j = 0;//子串下标索引
while(i < lenS && j < lenT)
{
if (S[i] == T[j])
++i,++j;//若相等则继续匹配下一个字符
else//不相等则回溯
{
//(当j==0时,即第一个字符不匹配,和Next[j]==0时事实上与BF算法相同)
if (j==0||Next[j]==0)
{
i = i-j+1;
j = 0;
}
else j = Next[j];//主串i位置不变,将子串下标索引挪到Next[j]的位置
}
}
if (j == lenT)
return i - j;
return -1;
}
int main(void)
{
char source[MAXSIZE] = "adcfabadcf";
char target[MAXSIZE] = "abcabcabbac";
printf("%d\n", Brute_force_1(source, target));
printf("%d\n", Brute_force_2(source, target));
printf("%d\n", KMP(source, target));getchar();
return 0;
}
附上BF与KMP的比较,你会发现两者其实挺相似

总结

​其实核心就在于本文第一句话的理解。

​KMP在子串含有相同前后缀时,利用Next数组减少匹配失败时的回溯次数有优势,而改进的Next数组在此基础上若子串含有较多相同字符则更进一步减少回溯。

​所以KMP总之是利用子串的特征来削除回溯,如果子串并不具有这些特征,那就还没有BF好,因为KMP还需要额外的空间来存放Next数组

​书上的next数组的生成很难懂,加油理解中。。。(ง •_•)ง

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,489
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,904
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,737
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,490
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,128
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,291