字符串匹配的KMP算法(后期不断学习后更新)【学习日记】

(网上的说法)

10月24日·第一天的学习

学习任务:这天我们先来了解一下什么是KMP,并且先不看代码自己揣测下应该如何去写这样的代码。

举例来说,有一个字符串“BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串“ABCDABD”?

1。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

首先,字符串“BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索词“ABCDABD”的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

2。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

因为乙与甲不匹配,搜索词再往后移。

3。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

4。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

接着比较字符串和搜索词的下一个字符,还是相同。

5。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

直到字符串有一个字符,与搜索词对应的字符不相同为止。

6。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把“搜索位置”移到已经比较过的位置,重比一遍。

7。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

一个基本事实是,当空格与d不匹配时,你其实知道前面六个字符是“ABCDAB” .KMP算法的想法是,设法利用这个已知信息,不要把“搜索位置”移回已经比较过的位置,继续把它向后移,这样就提高了效率。

8。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

怎么做到这一点呢?可以针对搜索词,算出一张“部分匹配表”(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

9。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

已知空格与d不匹配时,前面六个字符“ABCDAB”是匹配的。查表可知,最后一个匹配字符乙对应的“部分匹配值”为2,因此按照下面的公式算出向后移动的位数:

  移动位数=已匹配的字符数 – 对应的部分匹配值

因为6 – 2等于4,所以将搜索词向后移动4位。

10。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2(“AB”),对应的“部分匹配值”为0.所以,移动位数= 2 – 0,结果为2,于是将搜索词向后移2位。

11。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

因为空格与一个不匹配,继续后移一位。

12。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

逐位比较,直到发现C与D不匹配。于是,移动位数= 6 – 2,继续将搜索词向后移动4位。

13。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数= 7 – 0,再将搜索词向后移动7位,这里就不再重复了。

14。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

下面介绍“部分匹配表”是如何产生的。

首先,要了解两个概念:“前缀”和“后缀”“前缀”指除了最后一个字符以外,一个字符串的全部头部组合;“后缀”指除了第一个字符以外,一个字符串的全部尾部组合。

15。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

“部分匹配值”就是“前缀”和“后缀”的最长的共有元素的长度。以“ABCDABD”为例,

  - “A”的前缀和后缀都为空集,共有元素的长度为0;

  - “AB”的前缀为[A],后缀为[B],共有元素的长度为0;

  - “ABC”的前缀为[A,AB],后缀为[BC,C],共有元素的长度0;

  - “ABCD”的前缀为[A,AB,ABC],后缀为[BCD,CD,D],共有元素的长度为0;

  - “ABCDA”的前缀为[A,AB,ABC,ABCD],后缀为[BCDA,CDA,DA,A],共有元素为“A”,长度为1;

  - “ABCDAB”的前缀为[A,AB,ABC,ABCD,ABCDA],后缀为[BCDAB,CDAB,DAB,AB,B],共有元素为“AB”,长度为2;

  - “ABCDABD”的前缀为[A,AB,ABC,ABCD,ABCDA,ABCDAB],后缀为[BCDABD,CDABD,DABD,ABD,BD,D],共有元素的长度为0。

16。

《字符串匹配的KMP算法(后期不断学习后更新)【学习日记】》

“部分匹配”的实质是,有时候,字符串头部和尾部会有重复。比如,“ABCDAB”之中有两个“AB”,那么它的“部分匹配值”就是2(“AB”的长度)搜索词移动的时候,第一个“AB”向后移动4位(字符串长度 – 部分匹配值),就可以来到第二个“AB”的位置。

 

下一个数组在干嘛? 
上面说的做法,其实就是把模式串分割成前缀后缀来考虑,比如模式串为:ABCDABDABCDABD,我们可以分成以下几种情况

前缀后缀前缀后缀最长匹配
A BCDABD 0
AB CDABD 0
ABC DABD 0
ABCD ABD 2
ABCDA BD 0
ABCDAB D 0
之前提到过一个next 数组,这个数组是和模式串相关的,我们利用它存储到当前字符位置时,前缀后缀的最长匹配字符数,比如以上模式串的下一个数组表示为:

模式ABCDABD
下一个0 0 0 0 1 2 0
那么上面提到的位移公式就可以借助下一个数组来实现了。

KMP借助如何下移位数组 
道理其实很简单,如果目标串和模式串的字符匹配,那么就同时移动两者的下标;如果不能匹配,就使用下一个数组来获得移动的数目但编程方法实现的话,下一个数组我们需要再修改一下,这样就能够直接获得当前失配位置应当对应的新的模式串字符下标(因为我们关注的是在失配字符之前有几个匹配的字符)。

模式ABCDABD
next -1 0 0 0 0 1 2

(以下为自己对于KMP的理解)

10月25日·第二天的学习

  这次就要上代码了,然后自己通过调试的方式加上自己手推的来弄明白KMP的运行方式,并且给予相应注释。

  我们之前聊到KMP的待查基础的字符串的下一个[]数组,前面没有与自己相同的就赋值-1,第一个与前面头相等的就赋予0,且在此情况下,下一个还与第二个相等的就给next []对应的位置赋予1,以此类推;但要是遇到不等呢?我们返回不等的前一个状态,即向前回溯,怎么回溯“k = next [K]”就可以完成回溯的部分,因为之前存放的下一个[K]的刚好也是前一刻符合条件的状态,我们只要判断下一个字符是否与目前查询到的这一个字符相等,不等就重复上面步骤,直到“K == – 1”。

这一步称之为cal_next()附上对应代码及注释:

void cal_next(char *str, int *next, int len)        //我们用以处理基础待查的字符串,推导出其的next[]数组
{
    next[0]=-1;     //第0个数是一定没有前后相同的数的
    int k = -1;     //初始化,原因是我们每次查询的是K+1,所以我们让它从“-1”开始
    for(int q=1; q<=len-1; q++)     //基础待查字符串的长度
    {                                       //K+1从0开始,Ks初始化为-1的原因
        while(k>-1 && str[k+1]!=str[q])     //如果第K+1位与目前遍历这位相等,我们就继续往下走,不然就要向前回溯了
        {
            k = next[k];        //向前回溯
        }
        if(str[k+1] == str[q])      //如果经过前面判断后的下一位与目前的这位相等,那么就将K推到K+1
        {
            k++;
        }
        next[q]=k;      //给予next[]数组赋予对应的值
    }
}

  之后,我们需要将被查字符串与基础查询字符串联系起来,我们依旧要回去处理被查字符串:

  这次,我们查询的长度是被查字符串的长度,我们可以类比上面的查询并建立下一个[]数组的做法,进行遍历,这次的条件变成了如果基础查询字符串对应位与ķ + 1位相等,则ķ推到这一位,不然就是回溯到下一个[K]在进行判断是否满足,然后就是什么时候得到相等的第一位的问题了,如果ķ的大小恰在这一位置算完后与基础查询字符串的值相等,那么,就可以得到对应位置剪去相应长度+1的位置了。

int KMP(char *str, int slen, char *ptr, int plen)
{
    int *next = new int[plen];
    cal_next(ptr, next, plen);
    int k = -1;
    for(int i=0; i<slen; i++)       //我们往后推的是str的下标,然后判断其与现在的K+1是否相等
    {
        while(k>-1 && ptr[k+1]!=str[i])     //比较的是K的下一位,而最后转化K得到的也是K+1,此处判断K+1位置是否能继续推下去
        {
            k = next[k];
        }
        if(ptr[k+1] == str[i]) k++;     //ptr上的这一个(K+1)节点若是与str上的相等,那么就把K推到这一位
        if(k == plen-1)     //此时走到了基础查询字符的最后一位,说明可以走到,返回此时查询的被查字符的对应位置剪去相应的长度的位子
        {
            return i-plen+1;        //因为基础查询字符的长度是plen,所以我们返回的位子就是现在遍历到的i剪去长度+1
        }
    }
    return -1;
}

(难度由简入繁的一些题目)

10月26日·第三天学习

这天刷了些KMP的题目,来做些笔记:

Number Sequence 【HDU – 1711】【KMP模板】

我按照难度顺序给出一些题目,这一到就是KMP的模板,就是理解一下什么是KMP,next[]数组怎么推的样子。

剪花布条 【HDU – 2087】【KMP模板题】

往后递推就是了,尽管这道题其实还可以用哈希去做,但是作为理解KMP,我依然用了KMP去做,列写每次遍历到的点,然后以它之后的下一个点为下一个起点再次做KMP就是了。

Period 【HDU – 1358】【KMP求周期】

我们想知道字符串的前缀字符串有多少周期,可以用KMP来做,这时候刚好把KMP与周期联系了起来,子字符串的长度为len,next[len]的值可以知道,那么到此时的前面的最小周期长度就是len-next[len]与len比较能否可以被整除,就知道是否为完整的周期了。

Seek the Name, Seek the Fame 【POJ – 2752】【KMP算法next[]的用法拓展】

这道题,我么要求的是该字符串的前缀字符串,那么我们可以用知道总长然后利用KMP中的next[]去向前递推出长度来,毕竟符合前缀-后缀的函数就是next[]了,通过向前回溯的方法即可。

Cyclic Nacklace 【HDU – 3746】【KMP补周期】

  一道算得上良心的题目,只要我们在字符串的后面加最少的字符构成周期字符串即可,那么就知道是利用KMP中的next[]来处理的,对于最小周期就是len-next[len],所以我们先看是是否能被原长整除,能整除就不用删改,当然如果next[len]==0就要直接加上原长了,之后,我们需要将最小周期扩大到恰好大于原长,然后剪去原长即我们所求答案。

Common Divisors 【CodeForces – 182D】【KMP~有多少共有的子公共串数】

  名字还挺长的,其实就是让你求共有子字符串的种类数,我们先找出他们的最小周期数,看看是否相等,之后在看最小周期的整数倍是否能被两边的字符长度均整除即可。

Password CodeForces – 126B【KMP深度理解题】

  这道题到真是要理解半天,但其实想到还挺好懂的,就是我们要找一个这样的最长子串,使得它既是前缀又是后缀,并且还在原字符串中间出现过,我们了解下一个[ ]的构造后再利用STL的设置就很好做了:这里还是要讲下思路,我们知道下一个[]的前缀是0的,所以我们只用判断后缀即可,那我我们不妨就以最后一个字符为基础,因为最后一个字符是一定要利用到的,并且还要以它作为结尾,所以我们把1〜LEN-1的所有字符对应的下一个[]存进设置中,然后遍历下一个[长度]的所有向前回溯的值即可,假如出现了设置中的那么输出就是,它一定是最大值,但若是找到底都有可能没有答案的,输出题目给的条件就是。

 

今天补了好多题,还是要加深些印象才是,很多都还不是真正的了解,只是凭借刚学的余热去推测出来的,这还是不够。

(随机补题开始:)

10月30日

统计单词数【KMP做法~可以用作KMP的理解题】

  很多人都用了模拟来做,但我看到之后觉得这可以是道对于KMP的理解题,做完这道题之后,可以明白关于处理独立单词与独立单词的方式,我们用KMP查询的时候得到的是一段字符串(意味着不能确保是完整的单词),所以我们要进行判断,判断过后能确定它是不是完整的单词,是的话就得返回k=-1,但若不是的话,也得对k进行处理,我们想一下next的运行方式,就可以用k=next[k]来向前回溯,不然下一个k+1就会超范围的。

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/qq_41730082/article/details/83350315
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