关闭

算法-发明KMP算法的唐纳德·克努特是怎么想到失配函数next[j]的?

标签: 算法kmpnext
10569人阅读 评论(3) 收藏 举报
分类:

背景

字符串模式匹配,普通模式非常好理解,拿着模式串依次与主串做比较,知道完全匹配,但是这种算法,主串得不断地回溯,时间复杂度O(n*m)。


这里写图片描述

唐纳德·克努特

有没有降低时间复杂度的可能,唐纳德·克努特等人想到了一种办法不用使主串不停地回溯,而每次使模式串的某个字符与主串的待比较字符对齐,这个算法简称KMP。求解模式串的哪个字符该与这次比较的主串字符对齐,是KMP算法的核心,简称next函数或失配函数。这种算法求解复杂度降低到O(n+m)。

next函数语义

next[j]=k表达的意思是从模式串的 1~j-1 组成的子模式串,最长相同的前、后缀的长度为 k-1。举例说明,如下的字符串,next[6]=3,因为编号为6的字符c的最长前缀为编号为1的a ,编号为2的b 字符,最长后缀为编号为4的字符,编号为5的字符b,所以 k=3。

1 2 3 4 5 6 7 8
a b a a b c a c

再看一下失配函数next[j]的严格定义,模式串字符的编码从1开始。


这里写图片描述

next函数分析

next 函数值仅取决于模式串本身而与相匹配的主串无关。从next函数的定义出发用递推的方法求next函数值。

由定义得知 next[1]=0,设next[j]=k,这表明在模式串中存在下列关系

"P1...Pk-1" = "Pj-k+1...Pj-1"

图形化显示(一条竖线表示一个字符):


这里写图片描述

其中k为满足1 < k < j的某个值,并且不能存在k’ > k满足上个等式。此时 next[j+1]=? 分两种情况讨论,

1)若 Pk = Pj ,则 next[j+1] = next[j] + 1 ,即 k + 1 ,如下图显示:


这里写图片描述

2)若Pk不等于Pj,如下图所示,我们把如下字符,看成一个字符串,寻找它的最长相同的前、后缀:

"P1...Pj+1"


这里写图片描述

此时我们已知一个条件:

"P1...Pk-1" = "Pj-k+1...Pj-1"

也就是在上图中2个黄色区域表示的前、后缀字符串相等,这样我们依然在上图中的左侧黄色部分中寻找。最终找到了2块咖啡色区域 1~k’-1, k-k’+1~k-1 相等,根据next函数的定义,便是:

next[k]=k'


这里写图片描述

并且我们根据已知条件 ‘P1…Pk-1’ = ‘Pj-k+1…Pj-1’,可以推导出在右侧黄色区域也存在这样的咖啡色区域,根据等式传递,我们可以得出:

"P1...Pk'-1" = "Pj-k'+1...Pj-1"

因为Pj不等于Pk,所以我们新找出了一个k’(很显然1 < k’ < k),如果它真的满足了 Pj=Pk’,则 next[j+1] = k’ + 1 ,即 :

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

如果它很遗憾地又不等于Pj,也没关系,我们继续在[1,k’]这个区间内找这样的K点,如果真的不存在这样的k’,那么 根据定义可以得出:

next[j+1]=1

失配函数代码实现

        /// <summary>
        /// 失配函数
        /// </summary>
        /// <param name="p">模式字符串(编码从索引位置1开始)</param>
        /// <returns>模式字符串中每个字符的失配值数组</returns>
        private static int[] getNext(char[] p)
        {
            int[] next = new int[p.Length];
            next[1] = 0;
            int j = 1;
            int k = 0;

            while (j < p.Length - 1)
            {
                if (k == 0 || p[j] == p[k])
                {
                    next[++j] = ++k; //上述分析中的k'+1赋值给next[j+1]
                }
                else
                {
                    k = next[k]; //next[k]赋值给k,相当于上述分析中的k'
                }
            }
            return next;
        }

模拟分析

模拟失配函数求解的整个过程代码。

        static void Main(string[] args)
        {
            string pattern = "abaabcac";
            char[] pcharsfrom1 = preOperate(pattern);
            Console.WriteLine();
            int[] next = getNextWithTest(pcharsfrom1);

            printf(next);
            Console.ReadLine();
        }

