KMP算法next数组求解实现

首先我们通过应用场景将KMP算法中用到的名词做一个说明:

在一个字符串(string1)中查询是否存在另一个字符串(string2)。

在字符串匹配算法中,我们通常将字符串string1成为主串;字符串string2成为子串。

下面我们将分别说明朴素模式匹配算法和KMP算法,并重点说明KMP中next的求解方式。

1. 朴素模式匹配算法

朴素模式匹配算法步骤:

  • 从主串第一个元素和子串第一个元素开始匹配,
  • 如果相等,同时后移匹配后续字符;
  • 如果不相等,主串指针和子串指针同时回溯(子串回溯到第一个元素,主串回溯到开始匹配元素的下一个元素),之后重复这三个步骤。

效果图:




朴素匹配算法的C代码实现如下:

int findMatch(char *Str, char *match)
{
int i=0, j=0;
/*注意:这里下标从0开始计算*/
while(i < strlen(Str) && j < strlen(match)){
if(Str[i] == match[j]){
i++;
j++;
}else{
i = i - j + 1;
j = 0;
}
}
if( j == strlen(match)){
return i - j;/*指出匹配成功的主串上的位置*/
}else{
return -1;/*匹配失败*/
}
}

2. KMP算法

计算机界的先人们认为上述的匹配算法由于主串和子串指针在元素不相等时需要同时回溯,导致匹配效率低下。经过他们的一系列研究,提出了KMP匹配算法。该算法在匹配时无需主串指针进行回溯,执行回溯子串指针即可。

KMP是通过提前求取子串的特征来优化匹配流程的,该特征我们称之为next数组。它的作用是:当某一位置元素不匹配时,通过next数组来确定子串指针回溯的位置,从而避免每次都从子串的第一个元素开始。

KMP算法步骤:

  • 获取子串的next数字信息
  • 字符串匹配
    • 如果两个元素相等,同时向后移动指针,匹配后续字符
    • (如果主串元素与子串第一个元素都不等,则向后移动主串指针)
    • 如果两个元素不相等,则回溯子串指针,回溯到的位置为next中当前位置对应的值
    • 重复上述三个步骤

同样以上述例子为例进行说明:




KMP算法C代码实现如下:(代码实现上从0开始)

int getNext(char *str, int next[])
{
int i = 0;
int j = -1; if(!str || !next){
printf("Parameters can't be NULL or can't be zero\n");
return -1;
}
/* 下标从1开始
** index : 1 2 3 4 5 6 7 8 9 --i
** value : a b a b a a a b a
** next : 0 1 1 2 3 4 2 2 3 --j
**/
/* 下标从0开始
** index : 0 1 2 3 4 5 6 7 8 --i
** value : a b a b a a a b a
** next :-1 0 0 1 2 3 1 1 2 --j
**/
/* next回溯
** index : 0 1 2 3 4 5 6 7 8
** value : a b a b a a a b a
** a b a b a a a b a
**/
next[0] = -1;
printf("%2.2d ", next[0]);
while(i < strlen(str)-1){
if(j == -1 || str[i] == str[j]){
i++;
j++;
next[i]=j;
printf("%2.2d ", next[i]);
}else{
j = next[j];
}
}
printf("\n");
return 0;
}
// char *str="ababaaaaba";
// char *match="aaa";
int kmp(char *Str, char *match)
{
int i=0,j=0; int next[100] = {0}; int ret =getNext(match, next);
if(ret != 0){
printf("Get next error\n");
return -1;
}
while(i<(int)strlen(Str) && j<(int)strlen(match)){
if(j == -1 || Str[i] == match[j]){
i++;
j++;
}else{
j = next[j];
}
}
if(j == strlen(match)){
return i - j;
}else{
return -1;
}
}

3. next数组的求解

很多书上在讲解KMP算法时,元素都是从1开始的,而实际使用过程中都是从0开始的。这个并不是什么问题,除此之外,对于代码的实现也遇到了疑问,下面我将个人在学习KMP的疑问记录下来:

  • i,j下标的初值
  • 从0开始与从1开始的区别
  • next数组第一个元素的值设置依据
  • 两个元素不等时,j为什么要如此回溯?

下面对这几个疑问进行说明:

int getNext(char *str, int next[])
{
int i = 0;
int j = -1; if(!str || !next){
printf("Parameters can't be NULL or can't be zero\n");
return -1;
}
next[0] = -1;
while(i < strlen(str)-1){
if(j == -1 || str[i] == str[j]){
i++;
j++;
next[i]=j;
}else{
j = next[j];
}
}
return 0;
}

3.1 j=next[j]的理解

next数组用来表示当前字符之前的串相似程度。那个如果对相似度进行量化呢?这里我们使用下标来量化相似度。例如:

