KMP算法中NEXT数组的作用以及代码实现

在http://blog.csdn.net/u012613903/article/details/79004094中写到了如何手工去求一个NEXT数组,这个在很多考试中可以用来解题。但是在实际的使用中,NEXT数组究竟发挥着什么样的作用,如何用代码实现KMP算法呢?

KMP算法是用来确定一个串是否能在另一个串中找到与之完全匹配的子串,那么首先来看一个字符串匹配的实际例子;

被匹配的字符串:

abcdabef

要匹配的字符串:

abcdabw


下面开始进行匹配:(i代表被匹配的字符串的当前字符下标;j代表要匹配的字符串的当前字符下标。)

012345i 
abcdabef
abcdabw 
012345j 

可以看到,在{e,w}的位置匹配失败了,如果使用暴力的方法做,那么下一次的匹配就变成了这个样子:

0i234567
abcdabef
 abcdabw
 j123456

这时候 i = i-j+1;j=0;这就是暴力算法的思想:一旦匹配失败,就让要匹配的字符串从头开始与被匹配的字符串进行匹配。


我们来观察第一次匹配失败后的情况:

012345i 
abcdabef
abcdabw 
012345j 

在要匹配的字符串的{w}位置,往前看,看到♦的a,b与实心♦的a,b就是我们要求的最长的相同前缀与后缀字符串

其实它们代表的实际意义是这样的:

在{e,w}位置无法进行匹配了,但是在w之前的要匹配的字符串的所有字符都与在e之前的被匹配的字符串的所有字符完全匹配了;(这是个前提标记为@1)

下面具体分析实际情况:

abcdabef
abcdabw 

在{e,w}位置匹配失败了,我们不想使用暴力方法;想省力,那该怎么办呢?

要匹配的串中的w和被匹配串的e不匹配,进行下次匹配的时候,必然会有要匹配串中的w之前的某个字符取代w。也就是要将要匹配的串向右平移一个量(j+这个量<=i)。这里我们列出所有的平移情况:

(1)

abcdabef        
 abcdabw        

(2)

abcdabef        
  abcdabw       

(3)

abcdabef        
   abcdabw      

(4)

abcdabef        
    abcdabw     

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(5)

abcdabef            
     abcdabw    

(6)

abcdabef        
      abcdabw   

上面就是所有的平移情况,情况(1),(2),(3)从第一个就不匹配;情况(4)从第三个不匹配;(5),(6)不需要讨论;可以看到,在(4)之前都是从第一个字符起两个串就不匹配,直到(4)才出现不是在第一个字符就不匹配的情况。然后再看(4)中的匹配上的字符串,居然是 a,b,是要匹配的 最长的相同前缀与后缀字符串。为什么会这样,这就用到了上面的前提@1 。

再回到最初不匹配的情形下:

abcdabef
abcdabw 

正如前提@1所说,e,w之前两个字符串完全匹配,也就是完全相同。所以要求被匹配串e之前与要匹配串从0开始相同的最长子串串也就变成了在要匹配字串中求w之前与要匹配串中从0开始相同的最长子串

abcdabef
abcdabw 
就像图中表示的那样,要求黄色与红色的关系,就是求绿色与红色的关系;所以我们在求NEXT数组时所求出的最长的相同前缀与后缀字符串就是在不匹配的位置之前被匹配字符串中与要匹配的字符串中从0开始所以字符完全相同的最长的子串。

然后(1)->(2)->(3)->(4)的平移过程,其实就是使用暴力法的过程;KMP算法就是利用了这个特性,在某个位置发生不匹配的情况后,直接将比较情况变成(4)跳过了(1)、(2)、(3)这些从一开始就不匹配的匹配情况,而且断定了在a,b已经匹配,只需要测试a,b之后的字符是否匹配就可以了,如下图所示:

012345i7        
abcdabef        
    abcdabw     
    01j3        

在i的位置发生了不匹配,直接平移到了上图中的情况,然后直接从j=2位置的要匹配字符串的字符与i=6位置的被匹配字符串的字符进行对比就可以了;

为什么能跳过暴力算法的从开始就不匹配的步骤,并且确定了这次比较中已经匹配的字符串的长度呢?

个人理解:在i位置发生了不匹配的情况,我们从i往前逐渐加大长度来取得字符串,然后确定这个字符串在要匹配的串中从0开始能否得到,知道走到得不到的时候,

也就是恰好如上图中的 a,b,c中的c与 d,a,b中的b不想同的时候,由于d的出现,就可以确定它们从一开始就无法匹配;而反之取到 a,b的时候,说明a,b是可以匹配的,只需要确定i以及i之后与a,b之后是否能够匹配就可以了。


最后确定一下j与NEXT数组的关系,求出要匹配的字符串的NEXT数组的值,如下:

abcdabw
0111123

