C语言实现KMP子串匹配查找

KMP查找

1.概述

KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。

2.算法

设主串与子串如下:

主串abcabceabcabcdabcabcf
子串abcabcdabcabcf
  • 常见的字符串匹配:当主串与子串在逐字符匹配时一旦没有匹配成功,则让主串回跳到第一个位置重新开始比较,时间消耗很大。
  • 而KMP子串匹配:当某一位置匹配不成功时,主串不回跳使子串回跳,(但如果子串回跳到第一个位置,也会浪费一定的时间),KMP采用Next数组来解决这一问题。
Next数组的实现就需要引入字符串的前后缀

前缀:以当前串开头为开头的串,不能包括字符串本身。
后缀:以当前串结尾为结尾的串,也不能包括字符串本身。
最大匹配长度:前缀与后缀相等的最大字符个数。

试求出此字符串的前缀、后缀与前后缀最大匹配长度:ababa

前缀aababaabab
后缀abaabababa

由此可以看出此串最大匹配长度为3。

再回过头来看KMP匹配:

Next数组就是求子串的前后缀匹配最大长度
例如:

  • s2[0] a 前后缀最大匹配长度为:0
  • s2[1] ab 前后缀最大匹配长度为:0
  • s2[2] abc 前后缀最大匹配长度为:0
  • s2[3] abca 前后缀最大匹配长度为:1
  • s2[4] abcab 前后缀最大匹配长度为:2
  • s2[5] abcabc 前后缀最大匹配长度为:3
  • s2[6] abcabcd 前后缀最大匹配长度为:0

d和s2[13]:f 所面对的情况一样,肉眼很容易看出他的最大匹配长度为0,然而用代码实现并不是那么简单,你需要找到前一个字符所对应的Next数组,如果不为0你需要跳到它的Next数组所对应的s2的位置,如果为0你需要跳到子串开头位置,将此字符与其进行比较,如果都不相同,此时最大匹配长度才为0。

这么干说可能不太理解,用s2[6]='d’做例子。Next[5]=3,'d’则要和s2[3]='a’做比较,不相等。虽然s2[6]与s2[3]不相等,但他们所对应的前面的位置是相等的,设’d’现在所在位置为s2[3],Next[2]=‘0’,’d’则要和s2[0]='a’做比较,不相等,而s2也走到了字符串头的位置,这时才能说Next[6]=0,也就是说’d’所对应前后缀最大匹配长度为0

  • 后面情况类似…
下标0123456789101112131415161718
主串s1abcabceaabcabcdabcf
子串s2abcabcdabcf
Next00012301230

当把Next数组搞懂了,这个算法我们也完成80%了。

下面开始进行主串与子串的匹配

  1. s1与s2从下标为0的位置开始匹配
  2. 相等则向后匹配
  3. 直到s1[6]='e’与s2[6]='d’不相等时,开始使用Next数组
  4. 此时查看Next[5]=3,所以s1[6]='e’需要和s2[3]='a’开始比较,不相等
  5. 继续查看Next[2]=0,所以s1[6]='e’需要和s2[0]='a’再比较,还不相等
  6. 此时因为s2已经走到头了,所以s1字符串要向后偏移一个位置
  7. 让s1[7]与s2[0]重新开始比较,当匹配到s1[8]='a’与s2[1]=’b’时不相等
  8. 此时Next[0]=0,所以s1[8]='a’要与s2[0]='a’重新开始比较
  9. 后面步骤相同,略。
  10. 如果s2走到’\0’,则匹配成功,此时用s1走到的位置减去子串的长度则为子串第一次出现在主串中的位置。
总结来说就是通过Next数组实现子串的跳转与主串的匹配。

3.代码实现

#include<stdio.h>
#include<string.h>
#include<stdlib.h> 
int *GetNext(char *match) {   //获得Next数组
    int *pNext = NULL;
    pNext = (int *)malloc(sizeof(int)*strlen(match));   //手动申请空间      
    pNext[0] = 0;    //子串的第一个字符的Next一定为0
    int i = 1;  
    int j = i-1;  
    while(i<strlen(match))  {   
        if(match[i] == match[pNext[j]])   {    //如果与前面的字符相等
             pNext[i] = pNext[j]+1;    
             i++;    
             j = i-1;   
         }else   {      //不相等
             if(pNext[j] == 0)    {     //前一个next值是0  
                 pNext[i] = 0;     
                 i++;     
                 j = i-1;    
             }else    {    //前一个next值非0    
                 j = pNext[j]-1;    
             }   
         }  
     }  
     return pNext;    //返回Next数组
 } 
 int KMP(char *src,char *match) {    //主串与子串匹配的函数
     if(src == NULL || match == NULL)
         return -1;  
     //获得next数组  
     int *pNext = NULL;  
     pNext = GetNext(match);  //匹配
     int i,j;
     i=0;
     j=0; 
     while(i<strlen(src) && j<strlen(match)){    //判断主串或子串没到最后位置
         if(src[i] == match[j]){   //如果主串与子串所对应位置相等
             i++;
             j++;
         }else{ 	//不相等则匹配(子)串跳转 
             if(j == 0){   
	         i++;
             }else{
    		 j = pNext[j-1];
   	     }
  	 } 
      }
     //检测
     if(j == strlen(match)){     //如果子串匹配到了最后
         return i-j;    //返回子串在主串中首次出现的位置
     }else{    //如果主串到了最后匹配失败
         return -1;
     }
}
int main()
{
    char *src = "abcabceaabcabcdabcf";
    char *match = "abcabcdabcf";
    int n;
    n = KMP(src,match);
    printf("%d\n",n);
    return 0;
}
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值