下标 1 2 3 4 5 6 7 8 9
a b a b a a a b a
next数组 0 1 1 2 3 4 2 2 3

上表默认下标从1开始,这也是很多讲解KMP算法时的经常的方式。

什么叫使用下标来量化相似度呢?

比如第5个元素之前的字符串为"abab",它的前后相似的串为"ab", 因此当遇到以下情况时,

主串 a b a b c d e f g i j
子串 a b a b b
匹配情况 ×
next 0 1 1 2 3

直接从子串的第三个元素开始比较即可(S[5] == T[3] ?),而无需从第一个元素进行(前面元素通过next数组能保证一定相等):

主串S a b a b c d e f g i j
子串T a b a b b
匹配情况
0 1 1 2 3

next的值代表如果主串和子串元素不等时,主串位置i无需回溯,只需要将子串位置回溯,回溯的位置下标就是对应next数据的值。也就是代码中的j=next[j]。

那么j=next[j]怎么理解呢?

这里有个默认前提:对子串递归使用KMP。这样就比较容易理解j=next[j]

比如在求取第5个位置的next的值时:

已知第五个元素时,需要知道前四个元素的相似度,在第四个元素时我们已经知道T[1]=T[3], 现在只需要比较T[2] == T[4] ?即可,如果相等,那么next[5]=next[4]+1; 如果不相等,比如"ab"!=“ac”,那么就应该回溯到第一个位置的值,即j=next[j]

下标 1 2 3 4 5 6 7 8 9
子串T a b a c c a a b a
子串T a b a b c a a
next 0 1 1 2 ?

3.2 next数组第一个位置的值

next的第一个值无法进行计算,因为它之前没有元素,就没有办法计算相似度。这里是设置默认值的。

那么默认值的要求是什么呢?

我们已经知道next的实际上就是下标,而next的第一个元素的值不得与现有的下标冲突:

  • 如果下标从1开始,则next[1]可以是小于1的任意整数,因此默认使用0。此时ij的初始值分别为i=1;j=0;
  • 如果下标从0开始,则next[0]可以是小于0的任意整数,因此默认使用-1。此时ij的初始值分别为i=0;j=-1;

4. 代码

下面列出完整的实现(包括KMP和朴素匹配算法,下标从0开始)

/*************************************************************************
> File Name: kmp.c
> Author: Toney Sun
> Mail: vip_13031075266@163.com
> Created Time: 2020年06月27日 星期六 21时07分12秒
************************************************************************/ #include<stdio.h>
#include<string.h>
#include<stdlib.h>
int getNext(char *str, int next[])
{
int i = 0;
int j = -1; if(!str || !next){
printf("Parameters can't be NULL or can't be zero\n");
return -1;
}
/*
** index : 1 2 3 4 5 6 7 8 9 --i
** value : a b a b a a a b a
** next : 0 1 1 2 3 4 2 2 3 --j
**/
/*
** index : 0 1 2 3 4 5 6 7 8 9 --i
** value : a b a b a a a b a
** next :-1 0 0 1 2 3 1 1 2 --j
**/
/*
** index : 0 1 2 3 4 5 6 7 8
** value : a b a b a a a b a
** a b a b a a a b a
**/
next[0] = -1;
printf("%2.2d ", next[0]);
while(i < strlen(str)-1){
if(j == -1 || str[i] == str[j]){
i++;
j++;
next[i]=j;
printf("%2.2d ", next[i]);
}else{
j = next[j];
}
}
printf("\n");
return 0;
}
// char *str="ababaaaaba";
// char *match="aaa";
int kmp(char *Str, char *match)
{
int i=0,j=0; int next[100] = {0}; int ret =getNext(match, next);
if(ret != 0){
printf("Get next error\n");
return -1;
}
while(i<(int)strlen(Str) && j<(int)strlen(match)){
if(j == -1 || Str[i] == match[j]){
i++;
j++;
}else{
j = next[j];
}
}
if(j == strlen(match)){
return i - j;
}else{
return -1;
}
} int findMatch(char *Str, char *match)
{
int i=0, j=0; while(i < strlen(Str) && j < strlen(match)){
if(Str[i] == match[j]){
i++;
j++;
}else{
i = i - j + 1;
j = 0;
}
}
if( j == strlen(match)){
return i - j;
}else{
return -1;
}
} void main(int argc, char *argv[])
{
char *str="ababaaaaba";
char *match="c"; int index = kmp(str, match);
printf("-------index=%d------\n",index); index = findMatch(str, match);
printf("-------index=%d------\n",index); }

