KMP算法

KMP算法总结

引言

学完KMP算法好长时间了,当初第一次接触时感觉不太好接受。今天我又温习了一遍,重新理了一下KMP算法的思路,结果发现其实并没有那么难,只是有点繁琐。现在我想利用空闲时间根据个人的看法对KMP算法做一下总结。
串是一个线性结构,串匹配就是在主串中定位匹配串第一次出现的位置。

基础串匹配算法

基本思想

主串 “abababcabcabeaba“
匹配串”abcabe
两个字符串从头开始逐个比较,如果匹配就继续往下比较,如果不匹配则匹配串全体往后移动一位,然后从匹配串的第一位开始再重新比较,直至全部匹配。
事实上该算法每次发现不匹配都需要有重新进行比较,这样每次不匹配时的回溯就大大的提高了时间复杂度。而KMP算法正好巧妙的解决了这一问题。

代码展示
int search(const char *mainstr, const char *matchstr)
{
     int mainlen = strlen(mainstr);
     int matchlen = strlen(matchstr);
     int i = 0;
     int j = 0;

     if(mainlen <=0 || matchlen <=0 || mainlen < matchlen)   //串不符合条件直接返回-1
     {
        return -1;
     }
     while(i < mainlen && j < matchlen)           //只要没有比较完,就继续比较
     {
         if (mainstr[i] == matchstr[j])           //如果匹配就继续往后比较
         {
            i++;
            j++;
         }else{                                   //否则重新开始比较
            i -= j - 1;
            j = 0;
         }
    }
    if (matchstr[j] == 0)                        //如果匹配串遇到0结束标志,标识匹配出与主串匹配
    {
       return i - j;
    }else{                                       //否则,则说明不匹配
       return -1;
    }
}

基础串匹配算法很明显是正确的,但是时间复杂度比较高。如果是最差情况下它的时间复杂度是O(m*n)m表示主串长度,n表示匹配串长度。如果匹配规模较大,则这种算法是很糟糕的!

KMP算法

构建next数组

next数组的构建很巧妙的避免了普通串匹配算法的回溯问题。如果该字符失配,主串停留在该位置不动,移动匹配串,然后进行比较。
next数组含义是失配点前面最长有几个字符与前缀字符串相同,表示如果某字符失配,则下一次该用下标为几的字符比较,简单来说就是作为判断匹配串一次性可以移多少的指标。
例如:ababcabce该串为匹配串,接下来我们来计算该串对应的next数组。(加颜色是为了能够更明确的说明)

代码展示
void GetNext(const char *str, int *next)   //传参前用 callloc 申请数组空间,所申请的空间全部为0
{
      int i;
      int j;
      int flag;

      for(i = 2; str[i]; i++)      //next数组前两个都是0,所以循环直接从2开始
      {
            flag = 1;
            while(flag)
            {
               if(str[i - 1] == str[j])  //如果失配点前一个字符与该字符对应的next 数组值
               {                         //为下标所对应的字符相同
                  next[i] = ++j;         //该next数组值在原来的基础上+1
                  flag = 0;              //跳出while循环
               }else if(j == 0){         //如果上一个条件不成立且找到字符串与前缀字符串相同时 j = 0
                  next[i] = j;           //直接令该字符所对应的next数组值为 0
                  flag = 0;              //跳出while循环
               }else{                    //如果 j !=0,直接令 j 等于与之相同的前缀字符的next数组值
                  j = next[j]; 
               }  
            }
      }
}

还有另外一种实现方式:

public static int[] getNext(String string) {
    char[] p = string.toCharArray();

    int[] next = new int[p.length];
    next[0] = -1;

    int j = 0;
    int k = -1;

    while (j < p.length - 1) {
        if (k == -1 || p[j] == p[k]) {
            next[++j] = ++k;
        } else {
            k = next[k];
        }
    }
    return next;
}
KMP

将主串和匹配串逐字符进行比较,如果匹配继续,否则找到失配点,利用构建好的next数组,找到失配点所对应的next数组的值,然后以该值为匹配串的下标,再将主串失配字符与该下标所对应的匹配串的字符比较。(这种方法如果失配,主串里的下标就好停留在那里,不会回溯,这样就避免了主串里将比较完的字符再重新比较)如果(主串长度)mainlen - i (主串下标) + j (匹配串的next数组值) < matchlen(匹配串长度);程序结束,再没必要比较。

在这里插入图片描述

代码展示
int Kmp(const char *mainstr, const char *matchstr)  //主串和匹配串
{
     int mainlen = strlen(mainstr);
     int matchlen = strlen(matchstr);
     int *next = NULL;
     int i = 0;
     int j = 0;

     if (mainlen <= 0 || matchlen <= 0 || mainlen < matchlen)    //输入的串不符合要求
     {
        return -1;
     }
     next = (int *) calloc(sizeof(int), matchlen); //用 callloc 申请数组,使申请完空间的值都为 0
     if (matchlen > 2)
     {
         GetNext(matchstr, next);    //构建next 数组
     }
     while(mainlen - i + j >= matchlen)    //只要还没有匹配完,就继续。且如果最后的长度小于
     {                                     //匹配串的长度就不要再进行比较了
            while(mainstr[i] == matchstr[j] && matchstr[j] != 0)   //如果匹配,并且匹配串没完,
            {
                i++;                                               //继续比较
                j++;
            }
            if (matchstr[j] == 0)    //如果匹配串遇到 0 结束标志,说明匹配
            {
                free(next);         //释放申请的空间
                return i - matchlen;    // 返回匹配起始点的下标
            }else if(j == 0){           //如果没有匹配,且 j = 0,进行主串的下一个字符比较
                i++;
            }else{
                j = next[j];     //让主串失配点与匹配串失配点的next 数组值为下标对应的匹配串字符比较
            }
     }
      free(next);        //释放申请的空间
      return -1;         //返回-1 表示主串与匹配串不匹配
}
/*
以上只是核心代码,输出时还需要判断,如果函数返回值为-1 ,说明不匹配;如果返回值为 1 ,
说明匹配。
*/

总结

KMP算法的时间复杂度为O(m + n),m表示主串长度,n表示匹配串长度。
KMP算法是一种改进的字符串匹配算法。关键就是避免了主串的回溯,从而提高了时间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值