在这里,j=NEXT值-1; 其中当NEXT为0的时候,j=-1;这种情况说明第一个字符就不匹配,在算法中i会直接+1,然后j也直接+1;也就是从被匹配串的第i+1位置和要匹配串从0开始进行匹配。


下面是算法的具体实现,(最近在学python,所以就用python写了;PS:求next数组不是最优化的算法)



# -*- coding:utf-8 -*-
# Author: Evan Midef kmp(haystack, needle):slen = len(haystack)plen = len(needle)nexts = [0] * plenget_next(needle, nexts)print(nexts)i = 0j = 0
"""
当haystack[i] == needle[j]的时候,直接i++,j++看下一个是否匹配
一旦不匹配那么i不变,j=nexts[j]-1;边界情况下,j=nexts[0]-1=-1;
这中情况说明没有满足的NEXT值,也就是在i前面的已匹配字符串的长度为-1
也就是在i+1前面已匹配的字符串的长度为0(即从i+1与j=0处开始比较),
恰好也对应i++ (=>i+1),j++( =>0)然后看下一个是否匹配
"""while i < slen and j < plen:if haystack[i] == needle[j] or j == -1:i += 1j += 1else:j = nexts[j]-1if j == plen:return i-jelse:return -1"""
设要求的字符串为A
在求next数组的时候我们已经求的了位置j的NEXT值为k+1
也就是说在要求的字符串中从[0到k-1]与[j-k到j-1]完全相同,
所以如果A[k]=A[j]的时候,只需要j++;k++;nexts[j]=k+1 就求的了j+1位置的NEXT值
当A[k]!=A[j]的时候,我们把A进行复制,赋值一个A';让k对应与A',让j对应于A;且容易得到A'的NEXT数组和A完全相同
就可以看作在A'中[0,k-1]与A中[j-k,j-1]完全相同(也就是匹配成功),但是在{j,k}处匹配失败,所以j不变,而是将
k赋值为next[k]-1(在k之前的所有NEXT值是已经求得的),最差的情况k会取到NEXT[0]-1也就是k=-1;这时在j+1之前的A'
的长度是k+1=0;所以A[j+1]要与A'[0]对应,也就是k=0;所以也需要执行操作j++ ( =>j+1),k++( =>0) 然后nexts[j]=k+1
"""def get_next(needle, nexts):al = len(needle) #求出要求NEXT数组的字符串的长度nexts[0] = 0 #NEXT[0]是恒定值0k = -1j = 0while j < al - 1: if k == -1 or needle[j] == needle[k]:j += 1k += 1nexts[j] = k+1else:k = nexts[k]-1print(kmp("hello", "ll"))





本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/569211.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最长公共连续子串

给出两个字符串&#xff08;可能包含空格&#xff09;,找出其中最长的公共连续子串,输出其长度。 注意这里是找连续子串。 算法&#xff1a;动态规划。f[i][j]表示第一个字符串前i个字符中与第二个字符串前j个中的最长连续子串长度 那么状态转移为&#xff1a;当s1(i)s2(j)时&a…

求最长回文串-从动态规划到马拉车之路(上)

要解决的问题&#xff1a; 给定一个字符串&#xff0c;要求求出这个字符串中的最长的回文串子串。 例子&#xff1a; cbddba的最长回文子串为 bddb cbdedba的最长回文子串为dbedb 由上面的例子可以看到&#xff0c;在考虑回文子串的问题时需要考虑奇偶性。因为奇回文关于中…

为什么Python中称__lt__、__gt__等为“富比较”方法

Python中基类object提供了一系列可以用于实现同类对象进行“比较”的方法&#xff0c;可以用于同类对象的不同实例进行比较&#xff0c;包括__lt__、__gt__、__le__、__ge__、__eq__和__ne__六个方法。 那么为什么叫“富比较”(“rich comparison”)方法呢&#xff1f;查了相关…

求最长回文串-从动态规划到马拉车之路(下)

预备知识&#xff1a; &#xff08;1&#xff09;在一个数轴上有两点i和j&#xff08;i<j&#xff09;关于点m对称&#xff0c;那么有 i 2m-j&#xff1b; 证明&#xff1a; 因为 i<j 且 i 和 j 关于 m 对称&#xff0c;那么有 &#xff08;i j&#xff09;/ 2 m 所…

项目管理实战之团队管理 (转自:zhuweisky)

一个系统不仅需要优秀的分析和设计&#xff0c;更需要一个良好的过程将其从蓝图转化为实现。这个过程中最重要的是对团队的管理&#xff0c;也就是人的管理。一个优秀的团队和一个糟糕的团队的效能是天壤之别&#xff0c;她们之间的比例不是1:100或1:1000这样量化的数字能够表示…

python3 内置方法

# -*- coding:utf-8 -*- # Author: Evan Mi import functools # 取绝对值 print(abs:, abs(-1)) # 如果一个可迭代对象的所有元素都为真&#xff0c;返回true ;空也返回真 print(all:, all([1, 0, -3])) # 有一个为真就全为真 print(any:, any([1, 0, -1])) # 变成可打印的字符…