KMP算法中的几个疑问的更多相关文章

  1. 问题 1690: 算法4-7:KMP算法中的模式串移动数组

    题目链接:https://www.dotcpp.com/oj/problem1690.html 题目描述 字符串的子串定位称为模式匹配,模式匹配可以有多种方法.简单的算法可以使用两重嵌套循环,时间复杂 ...

  2. KMP算法中我对获取next数组的理解

    之前在学KMP算法时一直理解不了获取next数组的函数是如何实现的,现在大概知道怎么一回事了,记录一下我对获取next数组的理解. KMP算法实现的原理就不再赘述了,先上KMP代码: 1 void g ...

  3. KMP 算法中的 next 数组

    KMP 算法中对 next 数组的理解 next 数组的意义 此处 next[j] = k:则有 k 前面的浅蓝色区域和 j 前面的浅蓝色区域相同: next[j] 表示当位置 j 的字符串与主串不匹 ...

  4. KMP算法中next函数的理解

    首先要感谢http://blog.csdn.net/v_july_v/article/details/7041827以及http://blog.chinaunix.net/uid-27164517-i ...

  5. KMP算法中next数组的理解与算法的实现(java语言)

    KMP 算法我们有写好的函数帮我们计算 Next 数组的值和 Nextval 数组的值,但是如果是考试,那就只能自己来手算这两个数组了,这里分享一下我的计算方法吧. 计算前缀 Next[i] 的值: ...

  6. KMP算法中求next数组的实质

    在串匹配模式中,KMP算法较蛮力法是高效的算法,我觉得其中最重要的一点就是求next数组: 看了很多资料才弄明白求next数组是怎么求的,我发现我的忘性真的比记性大很多,每次看到KMP算法求next数 ...

  7. 关于KMP算法中,获取next数组算法的理解

    参考:KMP入门级别算法详解--终于解决了(next数组详解) https://blog.csdn.net/lee18254290736/article/details/77278769 在这里讨论的 ...

  8. kmp算法中的nextval实例解释

    求nextval数组值有两种方法,一种是不依赖next数组值直接用观察法求得,一种方法是根据next数组值进行推理,两种方法均可使用,视更喜欢哪种方法而定. 本文主要分析nextval数组值的第二种方 ...

  9. KMP算法中next数组的构建

    记得初学$kmp$的时候 老师让大家把它直接背下来 然而不理解的话 不仅调试起来比较慢 很多题目也难往$kmp$上想 ----------------------------------------- ...

随机推荐

  1. Nacos 笔记

    Nacos 笔记 目录 Nacos 笔记 1. Nacos简介 1.1 主流配置中心对比 1.2 主流注册中心对比 1.3 Nacos特性 2. 安装启动 支持外部 MySQL 3. 配置管理 3.1 ...

  2. Linux 文件、目录与磁盘格式

    文件属性      连接数  文件持有者 文件所属群组 文件容量 文件最后修改时间 文件名(就那个..) 第一栏其中文件属性有10,第一个属性代表这个文件是目录.文件或链接文件: [d]目录 [-]文 ...

  3. 北航面向对象OO第三单元——JML

    简介 本单元借助JML(Java Modeling Language),训练了我们关于的"规格(specification)"的意识和思想 本单元代码难度较低,简单来讲就是给你规定 ...

  4. 基于kail的docker下安装sqli-labs

    后面的关卡涉及到转码问题,比如空格,在Windows中会受到限制,比如24关的文件重命名问题,所以在这记录下在docker下安装sqli-labs,在linux下运行就不会受到限制. 参考链接:htt ...

  5. (四)Linux之用户管理(用户和用户组)

    Linux之用户管理(用户和用户组) 目录 Linux之用户管理(用户和用户组) 一.概述 二.用户和组的关系 三.关于UID和GID(用户ID和组ID) 四.用户和组的数据 /etc/passwd内 ...

  6. jenkins+docker部署java项目

    jenkins + maven + jdk + docker + docker register + dockerfile jenkins插件 # 安装插件 SSH # 配置 系统设置-> SS ...

  7. Ratel:一直站在Android逆向巅峰的平头哥

    本文来源:带动行业内卷,渣总义不容辞 字越少事儿越大,请关注github(可以点击阅读原文): https://github.com/virjarRatel 平头哥(ratel)是一个Android逆 ...

  8. C#比较两个对象是否为同一个对象。 Visual Studio调试器指南---多线程应用程序调试(一)

    两个对象是否为同一个对象:是看两个对象是否指向堆中的同一块内存. 1.使用object.ReferenceEquals() class Program { static void Main(strin ...

  9. 【C/C++】C/C++中的内存四区

    1 代码区 存放 CPU 执行的机器指令.通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可.代码区通常是只读的,使其只读的原因是 ...

  10. Quartz任务调度(3)存储与持久化操作配置详细解

    内存存储RAMJobStore Quartz默认使用RAMJobStore,它的优点是速度.因为所有的 Scheduler 信息都保存在计算机内存中,访问这些数据随着电脑而变快.而无须访问数据库或IO ...