热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

KMP算法形象化说明

voidPrefixMatch(conststringDstStr,vector<int>&NextRes){NextRes[0]0;intk0;for

void PrefixMatch(const string DstStr, vector & NextRes){
NextRes[0] = 0;
int k = 0;
for (int i = 1; i {
while (k > 0 && DstStr[k] != DstStr[i])
k = NextRes[k - 1];
if (DstStr[k] == DstStr[i])
k++;
NextRes[i] = k;
}
}

KMP算法用于在长字符串中找目标字符串。

最简单的匹配算法当然是拿目标字符串从长字符串开头开始一个一个匹配,如果不能全部匹配,就把目标字符串向后移动一位,继续匹配,如下图。


KMP的思想就是我不想每次匹配不成功了,一个位置一个位置挪了,我想每次能不能多挪几个,那么,如果大胆一点,我可以直接挪上一步已经比较过的字符个数个位置,在这里就是6个位置,如下图


但是我们也都知道,这样子肯定有时候会漏掉一些情况。

那么我们假设存在这样被漏掉的情况,在图中,其实我们可以发现,其实在第5位也可以发现与目标字符串匹配的,如下图。


那么,问题就变成了,怎么判断在已经比较过的字符串中,是否有可以从头开始比较的位置呢?假设已经比较了k个字符,这里有几点已知条件:

已经比较过的字符串是和目标字符串前几位完全相同的一段,即目标字符串前k位;

如果在已经比较过的字符串即目标字符串前k位字符中存在可以继续从此处开始的位置,那么从这个位置开始到这k个字符串最后一个这段字符串都应该与目标字符串前n个匹配;

这样的话,那我们如果事先知道:

设目标字符串str_obj,长度为m, 给出任意值k<=m,求出值最小的值i,使得str_obj(i), str_obj(i+1)……str_obj(k)=str_obj(1),str_obj(2)……str_obj(k-i+1)。这样的话,我就知道每次匹配不成功,我只需要向前移动k-i位。

在这个例子里,第一次匹配了6位,第七位不同,那么k=6,发现1-6的字符中发现最后两位与目标字符串前两位相同,这说明i=2,那么我们本次可以移动的步数应该是4,也就是如图:


所以只要我们在开始匹配前,把目标字符串每一位开始向前的连续字符串可以和目标字符串前几位完全匹配的最大数得到,那么在匹配的过程中就可以直接查询得到本次可以前移的步数了,这里有一点需要注意,这句话中的前者,向前的连续字符串,不能包括第一个,如果包括第一个,那永远都会有从第一个到这一位和目标字符串前k位完全相同,也就是说,有字符串1-m,对于 k=1,2,3…m,在字符2-k中找最小的i使得字符i- -k=字符1- -(k-i+1)。而这个过程就是被称为前缀函数的过程。这个例子里,各个位置的重复度或匹配值如下,可以自己揣摩一下。


而计算这个数值的过程也很费解,先看程序

代码如下:

void PrefixMatch(const string DstStr, vector & NextRes){
NextRes[0] = 0;
int k = 0;
for (int i = 1; i {
while (k > 0 && DstStr[k] != DstStr[i])
k = NextRes[k - 1];
if (DstStr[k] == DstStr[i])
k++;
NextRes[i] = k;
}
}

比较容易理解的是for循环里的if语句:有了上一步的匹配结果,下一步的话只要新增的字符还能匹配上那么这一步能匹配上的就是上一步的结果加1;

比较难理解的是while语句:先不管k>0,当新增字符不匹配的时候,那么把k置为上一步匹配到的字符串的长度的位置,这个的位置很关键。如果新增字符无法匹配,我们不把这一步的结果置为0的原因是,再之前匹配到的字符串中可能还存在能匹配到的子字符串,这样的话,我们就需要

(1)看已经匹配到的字符串中最长的匹配长度;

(2)然后再比较之后一个字符和新增字符是否相等。

这样的话(1)其实是我们之前的结果,就是语句

k = NextRes[k - 1];
(2)就是判断条件

DstStr[k] != DstStr[i]
如果不满足就迭代上述过程,有点递归的意思,那么退出条件就是,什么时候已经没有子字符串了,就可以退出了,这就是k>0.这就是全部。


具体的KMP从原理上和上边的求next数组相当相似,仔细想一下其实,(1)求next数组是求自身和自身的匹配问题,而(2)CMP是求目标字符串和长字符串的匹配问题。 问题(1)要求出目标字符串所有从头开始的子字符串的最大匹配长度,问题(2)只要目标字符串和长字符串匹配的最大长度等于目标字符串的部分。这样想就很简单了,直接上代码吧

int main()
{
string LongStr, DstStr;
cin >> LongStr >> DstStr;
int* NextRes = new int[DstStr.size()];
PrefixMatch(DstStr, NextRes);

int q = 0;
for (int i = 0; i {
while (q > 0 && DstStr[q] != LongStr[i])
q = NextRes[q - 1];
if (LongStr[i] == DstStr[q])
q++;
if (q == DstStr.size()){
cout <<"match from " <q = NextRes[q - 1];
}
}
return 0;
}




推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
author-avatar
549696530_c1f5e8
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有