JS 职责链模式

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><script>/*职责链模式 所有对象依次处理请求&#xff0c;1不能处理传给2&#xff0c;2不能处理传给3....*//*场景 打折 100以下不打折&…

python3字符串常用操作

# -*- coding:utf-8 -*- # Author: Evan Miname "my name is alex"print(name.capitalize()) # 首字母大写 print(name.count("a")) # 统计整个字符中a的个数 print(aaaaa.count("a", 0, len(aaaaa)-1)) # 前闭后开 print(name.center(50, &…

通过NGINX location实现一个域名访问多个项目

location ~ \.php$ {   root /home/webroot; //此目录下有多个项目 project1 &#xff0c;project2...   fastcgi_pass $php_upstream;   fastcgi_index index.php;   include fastcgi.conf; } location ~/project1 {   index index.php;   fastcgi_pass $php_u…

python3 set相关操作

# -*- coding:utf-8 -*- # Author: Evan Mi# 创建一个set list_1 [1, 3, 5, 7, 3, 6, 7, 9] list_1 set(list_1) list_2 set([2, 6, 0, 66, 22, 8, 4]) print(list_1, type(list_1))# 交集 print(list_1.intersection(list_2)) print(list_1 & list_2) # 并集 print(lis…

JDK环境变量

下载打开如下链接&#xff1a;http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html &#xff0c;进入JDK1.8下载官网&#xff0c;或者直接百度JDK1.8&#xff0c;也可进入下载官网。 进入官网后&#xff0c;按照所给信息下载对应系统版本的J…

python3字典相关方法

# -*- coding:utf-8 -*- # Author: Evan Miinfo {stu1101: TengLan Wu,stu1102: LongZe Luola,stu1103: XiaoZe Maliya } # 字典是无序的 print(info) print(info[stu1101]) # 不存在会报错 print(info.get(stu1101)) # 不存在返回None print(stu1103 in info) # 判断是否…

Shadow Defender 语言文件并注册

:: ::关闭回显 echo off ::设置标题 title 覆盖 Shadow Defender 语言文件并注册:: ::获取管理员权限 set "_FilePath%~f0" set "_FileDir%~dp0" setlocal EnableExtensions EnableDelayedExpansion fltmc >nul 2>&1 || (echo Set UAC CreateOb…

python3 列表相关操作

# -*- coding:utf-8 -*- # Author: Evan Mi import copynames ["ZhangYang", "Guyun", "XiangPeng", "XuliangChen"] #创建一个列表 names.append("LeiHaiDong") # 给列表的末尾追加元素 names.insert(1, "ChenRongHu…

NickLee 多層菜單

void InitMenu(){ //初始化菜單 MenuItem menuFirst; DataSet dsPermit; UserInfo ui ; DataSet dsFfunc.GetDataTable("select * from cqsSystemTree where F_Parent000 and isValid1 order by showSort"); foreach (DataRow myrow in dsF.Tables[0].Rows…

python3 生成器

要说生成器&#xff0c;就必须首先要知道列表的概念&#xff1b; 我们创建一个如下的列表&#xff1a; ls [1,2,3,4,5,6,7,8,9] 那么就开辟了一个门牌号为ls的内存区&#xff0c;然后真的把1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&…

递归 与 动态规划 区别

递归 &#xff1a; 后面的子问题由前面的子问题解来表示 f(n) _f(n-1) f(n-2)等来表示 动态规划&#xff1a;前面的解由后面的子问题解来选择&#xff0c;自底向上&#xff0c;后面的解层层向前 得到最前的解。 key&#xff1a;找到dp[n] 与 dp[n-1]的联系&#xff0c;也就…

python3 文件相关操作

# -*- coding:utf-8 -*- # Author: Evan Mi""" data open("E:/pythondata/day02/yesterday.txt").read() print(data) """ # r是读模式&#xff0c;找不到文件会报错 r 在读的基础上有了写的能力&#xff0c;这里的写就是追加 # w是…

libvirt虚拟机管理常用指令

创建虚拟机 virt-install virt-install --connect qemu:///system -n $NAME -r $MEM -f $DISK -s $DISK_SIZE --vnc --vnclisten0.0.0.0 --os-typelinux --os-variantcentos7 --vcpus2 --network bridgebr0 -c $ISO --force 其中DISK_SIZE以G为单位&#xff0c;MEM以MB为单位&a…

Copy: 了解SQL Server锁争用:NOLOCK 和 ROWLOCK 的秘密

From http://blog.csdn.net/Atwind/archive/2007/10/19/1832844.aspx 关系型数据库&#xff0c;如SQL Server&#xff0c;使用锁来避免多用户修改数据时的并发冲突。当一组数据被某个用户锁定时&#xff0c;除非第一个用户结束修改并释放锁&#xff0c;否则其他用户就无法修改…