KMP算法是字符串间进行匹配的一种算法,区别于一般的简单模式匹配算法。
假设有主串s和子串t,判断t是否是s的子串???
一般的算法是将s[i]和t[j]逐个比较,当s[i]==t[j]时i++;j++;当s[i]!=t[j]时,i和j的值回溯i=i-j+1;j=0;这种算法当遇到子串t有较多元素重复或者s和t有较多相同时是比较低效的,
所以采用KMP算法。
KMP算法的核心是采用一个数组next存储子串t每次回溯的位置,且i的值不用回溯。
下面我们看一个例子:
主串:a b c a b c a b c.....
下标:1 2 3 4 5 6 7 8 9
子串:a b c a b x
下标:1 2 3 4 5 6
很明显主串和子串前5个字符是相等的,但第六个c和x不相等,采用KMP的话就可以i的值为6,然后j的值为3,也就是这样
i
主串:a b c a b c a b c.....
下标:1 2 3 4 5 6 7 8 9
j
子串: a b c a b x
下标: 1 2 3 4 5 6
至于j为什么是3,其实是由子串s自己决定的。子串abcabx的next数组:
next: 0 1 1 1 2 3
首先next[1]=0,然后next[2]看前面有多少个前后缀是相同的,很明显next[2]是0个所以是1,next[3]是ab也是0个所以是1,next[4]是abc也是1,next[5]是abca,a和最后一个a相同,所以next[5]=2,next[6]是abcab有前面两个ab和后面两个ab相同,所以next[6]=3.
求next数组的函数:
void get_next(char *t,int *next,int t_lenth)
{
int i,j;
i=1;j=0;
next[1]=0;
while(i<t_lenth)
{
//t[i]表示后缀字符,t[j]表示前缀字符
if(j==0||t[i]==t[j])
{
i++;
j++;
next[i]=j;
}
else
j=next[j];//回溯
}
}
主函数:
int main(void)
{
char S_str[MAXSIZE],T_str[MAXSIZE];
int nextval[MAXSIZE];
int s_lenth,t_lenth;
int N,i,j,k;
scanf("%d",&N);
for(k=0;k<N;k++)
{
scanf("%s",S_str+1);
scanf("%s",T_str+1);
s_lenth=0;j=1;//主串长度
while(S_str[j++]!='\0') s_lenth++;
t_lenth=0;j=1;//子串长度
while(T_str[j++]!='\0') t_lenth++;
//计算nextval数组
get_nextval(T_str,nextval,t_lenth);
//查看是否匹配
i=j=1;
while(i<=s_lenth&&j<=t_lenth)
{
if(j==0||S_str[i]==T_str[j])
{
i++;
j++;
}
else
j=nextval[j];
}
if(j>t_lenth)
printf("yes\n");
else printf("no\n");
}
return 0;
}改进的KMP算法:
其实是为了预防子串是:xxxxxxxb这种前面有多个重复的情况
假设主串:aaaaaaccc....
子串:aaaaaab
运用原来的KMP,i的值会到7,根据next数组,j的值会从7到1,前面6个a是都是相同的,第6个a和c不同,为什么还要比较下去呢?所以我们对next数组进行了优化成nextval数组。
求nextval数组的函数:
//计算nextval数组函数
void get_nextval(char *t,int *nextval,int t_lenth)
{
int i,j;
i=1;j=0;
nextval[1]=0;
while(i<t_lenth)
{
if(j==0||t[i]==t[j])//t[i]表示后缀字符,t[j]表示前缀字符
{
i++;
j++;
if(t[i]!=t[j])//当前字符与前缀字符不同
nextval[i]=j;
else
nextval[i]=nextval[j];
}
else
j=nextval[j];//j回溯
}
}