预处理字符串,将字符串整体后移1位

        /// <summary>
        /// 预处理字符串,将字符串整体后移1位
        /// </summary>
        /// <returns></returns>
        private static char[] preOperate(string pattern)
        {
            char[] pchars = pattern.ToCharArray(0, pattern.Length);
            char[] pcharsfrom1 = new char[pchars.Length + 1];
            for (int i = pchars.Length; i > 0; i--)
                pcharsfrom1[i] = pchars[i - 1];
            return pcharsfrom1;
        }
        private static int[] getNextWithTest(char[] p)
        {
            int[] next = new int[p.Length];
            next[1] = 0;
            int j = 1;
            int k = 0;

            printf(p);


            while (j < p.Length - 1)
            {
                if (k != 0)
                    Console.WriteLine("p[{0}]({1}) == p[{2}]({3})??", j, p[j], k, p[k]);
                if (k == 0 || p[j] == p[k])
                {
                    if (k == 0)
                    {
                        ++j;
                        ++k;
                        next[j] = k;
                        Console.WriteLine("根据k=0得出:p[{0}]={1}", j, k);
                        Console.ForegroundColor = ConsoleColor.DarkGreen;
                        Console.WriteLine("--------------------------------");
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                    else
                    {

                        ++j;
                        ++k;
                        next[j] = k;
                        Console.WriteLine("根据p[j] == p[k]得出:p[{0}]={1}", j, k);
                        Console.ForegroundColor = ConsoleColor.DarkGreen;
                        Console.WriteLine("--------------------------------");
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                }
                else
                {
                    k = next[k];
                }
            }
            return next;
        }

        private static void printf<T>(T[] p)
        {
            int eachlineCount = 10;
            for (int line = 0; line < p.Length / eachlineCount + 1; line++)
            {
                for (int i = 0; i < eachlineCount && line * eachlineCount + i < p.Length; i++)
                {
                    Console.Write("  {0}  ", line * eachlineCount + i);
                }
                Console.Write("\n");
                for (int i = 0; i < eachlineCount && line * eachlineCount + i < p.Length; i++)
                {
                    if (line == 0)
                        Console.Write("  {0}  ", p[line * eachlineCount + i]);
                    else
                    {
                        Console.Write("   {0}  ", p[line * eachlineCount + i]);
                    }
                }

                Console.Write("\n\n");
            }
        }

模拟结果展示:


这里写图片描述

源码下载:

http://download.csdn.net/detail/daigualu/9791023

9
3
查看评论

关于KMP算法中next函数的详细解析

之前看到数据结构中字符串的模式匹配时,花了半天的时间,才把KMP算法中的next函数整明白了,结果过了几天在看到这时,只记得next[j+1]=next[j]+1,可是有时候能套公式正确算出,有时候就算不对,所以今天再重新理一遍思路,顺便记录下来,防止哪天脑子再短路了,又不知道怎么求解的了。 &#...
  • lianggangzzu
  • lianggangzzu
  • 2017-05-15 20:46
  • 645

数据结构KMP算法中next函数的求解思想及其解释

这个只是简单的next数组的求法,并没有完全实现KMP算法,有待改进
  • qq_28598203
  • qq_28598203
  • 2016-04-10 20:09
  • 730

KMP 算法之得到next的代码

最近温习了一下KMP算法。现在谈谈我对KMP算法的理解。 KMP算法目的:尽量快的解决单字符串匹配的问题。 一、问题: 主字符串: ababcababababcab 模式串: abababab 判断在主串中是否存在模式串,如果存在,在哪个位置。 二、解决 解决办法很多,单谈KMP...
  • henuyx
  • henuyx
  • 2015-03-27 09:46
  • 3964

字符串匹配的KMP算法---计算失配函数

关于理解计算失配函数的一点小心得。 首先,感谢Jake Boxer的文章给我的帮助。 在jake的文章里面,说的,前缀和后缀是理解的关键。请先阅读以下jake的文章(不然可能不好理解)。 正题:假设字符串 P 的长度是 m。我们给定 P 的失配函数为 failure[m],failure[0]...
  • lh340826
  • lh340826
  • 2014-02-25 18:48
  • 1343

KMP算法中next数组的手工计算方法

笔试题目中经常要求计算KMP算法的next数组,网上有很多讨论的文章,但是感觉都讲的不太清楚,特别是在如何手工计算这一方面,所以今天特别整理了一下放到这里,一来备忘,二来也希望给有缘人带来一些方便。 位置编号 1 2 3 4 5 6 7 8 字符串 a ...
  • wen_alan
  • wen_alan
  • 2015-08-05 22:01
  • 8491

KMP算法NEXT数组计算方法

KMP算法: 关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。 个人对于Next()函数的理解: 一:思路概括:我语文不太好可以忽略,直接先看手工实现 1,把将要进行...
  • zero9988
  • zero9988
  • 2017-03-05 23:24
  • 588

数据结构——关于KMP算法中next函数的详细解析

之前看到数据结构中字符串的模式匹配时,花了半天的时间,才把KMP算法中的next函数整明白了,结果过了几天在看到这时,只记得next[j+1]=next[j]+1,可是有时候能套公式正确算出,有时候就算不对,所以今天再重新理一遍思路,顺便记录下来,防止哪天脑子再短路了,又不知道怎么求解的了。 ...
  • u012532559
  • u012532559
  • 2015-03-19 20:49
  • 12514

KMP算法——从入门到懵逼到了解

本博文参考http://blog.csdn.net/v_july_v/article/details/7041827 关于其他字符串匹配算法见http://blog.csdn.net/WINCOL/article/details/4795369 暴力匹配算法     暴...
  • HyJoker
  • HyJoker
  • 2016-04-19 17:12
  • 10635

KMP算法next数组生成中k=next[k]解释

本文适用于读者对KMP算法有一定的了解void initNextArray(string p){ int k = -1; int j = 0; next[0] = -1; while(j < p.size()-1){ if(k == -1 || ...
  • BaiDingLT
  • BaiDingLT
  • 2017-04-09 12:14
  • 851

KMP中next和nextval算法简析

KMP中next和nextval算法简析
  • sinat_33107885
  • sinat_33107885
  • 2016-09-14 10:10
  • 4030
    算法channel

    交流思想,注重分析,实例阐述,通俗易懂,包含但不限于:经典算法,机器学习,深度学习,LeetCode 题解,Kaggle 实战。期待您的到来!

    算法与人工智能交流群:646901659

    个人资料
    • 访问:321989次
    • 积分:7614
    • 等级:
    • 排名:第3379名
    • 原创:350篇
    • 转载:1篇
    • 译文:0篇
    • 评论:58条
    博客专栏