• 那些有关求解next数组的算法


    next数组的历史

      有关字符串的模式匹配算法中,比较容易写出的是朴素的匹配算法也就是一种暴力求解方式,但是由于其时间复杂度为子串长度和主串长度的乘积,例如strlen(subStr) = n,strlen(mainStr) = m,则其时间复杂度为O(mn)。

      为了能够得到更有效的匹配算法,D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特--莫里斯--普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。这也即是KMP算法的设计思想。

    next数组的求解思路

      求next数组,next[j]表示,当模式串j位置与主串i位置处发生不匹配时,i指针不回溯,j指针回溯到next[j]的位置。

      对于求next[j]有三种情况:

    1、j = 0时,next[j] = -1;//即模式串的第一个字符与主串i位置发生不匹配,应将i跳过当前位置,从下一个位置和模式串的第一个字符继续比较。

    2、假设已知next[j] = k,即subStr[0,...,k-1] = subStr[j-k,j-1]。当subStr[k] = subStr[j]时,也就是说模式串满足subStr[0,...,k] = subStr[j-k,j],可以得知next[j+1] = k + 1 = next[j] + 1;

    3、当subStr[k] != subStr[j]时,就需要从k位置之前去查找与subStr[j]匹配的位置,假设为j'。这样问题又可以转化为第二种情况,即next[j+1] = next[j'] + 1 = k' + 1。

    三种求解next数组的算法

      但是如何去求解next数组呢?有关这个问题,我思考了很长时间,下面给出几种算法:

      算法一,严格根据next数组的定义:

     1 void getNext(char subStr[],int next[])
     2 {
     3     int i = 1, j = i - 1,k = -1;
     4     next[0] = -1;
     5     while (i < strlen(subStr))
     6     {
     7         //k = -1时表示j指针回溯到第一个字符的位置
     8         //subStr[k] == subStr[i-1]表示第k个字符和i - 1个字符相等,属于情况二
     9         if (k == -1 || subStr[k] == subStr[i-1])
    10         {
    11             next[i] = k + 1;
    12             k = next[i];
    13             i++;
    14         }
    15         //情况三,不相等的话,要回溯j指针,subStr[j'] = subStr[i-1]的位置j'
    16         else
    17         {
    18             int t = i - 2;
    19             while (t>=0)
    20             {
    21                 if (subStr[t] == subStr[i - 1])
    22                 {
    23                     j = t;
    24                     break;
    25                 }
    26                 t--;
    27             }
    28             if (t < 0)
    29                 j = 0;
    30             k = next[j];
    31         }
    32 
    33     }
    34 }

      算法二,算法的设计思想和算法一大致相同

    void getNext(const char P[], int next[])
    {
        int q, k;
        int m = strlen(P);
        next[0] = -1;//模版字符串的第一个字符的最大前后缀长度为0
        for (q = 1; q < m; ++q)//for循环,从第二个字符开始,依次计算每一个字符对应的next值
        {
            k = next[q - 1];
            while (k > 0 && P[q - 1] != P[k])//迭代地求出P[0]···P[q]的最大的相同的前后缀长度k
            {
                k--;
            }
    
            if (P[q-1] == P[k])//如果相等,那么最大相同前后缀长度加1
            {
                k++;
            }
            if (k == -1)
                k = 0;
            next[q] = k;
    
        }
    }

      算法三,更加优化的求解next数组的算法

    void getNext(const char subStr[], int next[])
    {
        int i = 1, j = -1;
        next[0] = -1;
        while (i < strlen(subStr))
        {
                    //i从0开始的,属于情况二
                    //j是前后缀长度
            if (j == -1 || subStr[i] == subStr[j])
            {
                i++;
                j++;
                next[i] = j;
            }
                    //情况三,不同则j指针回溯
            else
                j = next[j];
        }
    }

      现在来进行总结一下,对于算法一和算法二来说,它们的时间复杂度是一样的,但是相对于算法三来说,虽然不如算法三高效,但是比较容易理解!

    PS:如果有误的地方,请指出,共同进步!

  • 相关阅读:
    ubuntu16.04下安装Wineqq+Firefox flash安装+搜狗输入法+截图软件ksnatshot
    集合数据类型
    hadoop2.7ubuntu伪分布式搭建
    广播变量&累加变量
    第一行代码----服务的最佳实践(体会,问题,解决)
    c语言中产生随机数
    如何把StringBuilder类型字符串中的字符换位置
    判断字母的大小写方法(3种)
    方法的参数个数讨论。
    中缀表达式->后缀表达式
  • 原文地址:https://www.cnblogs.com/tgycoder/p/4997067.html
Copyright © 2020-2023  润新知