前缀表思想:优化字符串匹配在计算机科学领域,字符串匹配是一项至关重要的任务。KMP算法凭借其效率和处理海量数据的能力,在解决字符串匹配问题时脱颖而而出。KMP算法的核心思想是利用前缀表,一个记录着模板串(要查找的模式)中每个后缀与该后缀的前缀的最长匹配长度的数组。
让我们用一个例子来理解前缀表。假设模板串是"abcab",那么它的前缀表如下:
这表明,当模板串从索引2开始与自身匹配时,它可以匹配前缀"ab",长度为2。前缀表为KMP算法提供了关于模板串匹配行为的重要信息。
Next数组:跳跃式匹配
Next数组是KMP算法的另一个关键组成部分。这是一个大小与模板串相同的数组,它存储了每个后缀匹配失败后,模板串应该从哪个位置重新开始匹配。
想象一下,我们在寻找模板串"abcab"在目标串"abcabcbabc"中的位置。当我们比较模板串的第二个字符"b"与目标串的第三个字符"a"时,匹配失败。此时,Next数组的作用就凸显出来了。Next[2]告诉我们,匹配失败后,我们应该从模板串的第一个字符"a"重新开始匹配。
算法流程:高效匹配
有了前缀表和Next数组,KMP算法就可以高效地进行字符串匹配。算法流程如下:
计算前缀表: 根据模板串计算前缀表。
初始化Next数组: 初始化Next数组,Next[0]=-1,Next[1]=0。
循环匹配: 使用两个指针i和j,分别指向目标串和模板串。
匹配成功: 如果目标串中i处的字符与模板串中j处的字符相等,则i和j都加1。
匹配失败: 如果匹配失败,则根据Next数组跳转到模板串中的下一个匹配点。
匹配完成: 如果j等于模板串的长度,则目标串中从i-j开始的部分与模板串匹配。
代码示例:
以下Python代码展示了如何使用KMP算法进行字符串匹配:
void get_Next(string s, int next[]) //这个函数对字符串s进行预处理得到next数组
{
int j = 0;
next[0] = 0; //初始化
for(int i = 1; i<s.size(); i++){ //i指针指向的是后缀末尾,j指针指向的是前缀末尾
while(j>0&&s[i]!=s[j]) j = next[j-1]; //前后缀不相同,去找j前一位的最长相等前后缀
if(s[i]==s[j]) j++; //前后缀相同,j指针后移
next[i] = j; //更新next数组
}
}
void getnext01(char t[],int tlen,int next[6]){
int j=0;next[0]=0;
for(int i=1;i<tlen;i++){
j=next[i-1];
while(j>0&&t[i]!=t[j])//
{
j=next[j-1];
}
if(t[i]==t[j])
j++;
next[i]=j;
}
}
上面是计算模式串的前缀表next的代码,其流程见注释。
得到了next数组后,我们进入匹配查找的阶段。
int strSTR(string s, string t) //这个函数是从s中找到t,如果存在返回t出现的位置,如果不存在返回-1
{
if(t.size()==0) return 0;
get_Next(t, next);
int j = 0;
for(int i = 0; i < s.size(); i++){
while(j>0&&s[i]!= t[j]) j = next[j-1];
if(s[i]==t[j]) j++;
if(j==t.size()) return i - t.size() + 1;
}
return -1;
}
void getnext01(char t[],int tlen,int next[6]){
int j=0;next[0]=0;
for(int i=1;i<tlen;i++){
j=next[i-1];
while(j>0&&t[i]!=t[j])//t为模式串
{
j=next[j-1];
}
if(t[i]==t[j])
j++;
next[i]=j;
}
}
int qianzuipipei(char s[],int slen,char t[],int tlen,int next[6])//根据上面的python代码编写
{
int i=0,j=0;
while(i<slen)
{
if(s[i]==t[j])
{
i++,j++;
if(j==tlen)
return i-j;
}else if(j>0)
{
j=next[j-1];
}
else i=i+1;
}
return -1;
}
int main(int argc, char* argv[])
{
char s[]="acaaaaabaabcacaabc";
int slen=strlen(s);
// char t[7]="abaabc";
// char t[]="aabaaf";
char t[]="aaaaab";
int tlen=strlen(t);
int next[6];
getnext01(t,tlen,next);
// getnext_youhua(t,next);
for(int i=0;i<6;i++)
printf("%d= ",next[i]);
printf("\n");
int index=qianzuipipei(s,slen,t,tlen,next);
printf("index=%d\n",index);
//int index=kmp(s,slen,t,tlen,next);
// printf("index=%d\n",index);
return 0;
}
void getnext02(char t[],int tlen,int next[5]){
int j=0;next[0]=0;
for(int i=1;i<tlen;i++){
if(j>0&&t[i]!=t[j])//
{
j=next[j-1];
}
if(t[i]==t[j])
j++;
next[i]=j;
}
}
void getnext01(char t[],int tlen,int next[6]){
int j=0;next[0]=0;
for(int i=1;i<tlen;i++){
j=next[i-1];
while(j>0&&t[i]!=t[j])//对应严书83页p[k]!=p[j]的思想
{
j=next[j-1];
}
if(t[i]==t[j])//对应严书83页p[k]==p[j]
j++;
next[i]=j;
}
}
int strSTR(char s[],int slen, char t[],int tlen,int next[6]) //这个字符串匹配函数是从s中找到t,如果存在返回t出现的位置,如果不存在返回-1
{
int j = 0;
for(int i = 0; i < slen; i++){
while(j>0&&s[i]!= t[j]) j = next[j-1];
if(s[i]==t[j]) j++;
if(j==tlen) return i - tlen+ 1;
}
return -1;
}
int qianzuipipei(char s[],int slen,char t[],int tlen,int next[6])//和strSTR有什么不同,打断点跟踪
{
int i=0,j=0;
while(i<slen)
{
if(s[i]==t[j])
{
i++,j++;
if(j==tlen)
return i-j;
}
else if(j>0)//j>0不牵涉while循环条件相当于while(1),一旦满足j>0就进入循环
{
j=next[j-1];
}
else i=i+1;//j==0&&s[i]!=t[j],模式串0号单元与主串不匹配
}
return -1;
}
辅助理解:严蔚敏《数据结构》81页,关于kmp的两种情况(对应int qianzuipipei(char s[],int slen,char t[],int tlen,int next[6])//和strSTR有什么不同,打断点跟踪)
在求得模式的next函数之后,匹配可如下进行:假设以指针i和j 分别指示主串s和模式t中正待比较的字符,令i的初值为0,j的初值为0。若在匹配过程中 ,s[i]==t [j] ,则 i 和j 分别增1,否则,i不变,而j退到next[j]的位置再比较,若相等,则指针各自增1,否则j再退到下一个next 值的位置,依次类推,直至下列两种可能:
依次类推,直至下列两种可能:(对应strSTR)
一种是j 退到某个next 值(next [...next[j]...]])时字符比较相等,则指针各自增1,继续进行匹配;
另一种是j 退到值为-1(即模式的第一个字符“失配”,第一个字符对应数组的0号单元,所以数组0号单元失配则则退到0号单元的左边),则此时需将模式继续向右滑动一个位置,即从主串的下一个字符 s[i+1] 起和模式重新开始匹配。
总结
KMP算法通过巧妙地利用前缀表和Next数组,将字符串匹配问题转化为一系列高效的比较操作。它在处理大规模文本数据时,展示了卓越的性能,使之成为解决字符串匹配问题的一项强大技术。
常见问题解答
1. 前缀表是如何帮助KMP算法的?
前缀表提供了一种快速查找模板串中每个后缀的最长匹配长度的方法,从而减少了不必要的比较操作。
2. Next数组在KMP算法中扮演什么角色?
Next数组在匹配失败时指示模板串中下一个潜在匹配点,从而实现了高效的跳跃式匹配。
3. KMP算法比其他字符串匹配算法有什么优势?
KMP算法利用前缀表和Next数组,即使对于非常长的模式,也能实现O(n)的时间复杂度。
4. 如何在实践中使用KMP算法?
KMP算法广泛应用于文本编辑器、搜索引擎和生物信息学等领域。
5. KMP算法有局限性吗?
KMP算法在处理不确定或模糊的模式时表现不佳。