KMP模式匹配算法
普通的字符串匹配算法非常的低效,下面我为大家讲解KMP快速匹配算法。
讲KMP算法之前,我先讲一下普通的匹配算法
简单的说,普通的算法要对字符串进行匹配,就要对S进行大循环,直到匹配成功或者全部遍历为止。
char *S = '12351234';
char *T = '1234';
复制代码
完整代码
#include <stdio.h>
#include <string.h>
int sel(char * S,char *T){
int i=0,j=0;
while (i<strlen(S) && j<strlen(T)) {//循环开始
if (S[i]==T[j]) {//首字符对比
i++;
j++;
}else{
i=i-j+1;//匹配失败,回到上次匹配的下一位
j=0;//从T的首字符和S的下一位字符再继续匹配
}
}
if (j==strlen(T)) {//当j等于T的长度时说明匹配成功返回位置
return i-strlen(T)+1;
}
return 0;
}
int main() {
int add=sel("12351234", "1234");
printf("%d",add);
return 0;
}
//运行结果: 5
复制代码
普通算法流程
S: 1 2 3 5 1 2 3 4 //第一次 i=1, j=1
T: 1 2 3 4
S: 1 2 3 5 1 2 3 4 //第二次 i=2, j=2
T: 1 2 3 4
S: 1 2 3 5 1 2 3 4 //第三次 i=3,j=3
T: 1 2 3 4
S: 1 2 3 5 1 2 3 4 //第四次失败i=1, j=0
T: 1 2 3 4
...
普通算法会无脑的循环匹配,其实效率是很低的,大家有没有发现,只要匹配失败就会从T的第i位开始从新匹配S的第一位。
S: 1 2 3 5 1 2 3 4 //第一次 i=1, j=1
T: 1 2 3 4
当T[0]=1, T[1]=2, S[1]=2, 显然T[0]≠T[1], T[1]=S[1],这里就是KMP算法的关键了,去除多余的判断。
KMP算法对比普通匹配算法的优势是:在保证i指针不回溯的前提下,当匹配失败时,让匹配字符串向右移动最大的距离。
要保证i指针不回溯且实现功能,就只能让j指针回溯。j指针回溯的距离,就是T字符串向右移动的距离,在T中每个字符对应j指针的位置可以用算法得出,并将得出结果存在一个数组中(数组名为 next)。
每个匹配的字符串(T),第一个对应的值为0,第二个对应的值为1。
例如:求Y(1,2,3,1,2,4,5)的next,前两个字符对应的0和1是固定的。
第三个字符 “3” 提取字符“12”,“1”和“2”不相等,相同的个数为0,0+1=1,所以“3”对应的next是1。
第四个字符 “1” 提取字符“123”,首先“1”和“3”不想等,相同个数为0,0+1=2,所以“1” 对应的next是1。
第五个字符 “2” 提取字符“1231”,第一个“1”和最后一个“1”相同,相同个数为1,1+1=2,所以“2” 对应的next是2。
第六个字符 “4” 提取字符“12312”,前面两个“12”和后面两个“12”相同,相同个数为2,2+1=3,所以“4” 对应的next是3。
第七个字符 “5” 提取字符“123124”,第一个字符“1”和最后一个字符“4”不相等,相同个数为0,0+1=1,所以“5” 对应的next是1。
最终得出next数组结果值为(0,1,1,1,2,3,1)
具体算法如下
一
Y(下标从1开始):“1231245”
next数组(下标从1开始):01
第三个字符为“3”:由于前一个字符“2”的next的值为1,取Y[1]=“1”和“2”比较,不相等,继续;
由于next[1]=0,结束。“3”对应的next值为1。(只要循环到next[1]=0,该字符的next都为1)
复制代码
二
Y:“1231245”
next数组:011
第四个字符为“1”:由于前一个字符“3”的next的值为1,取Y[1]=“1”和“3”比较,不相等,继续;
由于next[1]=0,结束。“1”对应的next值为1。
复制代码
三
Y:“1231245”
next数组:0111
第五个字符为“2”:由于前一个字符“1”的next的值为1,取Y[1]=“1”和“1”比较,相等,1(前一个
字符“1”的next值)+1=2;“2”对应的next值为2。
复制代码
四
Y:“1231245”
next数组:01112
第六个字符为“4”:由于前一个字符“2”的next的值为2,取Y[2]=“2”和“2”比较,相等,2
(前一个字符“2”的next)+1=3“4”对应的next值为3。
复制代码
五
Y:“1231245”
next数组:011123
第七个字符为“5”:由于前一个字符“4”的next的值为3,取Y[3]=“3”和“4”比较,不相等,继续,
由于next[3]=1,所以取Y[1]=“1”和4比较,不相等,结束;“2”对应的next值为2。
复制代码
最终得出next数组结果值为(0,1,1,1,2,3,1)
算法实现 next数组
#include <stdio.h>
#include <string.h>
void Next(char*T,int *next){
int i=1;
next[1]=0;
int j=0;
while (i<strlen(T)) {
if (j==0||T[i-1]==T[j-1]) {
i++;
j++;
next[i]=j;
}else{
j=next[j];
}
}
}
复制代码
kmp算法
int KMP(char * S,char * T){
int next[10];
Next(T,next);//根据模式串T,初始化next数组
int i=1;
int j=1;
while (i<=strlen(S)&&j<=strlen(T)) {
if (j==0 || S[i-1]==T[j-1]) {//如果j==0那么表示不匹配,指针向下移动
//如果匹配成功指针也向下移动
i++;
j++;
}
else{
j=next[j];
}
}
if (j>strlen(T)) {
return i-(int)strlen(T);
}
return -1;
}
//主体
int main() {
int i=KMP("ababcabcacbab","abcac");
printf("%d",i);
return 0;
}
复制代码
运行结果:6
优化next数组
例如:
T: a b c a c
next 0 1 1 1 2
在T中,有两个字符“a”,我们假设第一个为 a1,第二个为 a2。在程序匹配过程中,如果 j 指针指向 a2 时匹配失败,那么此时, i 指针不动,j 指针指向 a1 ,很明显,由于 a1==a2,而 a2!=S[i],所以 a1 也肯定不等于 S[i]。
为了避免不必要的判断,需要对 next 数组进行精简,对于T来说,由于 T[4] == T[next[4]] ,所以,可以将next数组改为:
T: a b c a c
next :0 1 1 0 2 //前面说过,如果j==0那么表示不匹配,j和i指针向下移动
void Next(char*T,int *next){
int i=1;
next[1]=0;
int j=0;
while (i<strlen(T)) {
if (j==0||T[i-1]==T[j-1]) {
i++;
j++;
if (T[i-1]!=T[j-1]) {
next[i]=j;
}
else{
next[i]=next[j];
}
}else{
j=next[j];
}
}
}
复制代码