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数组解释的 第二点

  • 相关阅读:
    out/host/linuxx86/obj/EXECUTABLES/aapt_intermediates/aapt 64 32 操作系统
    linux 查看路由器 电脑主机 端口号 占用
    linux proc进程 pid stat statm status id 目录 解析 内存使用
    linux vim 设置大全详解
    ubuntu subclipse svn no libsvnjavahl1 in java.library.path no svnjavahl1 in java.library.path no s
    win7 安装 ubuntu 双系统 详解 easybcd 工具 不能进入 ubuntu 界面
    Atitit.json xml 序列化循环引用解决方案json
    Atitit.编程语言and 自然语言的比较and 编程语言未来的发展
    Atitit.跨语言  文件夹与文件的io操作集合  草案
    Atitit.atijson 类库的新特性设计与实现 v3 q31
  • 原文地址:https://www.cnblogs.com/unclejelly/p/4082076.html
Copyright © 2011-2022 走看看