彻底弄懂kmp-字符匹配

12 篇文章 0 订阅

        这个算法很有技巧性,这主要是next[],它的值与匹配字符串有关系,而与主字符串没关系,它是表示副字符串(匹配的字符串)的移动位置。

当主字符串的第i个与副的第j个不匹配,就要用到next[j],这时它就决定j的值了,意思就是:j此时的位置取决于匹配的字符串的结构。此时j的移动是为了与主字符串第i个相匹配,

我们不难发现,主字符串中的 i 并没有回溯,只是副字符串在回溯,但j并不是时时刻刻一个一个地移动,因为next[j]中的值并不是固定不变的。

下面要重点弄懂next数组的来历:

比如:

主:ffe,副:deoffef。

首先,要把副字符串传递给getnext函数。

void getnext(char *e)                                  //特别指明:next的值是字符串,自己与自己比较而得。

{

   int i=0,j=-1;  

    next[0]=-1;                                           //deoffef

   int r=strlen(e);                                        //deoffef      第一个与第二个比较。

  while(i<r)                                                     

    {                                                              //当i等于e的字符长度就退出,表示next[]已经赋值完了

         if(j==-1||e[i]==e[j])                             //从第一个开始判断(字符串的第一个与第二个匹配),如果相同就一直赋值给next[i],

          next[++i]=++j;                                // 如果匹配,就让j回溯,此时j一定会小于i,即向(j-1)的去寻找与第i个相匹配的。

        else j=next[j];                                   //找的到,进入if语句,那么next[i]的值就是副字符中与第主字符串相同的位置。

  }                                                           //如果找不到,j最终会等于-1,也会进入if语句,此时的next[i]=-1,

}

分析了getnext函数,deoffef 对应的next中的值为:-1,-1,-1,-1,-1,-1,-1。因为第一个字母d,后面的没有一个为d。

再比如 “aaaaccc”,next中的值:-1,0,1,2,-1,-1,-1。

最后,你会发现,next的值,只是为了得到第一字母与后面的字母(与第一相同的字母)的位置,以便在与主字符串相比较时,发生不相同是,就要去找主字符串中的第i个,与第j个是否相,这时,就取出next[j]的值,如果为-1,表明该字符与副字符串中第一个不同,然后,就跳到第一个字母,与主字符串第i个相比较。我们不如好好想想,假如,副字符串与主字符串有j个不相匹配,前面虽然有j-1个相匹配,但还是不成立,所以我就让它与第一个去比较,另外,不要担心中间有可能刚好相匹配,这个问题不大,因为相匹配的

字符串,next中的值只与副字符串有关,所以,如果后面不匹配,它会一次在前面找。

如何求next函数。

next值仅取决于模式串而与主串无关。可以从next函数的定义出发,用递推的方法求得next函数值。

有定义 next[0]=-1.

设 next[j]=k,即有

                     “t1t2......tk-1"="t(j-k+1) t(j-k+2)....tj"

这就是说next[j+1]=k+1,即

                        next[j+1]=next[j]+1

第二种情况:若tk不等于tj则

                      ”t1t2.....tk"≠“tj-k+1tj-k+2.....tj"

此时可以把求next函数值的问题看成一个模式匹配问题,整个模式串即是主串又是模式,而当前在匹配的过程中,已有“t1t2......tk-1"="t(j-k+1) t(j-k+2)....tj"式成立,则当tk≠tj时应将模式向右移动,使得第next[k]个字符和”主串“中的第j个字符相比较。若next[k]=k',且tk'=tj',则说明在主串第j+1个字符前存在一个最大度为k‘的子串,使得

 “t1t2......tk-1"="t(j-k+1) t(j-k+2)....tj'

因此 next[j+1]=next[k]+1

同理若tk≠tj则模式继续向右移动,使得next[k']个字符和tj对值,依次类推,直到tj和模式中的某个字符匹配成功或不存在任何k’(1<k'<k<...<j)满足,此时next[j+1]=0.

求next函数算法如下:

void getnext(char t[],int next[])

{

  int i=0,j=-1;

  next[0]=-1;

while(i<t[0])

{

    if(j==-1||t[i]==t[j])

       next[++i]=++j;

  else

        j=next[j];

   }

}

还有所不明白:点击打开链接看这个

下面以一个列子,来看看,

题意:输入两个字符串s,t。要你把它们合并,要求是:把它相同的一段只保留一个。比如.aabat bat 答案应该是 aabat,另外,如果两者分别当作主串都没共相同的,就按照

字典循序合并,比如sfetrg aerf 输出应该是:aerfsfetrg,如果互换匹配都有公共子段,应该输出公共子段长的那种方案,比如:staa aastaa输出应该是:aastaa.

该题就是用kmp算法实现的。代码如下:

#include<stdio.h> 
#include<string.h>  
#define MAX 100005
 
int next[MAX];
  
void get_next(char *e)  
{  
    int i=0,j=-1;   
    int r=strlen(e);
    next[0]=-1; 
    while(i<r) 
	{
        if(j==-1||e[i]==e[j])  
         next[++i]=++j; 
        else 
          j=next[j]; 
	}
	for(int i=0;i<r;i++)
	//printf("%d ",next[i]);
	//printf("\n");
} 
 
int kmp(char *s,char *t)  
{  
    int i=0,j=0;  
    get_next(t);  
    int n=strlen(s),m=strlen(t);  
    while(i<n&&j<m)
      {
        if(j==-1||s[i]==t[j])  
            ++i,++j;  
        else  
            j=next[j]; 
      } 
   // printf("*%d*",j);
    return j;    
} 
 
int main()  
{  
    char s[MAX],t[MAX];  
    while(scanf("%s%s",s,t)!=EOF)  
    {  
        int x=kmp(s,t);  
        int y=kmp(t,s);
        if(x==y)  
        {  
            if(strcmp(s,t)>0)  
            {  
                printf("%s",t);  
                printf("%s\n",s+x);  
            }  
            else  
            {  
                printf("%s",s);  
                printf("%s\n",t+x);  
            }  
        }  
        else if(x>y)  
        {      
            printf("%s",s);  
            printf("%s\n",t+x);  
        }  
        else  
        {  
            printf("%s",t);  
            printf("%s\n",s+y);  
        }  
    }  
    return 0;  
}  

/* 
cac abaabacac
*/



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值