zoukankan      html  css  js  c++  java
  • hdu3336解读KMP算法的next数组

    查看原题

    题意大致是:给你一个字符串算这里面所有前缀出现的次数和。比如字符串abab,a出现2次,ab出现2次,aba出现1次,abab出现1次。总计6次。

    并且结果太大,要求对1007进行模运算。

    AC代码

    #include <iostream>
    using namespace std;
    #include <string>
    string s;
    int n,Next[200005];
    void getNext()
    {
        int len = n;    
        Next[0]=-1;
        int i=0,j=-1;
        while (i<len)
        {
            if(j==-1||s[i]==s[j])
            {
                ++i;
                ++j;
                Next[i]=j;
            }
            else
                j = Next[j];
        }
    }
    
    void main()
    {
        int t;
        cin>>t;
        while (t--)
        {
            cin>>n;
            cin>>s;
            getNext();
            int sum=0;
            for(int i=1;i<=n;i++)
            {
                int j=i;
                while(j)
                {
                sum = (sum+1)%10007;
                j = Next[j];
                }
            }
            cout<<sum<<endl;
        }
    }

    KMP的next数组

    概述

    贴代码不是目的,讲解算法才是关键!!。解题的思路是使用了 KMP 算法,然而把并不是完整的KMP算法。只用到了它的next数组的求法。然而这正是KMP算法本身的关键所在。关于上面代码中getNext函数中进行的求next数组的实现部分,属于经典实现,模板代码。很容易找到,这里关键在于讲解next数组的思想。

    在漫天飞的网络资料中,next数组的表示方法大致有两种:

    • next数组第一位为-1
    • next数组第一位为0

    基本上是异曲同工。这里我用的是首元素为-1的解决方案,要注意的是若是首元素为-1的方案,那么next数组的大小是模式串长度+1!举个例子:

    下标 0 1 2 3 4
    模式串 a b a b  
    next数组 -1 0 0 1 2

            当然了,这里我表示的是c++的string字符串。不是C风格字符串,所以没写''.如果是C风格字符串(字符数组)那么红色部分就是''了。不过这不是重点,不是么?

    在KMP算法中,关于next数组一般也作两种理解(以next数组首元素为-1为例,为0时表述略有不同):

    1. 在模式串在某处与主串失配时,模式串应该回溯的位置。
    2. 以当前位置的前一位为结尾,其之前字符串与该串前缀相配的最大长度。

    下面,略为解释一下这两点:

    第一点

    比如有一主串abacabab,有一模式串abab,要从主串之中查找是否包含模式串。那么我们依次遍历两个串,假设遍历两串有两个指针(逻辑意义上的指针),或者称为光标。

    开始时,前三位都能匹配。

    下标 0 1 2 3 4 5 6 7
    主串 a b a c a b a b
    模式串 a b a b        

    然后在下标为3处,也就是红色部分失配了。那么朴素的字符串匹配算法就是要让主串的指针移动到下标为1.模式串指针归零,即移到首位,然而这很明显是低效的操作。KMP算法则是在这种情况下,不修改主串的指针,只修改模式串指针,故KMP算法又称无回溯KMP算法。那么模式串指针修改为什么呢,那就要看next数组了。

    在上例中在下标为3处失配,则去看next[3],没错是 1 。于是

    下标 0 1 2 3 4 5 6 7
    主串 a b a c a b a b
    模式串     a b a b    
    直接把模式串的指针移动到 下标为 1 处。再次失配,则观察 next[1] =0.继续重复上一过程。直至遍历完成。KMP算法效率为O(m+n),其中m和n分别为主串和模式串的长度。

    第二点

    我们再次观察next数组的表格,

    下标 0 1 2 3 4 5 6 7
    主串 a b a c a b a b
    模式串 a b a b        

    • 当下标为1时,要看它前一位的字符串,也就是看a,自身匹配不算。next数组为0。
    • 当下标为2时,要观察ab,a与b不匹配。next数组为0。
    • 当下标为3时,要观察aba,此时末尾的a与前缀a匹配,因为匹配长度为1所以next数组为1.
    • 当下标为4时,要观察abab,此时末尾的ab与前缀ab匹配,因为匹配长度为2所以next数组为2.

    回到本题

    代码中:

            int sum=0;
            for(int i=1;i<=n;i++)
            {
                int j=i;
                while(j)
                {
                sum = (sum+1)%10007;
                j = Next[j];
                }
            }
    用于求解所有前缀出现次数和,那么为什么这样呢?

    首先看for循环,从1遍历到n,大家应该很明白了。我们的next数组的长度比串长度多1个。

    while(j)造成的情况就是for循环中i = 1,2,3……n都会使sum+1.

    这是很好理解的因为,比如abab,那么 a,ab,aba,abab。这4个前缀肯定会算1个的对不?那么长度为n的字符串也会至少使sum+n对不。

    然后接下来是 j = next[j].接下来我们用逆向思维来讲解,另举一例。另有以字符串ababa,求它的sum(前缀出现次数和)。我们可以得到它的next数组:

    下标 0 1 2 3 4 5
    模式串 a b a b a  
    next数组 -1 0 0 1 2 3

    代入到上述代码中,和abab相比,只多了一位。所以直接看 i 等于n(n为5)的时候,在sum=6(abab的sum值为6)的基础上来看。

    • j=i=5                //表示的是ababa这个长度为5的最长前缀
    • while(j)成立,sum=6+1=7
    • j=next[5]=3      //表示的是aba这个长度为3的前缀
    • while(j)成立,sum=7+1=8
    • j=next[3]=1      //表示的是a这个长度为1的最短前缀
    • while(j)成立,sum=8+1=9
    • j=next[1]=0.
    • while(j)不成立,结束。
    • 最终sum=9

    要理解上面的注释部分,需要再回到前面去看关于next数组解释的 第二点

  • 相关阅读:
    一行代码搞定图片缩放、旋转、加水印
    如何学习 Webpack
    Webpack 概念
    Webpack 入门
    asp.net core教程 (一)
    asp.net core教程 (二)
    ap.net core 教程(三)
    Grafana 安装配置
    zabbix-3.0.x LTS源码安装配置
    MariaDB Security
  • 原文地址:https://www.cnblogs.com/unclejelly/p/4082076.html
Copyright © 2011-2022 走看看