严蔚敏 数据结构 kmp算法详解

合集下载

KMP算法详解

KMP算法详解
1
A = a b a b a b a a b a b … B = a b a b a c b j = 1 2 3 4 5 6 7 这时,新的 j=3仍然不能满足 A[i+1]=B[j+1],此时我们再次减小 j 值,将 j 再次更新为 P[3] ,即1: i = 1 2 3 4 5 6 7 8 9 …… A = a b a b a b a a b a b … B = a b a b a c b j = 1 2 3 4 5 6 7 现在,i 还是7,j 已经变成1了。而此时 A[8]居然仍然不等于 B[j+1]。这样,j 必须减小到 P[1],即0: i = 1 2 3 4 5 6 7 8 9 …… A = a b a b a b a a b a b … B = a b a b a c b j = 0 1 2 3 4 5 6 7 终于,A[8]=B[1],i 变为8,j 为1。事实上,有可能 j 到了0仍然不能满足 A[i+1]=B[j+1](比如 A[8]="d"时) 。 因此,准确的说法是,当 j=0了时,我们增加 i 值但忽略 j 直到出现 A[i]=B[1]为止。 这个过程的代码很短(真的很短) ,我们在这里给出: j:=0; for i:=1 to n do begin while (j>0) and (B[j+1]<>A[i]) do j:=P[j]; if B[j+1]=A[i] then j:=j+1; if j=m then begin writeln('Pattern occurs with shift ',i-m); j:=P[j]; end; end; 最后的 j:=P[j]是为了让程序继续做下去,因为我们有可能找到多处匹配。 这个程序或许比想像中的要简单,因为对于 i 值的不断增加,代码用的是 for 循环。因此,这个代码可以这 样形象地理解:扫描字符串 A,并更新可以匹配到 B 的什么位置。 现在,我们还遗留了两个重要的问题:一,为什么这个程序是线性的;二,如何快速预处理 P 数组。 为 什么这个程序是 O(n)的?其实,主要的争议在于,while 循环使得执行次数出现了不确定因素。我们将 用到时间复杂度的摊还分析中的主要策略,简单地说 就是通过观察某一个变量或函数值的变化来对零散的、杂 乱的、不规则的执行次数进行累计。KMP 的时间复杂度分析可谓摊还分析的典型。我们从上述程序的 j 值入手。 每一次执行 while 循环都会使 j 减小(但不能减成负的) ,而另外的改变 j 值的地方只有第五行。每次执行了这一 行,j 都只能加1;因此,整个过程 中 j 最多加了 n 个1。于是,j 最多只有 n 次减小的机会(j 值减小的次数当然 不能超过 n,因为 j 永远是非负整数) 。这告诉我们,while 循环总共最多执行 了 n 次。按照摊还分析的说法,平 摊到每次 for 循环中后,一次 for 循环的复杂度为 O(1)。整个过程显然是 O(n)的。这样的分析对于后面 P 数组预 处理 的过程同样有效,同样可以得到预处理过程的复杂度为 O(m)。 预处理不需要按照 P 的定义写成 O(m^2)甚至 O(m^3)的。我们可以通 过 P[1],P[2],...,P[j-1]的值来获得 P[j]的 值。对于刚才的 B="ababacb",假如我们已经求出了 P[1],P[2],P[3]和 P[4],看看我们应该怎么求出 P[5]和 P[6]。 P[4]=2,那么 P [5]显然等于 P[4]+1,因为由 P[4]可以知道,B[1,2]已经和 B[3,4]相等了,现在又有 B[3]=B[5],所 以 P[5]可以由 P[4] 后面加一个字符得到。P[6]也等于 P[5]+1吗?显然不是,因为 B[ P[5]+1 ]<>B[6]。那么,我们 要考虑“退一步”了。我们考虑 P[6]是否有可能由 P[5]的情况所包含的子串得到,即是否 P[6]=P[ P[5] ]+1。这里想 不通的话可以仔细看一下:

数据结构——KMP算法

数据结构——KMP算法

数据结构——KMP算法算法介绍 KMP算法是⼀种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此⼈们称它为克努特—莫⾥斯—普拉特操作(简称KMP算法)。

KMP算法的核⼼是利⽤匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的⽬的。

具体实现就是通过⼀个next()函数实现,函数本⾝包含了模式串的局部匹配信息。

KMP算法的时间复杂度O(m+n)。

next数组 我们记主串为字符串S,模式串为字符串P。

我们⽤next[j]表⽰以字符Pj结尾的⼦串的长度相等的前缀字符串与后缀字符串长度的最⼤值。

特别地,当没有满⾜条件的⼦串时,next[j] = 0。

为了⽅便起见,我们将字符串从下标1开始匹配。

如此,next数组所表⽰的长度就与下标数值相等了。

算法思路 我们从左到右依次枚举S的每⼀个字符Si,对于当前待匹配字符Si,我们假设当前P字符串中已匹配到Pj。

那么我们只需判断Si和Pj+1,若两者相同,则继续匹配。

若两者不相同,那么我们使j=next[j],即可最⼤限度的减少匹配次数。

因为S字符串的从某位置开始到前i-1的部分与P字符串的前j个字符已匹配(即完全相同),如图中两蓝⾊直线所夹的S、P的两段,⽽P1到Pnext[j]部分是长度最⼤的与以Pj结尾的后缀完全相同的前缀(图中绿⾊线段),⽽该以Pj结尾的后缀则必定与S中⼀段以Si-1结尾的⼦串完全相同,因⽽保证了上述操作的正确性。

接下去只需重复上述操作即可。

⽽对于next数组的预处理,也同上述操作类似,我们只需要以字符串P来匹配字符串P即可。

模板呈现 模板题链接: 代码如下:#include <iostream>#include <algorithm>#include <cstdio>using namespace std;const int M = 1e5+10;int n,m;int ne[M];char s[M],p[M];int main(){cin>>n>>p+1;cin>>m>>s+1;for(int i=2,j=0;i<=n;i++){while(j && p[i]!=p[j+1])j=ne[j];if(p[i]==p[j+1])j++;ne[i]=j;}for(int i=1,j=0;i<=m;i++){while(j && s[i]!=p[j+1])j=ne[j];if(s[i]==p[j+1])j++;if(j==n){printf("%d ",i-n+1-1);j=ne[j]; //可有可⽆,好习惯要加上。

KMP算法详解

KMP算法详解

KMP算法详解我们从一个普通的串的模式匹配算法开始讲起,这样你才能更深入的了解KMP算法及其优点。

咱们先来看看普通的串的模式匹配算法是怎么进行比较的主串(S) a b a b c a b c a c b a b子串(T)a b c a c (子串又被称为模式串)红色表示当前这趟比较指针所在位置,兰色表示当前这趟比较中匹配的部分第一趟(详细过程)a b a b c a b c a c b a ba b c a ca b a b c a b c a c b a ba b c a ca b a b c a b c a c b a ba b c a c遇到不匹配的地方时指针回朔,子串向前移动一位(下同),变成如下形式a b a b c a b c a c b a ba b c a c第二趟(省略了中间阶段指针移动比较过程,下同)a b a b c a b c a c b a ba b c a c第三趟a b a b c a b c a c b a ba b c a c第四趟a b a b c a b c a c b a ba b c a c第五趟aba b c a b c a c b a ba b c a c第六趟ab a b c a b c a c b a ba b c a c_完成匹配,跳出这就是普通算法的详细匹配过程,看明白了算法就简单了详细算法我现在就不给了,等以后有时间再编辑。

不过假如串的长度为m,子串的长度为n 的话,那么这个算法在最坏的情况下的时间复杂度为O(m*n) ,有没有办法降低它的时间复杂度呢?(废话,当然有拉,不然回这个帖子干什么)拜D.E.K nuth 和J.H.M orris 和V.R.P ratt 所赐,我们有了一种时间复杂度为O(m+n)的算法,为了纪念这3位强人为计算机科学所做的贡献,分别取这3位先生的名字的首写字母K,M,P来命名这个算法,即著名的KMP算法。

我们先不管这个KMP算法是什么,我们先来看看我们能够想到怎样的方法来改进上面的普通算法。

KMP算法思想

KMP算法思想

KMP字符串模式匹配详解来自CSDN A_B_C_ABC网友KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。

简单匹配算法的时间复杂度为O(m*n);KMP匹配算法。

可以证明它的时间复杂度为O(m+n).。

一.简单匹配算法先来看一个简单匹配算法的函数:int Index_BF ( char S [ ], char T [ ], int pos ){/*若串S中从第pos(S的下标0≤pos<StrLength(S))个字符起存在和串T相同的子串,则称匹配成功,返回第一个这样的子串在串S中的下标,否则返回-1 */int i = pos, j = 0;while ( S[i+j] != '\0'&& T[j] != '\0')if ( S[i+j] == T[j] )j ++;//继续比较后一字符else{i ++; j = 0;//重新开始新的一轮匹配}if ( T[j] == '\0')return i;//匹配成功返回下标elsereturn -1;//串S中(第pos个字符起)不存在和串T相同的子串}// Index_BF此算法的思想是直截了当的:将主串S中某个位置i起始的子串和模式串T 相比较。

即从j=0起比较S[i+j]与T[j],若相等,则在主串S中存在以i为起始位置匹配成功的可能性,继续往后比较( j逐步增1 ),直至与T串中最后一个字符相等为止,否则改从S串的下一个字符起重新开始进行下一轮的"匹配",即将串T向后滑动一位,即i增1,而j退回至0,重新开始新一轮的匹配。

例如:在串S=”abcabcabdabba”中查找T=” abcabd”(我们可以假设从下标0开始):先是比较S[0]和T[0]是否相等,然后比较S[1]和T[1]是否相等…我们发现一直比较到S[5]和T[5]才不等。

数据结构与算法(清华大学出版社,严蔚敏)实验讲义

数据结构与算法(清华大学出版社,严蔚敏)实验讲义

实验内容与实验指导--计算机系列课程
实验1: 线性表的顺序存储结构定义及基本操作
(必做)

一、实验目的
. 掌握线性表的逻辑特征 . 掌握线性表顺序存储结构的特点,熟练掌握顺序表的 基本运算 . 熟练掌握线性表的链式存储结构定义及基本操作 . 理解循环链表和双链表的特点和基本运算 . 加深对顺序存储数据结构的理解和链式存储数据结构 的理解,逐步培养解决实际问题的编程能力
实验2: 线性表的综合应用 (选做)
一、实验目的
掌握顺序表和链表的概念,学会对问题进行分 析,选择恰当的逻辑结构和物理结构 加深对顺序表和链表的理解,培养解决实际问 题的编程能力
实验内容与实验指导--计算机系列课程
二、实验内容
实现一元稀疏多项式的表示及基本操作(建 立、销毁、输出、加法、减法、乘法等操作)
实验内容与实验指导--计算机系列课程
5) 新建文件/C/C++ Header File,选中“ 添加到工程 的复选按钮” ,输入文件名“ seqlistAlgo. h” ,按“ 确 定” 按钮,在显示的代码编辑区内输入如上的参考程 序; 6) 新建文件/C++ Source File,选中“ 添加到工程的复 选按钮” ,输入文件名“ seqlistUse. cpp” ,按“ 确定” 按 钮,在显示的代码编辑区内输入如上的参考程序; 7) 按F7键,或工具图标进行工程的建立,如有错 误,根据错误显示区中的提示,改正错误,重新建立 应用程序; 8) 按Ctrl+F5键,或工具图标进行工程的执行。
2.实现要求: 对链表的各项操作一定要编写成为C (C++)语言函数,组合成模块化的形式,还要 针对每个算法的实现从时间复杂度和空间复杂 度上进行评价; 按要求编写实验程序,将实验程序上机调试 运行,给出输出的结果,并提交实验报告,写 出调试运行程序的分析和体会。

数据结构kmp算法详解

数据结构kmp算法详解

数据结构kmp算法详解
KMP算法是一种字符串匹配算法,用于在一个主串中查找一个模式串的出现位置。

KMP 算法的时间复杂度为O(m+n),其中m为主串的长度,n为模式串的长度。

理解KMP算法的关键是要理解它使用的“部分匹配表”(partial match table),该表可以在O(n)的时间内预处理出来,用于帮助寻找匹配失败时应该跳转到的下一个位置。

相比于暴力匹配算法,KMP算法在遇到不匹配的字符时,不会同时回退txt和pat的指针,而是借助 dp 数组中储存的信息把 pat 移到正确的位置继续匹配,因此不会重复扫描txt,时间复杂度只需O(N)。

KMP算法的难点在于,如何计算 dp 数组中的信息以及如何根据这些信息正确地移动pat 的指针,这需要确定有限状态自动机来辅助。

KMP算法详解

KMP算法详解

KMP算法详解KMP 算法详解KMP 算法是⼀个⼗分⾼效的字符串查找算法,⽬的是在⼀个字符串 s 中,查询 s 是否包含⼦字符串 p,若包含,则返回 p 在 s 中起点的下标。

KMP 算法全称为 Knuth-Morris-Pratt 算法,由 Knuth 和 Pratt 在1974年构思,同年 Morris 也独⽴地设计出该算法,最终由三⼈于1977年联合发表。

举⼀个简单的例⼦,在字符串 s = ababcabababca 中查找⼦字符串 p = abababca,如果暴⼒查找,我们会遍历 s 中的每⼀个字符,若 s[i] = p[0],则向后查询p.length() 位是否都相等。

这种朴素的暴⼒的算法复杂度为O(m×n),其中m和n分别是 p 和 s 的长度。

KMP 算法可以⽅便地简化这⼀查询的时间复杂度,达到O(m+n)。

1. PMT 序列PMT 序列是 KMP 算法的核⼼,即 Partial Match Table(部分匹配表)。

举个例⼦:char a b a b a b c aindex01234567PMT00123401PMT 的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。

PMT[0] = 0: 字符串 a 既没有前缀,也没有后缀;PMT[1] = 0: 字符串 ab 前缀集合为 {a},后缀集合为 {b},没有交集;PMT[2] = 1: 字符串 aba 前缀集合为 {a, ab},后缀集合为 {ba, a},交集为 {a},交集元素的最长长度为1;PMT[3] = 2: 字符串 abab 前缀集合为 {a, ab, aba},后缀集合为 {bab, ab, b},交集为 {ab},交集元素的最长长度为2;…… 以此类推。

2. 算法主体现在我们已经知道了 PMT 序列的含义,那么假设在 PMT 序列已经给定的情况下,如何加速字符串匹配算法?tar 存储 s 的下标,从 0 开始,若 tar > s.length() - 1,代表匹配失败;pos 存储 p 的下标,从 0 开始,若 s[tar] != p[pos],则 pos ⾛到下⼀个可能匹配的位置。

KMP算法中next数组的计算方法研究

KMP算法中next数组的计算方法研究
计算机技术与发展 第 19 卷 第6期 Vol. 19 No. 6 2009 年 6 月 COMPU TER TECHNOLO GY AND DEV ELOPM EN T J une 2009
KMP 算法中 next 数组的计算方ห้องสมุดไป่ตู้研究
汤亚玲
( 安徽工业大学 计算机学院 ,安徽 马鞍山 243002)
( Recurrence) 的思想来加以实现 。
递推的思路是 ,先根据定义赋值 next [ 1 ] = 0 , 通过
next [ 1 ] 计算出 next [ 2 ] , 在得到 next [ 1 ] 、 next [ 2 ] 、 … next [ i - 1 ] 的值基础之上 , 计算 next [ i ] 的值 , 它递推
F ( N ) 问题转化为求解 F ( N - 1) 的问题 , 把计算 F ( N - 1) 转化为计算 F ( N - 2) , 直到某个特定自然数 n0 , F ( n 0) 可解 , 即得到求解问题 F ( N ) 的递归计算方法 。
此时 next [ i ] = k + 1 , 即 :
next [ i ] = next [ i - 1 ] + 1
- 1 ] , 如果相等 , 则 next [ i ] 的值应该为 next [ next [ i -
1 ] + 1 , 反之如果 t [ next [ next [ i - 1 ] ] ] 不等于 t [ i 1 ] , 则判断 t [ next [ next [ next [ i - 1 ] ] ] ] 是否等于 t [ i 1 ] , 如此一直下去 , 直至存在某个 k = next [ … next [ i 1 ] …] > 0 , 使得 t [ k ] = t [ i - 1 ] , 此时 next [ i ] = k + 1 , 否则当 k 等于 0 时 , 则 next [ i ] 值为 1 ( ( 1) 式定义中

数据结构教学中KMP算法解析

数据结构教学中KMP算法解析

数据结构教学中KMP算法解析摘要:模式匹配是字符串的基本运算之一,也是数据结构教学中的难点之一。

分析了模式匹配KMP算法以及算法中next函数的含义,给出了next函数的两种实现方法,有助于在教学实践中帮助学生更好地理解该算法。

关键词:数据结构;模式匹配;KMP算法0引言模式匹配(Patten Matching)是许多计算机应用领域的基础问题,在数据结构中模式匹配是字符串的基本运算之一。

字符串模式匹配指的是,找出特定的模式串在一个较长的字符串中出现的位置。

有两个字符串S和T,字符串S称为目标串,字符串T称为模式串,要求找出模式T在S中的首次出现的位置。

一旦模式T在目标S中找到,就称发生一次匹配。

有些应用可能会要求找出所有的匹配位置<sup>[1]</sup>。

例如,目标串S= 'Shanghai',模式串T= 'gha',则匹配结果为4。

模式匹配的典型算法包括朴素匹配算法、KMP算法和BM算法等,其中KMP算法是效率较高且经典的模式匹配算法之一<sup>[2]</sup>。

在数据结构教学中,由于KMP算法较难理解,课堂讲授往往很难取得好的效果。

本文通过对传统的朴素匹配算法与KMP算法的比较,分析next函数的含义以及实现方法,来帮助理解KMP算法。

1朴素匹配算法在朴素匹配算法中,S和T分别为目标串和模式串,变量i和j 为两个静态指针,分别表示S和T中当前正待比较的字符位置。

算法的基本思想是:第1趟匹配:从S的第1个字符(序号为0)起和T的第一个字符比较之,如果相等,则继续逐个比较后续字符(i++;j++),否则开始下一趟匹配。

新的一趟匹配:i的初值为上一趟的初值+1 ,j的初值为1,如果比较结果相等,则继续逐个比较后续字符,否则开始下一趟匹配。

依次类推,直至某一趟匹配中,T的每个字符依次和S中的一个连续的字符序列相等,则称匹配成功,否则称匹配不成功。

(原创)详解KMP算法

(原创)详解KMP算法

(原创)详解KMP算法KMP算法应该是每⼀本《数据结构》书都会讲的,算是知名度最⾼的算法之⼀了,但很可惜,我⼤⼆那年压根就没看懂过~~~之后也在很多地⽅也都经常看到讲解KMP算法的⽂章,看久了好像也知道是怎么⼀回事,但总感觉有些地⽅⾃⼰还是没有完全懂明⽩。

这两天花了点时间总结⼀下,有点⼩体会,我希望可以通过我⾃⼰的语⾔来把这个算法的⼀些细节梳理清楚,也算是考验⼀下⾃⼰有真正理解这个算法。

什么是KMP算法:KMP是三位⼤⽜:D.E.Knuth、J.H.Morris和V.R.Pratt同时发现的。

其中第⼀位就是《计算机程序设计艺术》的作者!!KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。

说简单点就是我们平时常说的关键字搜索。

模式串就是关键字(接下来称它为P),如果它在⼀个主串(接下来称为T)中出现,就返回它的具体位置,否则返回-1(常⽤⼿段)。

⾸先,对于这个问题有⼀个很单纯的想法:从左到右⼀个个匹配,如果这个过程中有某个字符不匹配,就跳回去,将模式串向右移动⼀位。

这有什么难的?我们可以这样初始化:之后我们只需要⽐较i指针指向的字符和j指针指向的字符是否⼀致。

如果⼀致就都向后移动,如果不⼀致,如下图:A和E不相等,那就把i指针移回第1位(假设下标从0开始),j移动到模式串的第0位,然后⼜重新开始这个步骤:基于这个想法我们可以得到以下的程序:1/**23 * 暴⼒破解法45 * @param ts 主串67 * @param ps 模式串89 * @return如果找到,返回在主串中第⼀个字符出现的下标,否则为-11011*/1213public static int bf(String ts, String ps) {1415char[] t = ts.toCharArray();1617char[] p = ps.toCharArray();1819int i = 0; // 主串的位置2021int j = 0; // 模式串的位置2223while (i < t.length && j < p.length) {2425if (t[i] == p[j]) { // 当两个字符相同,就⽐较下⼀个2627 i++;2829 j++;3031 } else {3233 i = i - j + 1; // ⼀旦不匹配,i后退3435 j = 0; // j归03637 }3839 }4041if (j == p.length) {4243return i - j;4445 } else {4647return -1;4849 }5051 }上⾯的程序是没有问题的,但不够好!(想起我⾼中时候数字⽼师的⼀句话:我不能说你错,只能说你不对~~~)如果是⼈为来寻找的话,肯定不会再把i移动回第1位,因为主串匹配失败的位置前⾯除了第⼀个A之外再也没有A了,我们为什么能知道主串前⾯只有⼀个A?因为我们已经知道前⾯三个字符都是匹配的!(这很重要)。

kmp百度百科

kmp百度百科

kmp算法[编辑本段]kmp算法-概述一种改进的字符串匹配算法,由 D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。

[编辑本段]kmp算法-学习介绍完全掌握KMP算法思想学过数据结构的人,都对KMP算法印象颇深。

尤其是新手,更是难以理解其涵义,搞得一头雾水。

今天我们就来面对它,不将它彻底搞懂,誓不罢休。

如今,大伙基本上都用严蔚敏老师的书,那我就以此来讲解KMP 算法。

(小弟正在备战考研,为了节省时间,很多课本上的话我都在此省略了,以后一定补上。

)严老的《数据结构》79页讲了基本的匹配方法,这是基础。

先把这个搞懂了。

80页在讲KMP算法的开始先举了个例子,让我们对KMP的基本思想有了最初的认识。

目的在于指出“由此,在整个匹配的过程中,i指针没有回溯,”。

我们继续往下看:现在讨论一般情况。

假设主串:s: ‘s(1) s(2) s(3) ……s(n)’; 模式串:p: ‘p(1) p(2) p(3)…..p(m)’把课本上的这一段看完后,继续现在我们假设主串第i个字符与模式串的第j(j<=m)个字符‘失配’后,主串第i个字符与模式串的第k(k<j)个字符继续比较此时,s(i)≠p(j), 有主串:S(1)……s(i-j+1)……s(i-1) s(i) ………….|| (相配) || ≠(失配)匹配串:P(1) ……. p(j-1) p(j)由此,我们得到关系式‘p(1) p(2) p(3)…..p(j-1)’= ’s(i-j+1)……s(i-1)’由于s(i)≠p(j),接下来s(i)将与p(k)继续比较,则模式串中的前(k-1)个字符的子串必须满足下列关系式,并且不可能存在k’>k 满足下列关系式:(k<j),‘p(1) p(2) p(3)…..p(k-1)’= ’s(i-k+1)s(i-k+2)……s(i-1)’即:主串:S(1)……s(i-k +1) s(i-k +2) ……s(i-1) s(i) ………….|| (相配) || || ?(有待比较)匹配串:P(1) p(2) ……p(k-1) p(k)现在我们把前面总结的关系综合一下有:S(1)…s(i-j +1)…s(i-k +1) s(i-k +2) ……s(i-1) s(i) ……|| (相配) || || || ≠(失配)P(1) ……p(j-k+1) p(j-k+2) ….... p(j-1) p(j)|| (相配) || || ?(有待比较)P(1) p(2) ……. p(k-1) p(k)由上,我们得到关系:‘p(1) p(2) p(3)…..p(k-1)’= ’s(j-k+1)s(j-k+2)……s(j-1)’接下来看“反之,若模式串中存在满足式(4-4)。

kmp算法详解

kmp算法详解

引记此前一天,一位MS的朋友邀我一起去与他讨论快速排序,红黑树,字典树,B树、后缀树,包括KMP算法,唯独在讲解KMP算法的时候,言语磕磕碰碰,我想,原因有二:1、博客内的东西不常回顾,忘了不少;2、便是我对KMP算法的理解还不够彻底,自不用说讲解自如,运用自如了。

所以,特再写本篇文章。

由于此前,个人已经写过关于KMP算法的两篇文章,所以,本文名为:KMP算法之总结篇。

本文分为如下六个部分:1. 第一部分、再次回顾普通的BF算法与KMP算法各自的时间复杂度,并两相对照各自的匹配原理;2. 第二部分、通过我此前第二篇文章的引用,用图从头到尾详细阐述KMP算法中的next数组求法,并运用求得的next数组写出KMP算法的源码;3. 第三部分、KMP算法的两种实现,代码实现一是根据本人关于KMP算法的第二篇文章所写,代码实现二是根据本人的关于KMP算法的第一篇文章所写;4. 第四部分、测试,分别对第三部分的两种实现中next数组的求法进行测试,挖掘其区别之所在;5. 第五部分、KMP完整准确源码,给出KMP算法的准确的完整源码;6. 第六步份、一眼看出字符串的next数组各值,通过几个例子,让读者能根据字符串本身一眼判断出其next数组各值。

力求让此文彻底让读者洞穿此KMP算法,所有原理,来龙去脉,让读者搞个通通透透(注意,本文中第二部分及第三部分的代码实现一的字符串下标i 从0开始计算,其它部分如第三部分的代码实现二,第五部分,和第六部分的字符串下标i 皆是从1开始的)。

在看本文之前,你心中如若对前缀和后缀这个两个概念有自己的理解,便最好了。

有些东西比如此KMP算法需要我们反复思考,反复求解才行。

个人写的关于KMP算法的第二篇文章为:六(续)、从KMP算法一步一步谈到BM算法;第一篇为:六、教你初步了解KMP算法、updated(文末链接)。

ok,若有任何问题,恳请不吝指正。

多谢。

第一部分、KMP算法初解1、普通字符串匹配BF算法与KMP算法的时间复杂度比较KMP算法是一种线性时间复杂的字符串匹配算法,它是对BF算法(Brute-Force,最基本的字符串匹配算法的)改进。

数据结构C语言版第2版习题答案解析-严蔚敏

数据结构C语言版第2版习题答案解析-严蔚敏

数据结构C语言版第2版习题答案解析-严蔚敏在学习计算机科学的道路上,数据结构无疑是一座重要的基石。

而严蔚敏老师的《数据结构(C 语言版)第 2 版》更是众多学子的经典教材。

其中的习题,不仅有助于我们巩固所学知识,还能引导我们深入思考,提高解决实际问题的能力。

接下来,让我们一同走进这些习题的答案解析。

首先,我们来谈谈线性表这一章节的习题。

线性表是数据结构中最基础、最常用的结构之一。

比如有这样一道题:“设计一个算法,从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素的值。

”对于这道题,我们首先需要遍历整个顺序表,找到最小值及其位置。

然后,将其后的元素依次向前移动一位,实现删除操作。

在 C 语言中,可以这样实现:```cinclude <stdioh>int deleteMinElement(int arr, int n) {int min = arr0;int minIndex = 0;for (int i = 1; i < n; i++){if (arri < min) {min = arri;minIndex = i;}}int temp = arrminIndex;for (int i = minIndex; i < n 1; i++){arri = arri + 1;}return temp;}int main(){int arr ={5, 3, 8, 2, 7};int n = sizeof(arr) / sizeof(arr0);int deletedElement = deleteMinElement(arr, n);printf("删除的元素为:%d\n", deletedElement);for (int i = 0; i < n 1; i++){printf("%d ", arri);}return 0;}```再来看栈和队列这部分的习题。

比如:“利用两个栈 S1 和 S2 模拟一个队列,如何实现队列的入队和出队操作?”这就需要我们巧妙地利用栈的特性。

数据结构 KMP算法

数据结构 KMP算法

问题的进一步分析:
S=“abcabeacadaadadasfsf” T=“abcabc” 上述第一轮比较在i j都为6时失败,传统的方案 是j=1,i=2进行下一轮,及如下: S=“abcabeacadaadadasfsf”
T=“abcabc” 而KPM告诉我们,应该这样: S=“abcabeacadaadadasfsf”
T=“abcde” 上式中,当i,j都为5时,比较失败,其时iห้องสมุดไป่ตู้回 溯,j从1开始,进行如下比较:
S=“abcdabcabababcde”
T=“abcde”
上例是特例,模式任一子串不是其它串的子串,即 字符互不相等。事实上:
1、我们可以相对容易的找出模式自身的特征;
2、我们依据刚比较的这一轮,结合模式内在特征 来找出模式回溯的位置
Times=5+1+3+1+1+5=16
KMP算法的思想是发现出模式内在的关联, 减少回溯:
极端的例子
目标 S=“abcdabcabababcde” 模式 T=“abcde” 可以想见,由于模式T所有字符都不相等,所
以如果某次比较失败后,目标指针根本不 需要回溯!
???
目标 S=“abcdabcabababcde” 模式 T=“abcde” 设目标的指针为i,模式的当前指针为j S=“abcdabcabababcde”
next(j) 0
1
1
1
2
2
3
2
3
KMP算法的思想就是:假设指针i ,j分别为主 串和子串不匹配所在的位置, 则i不变,j退 到next(j)所在的位置再进行比较,如相等, i,j各自增1,否则,当前的j再一次退到 next(j),直至j=1时,此时,i增1,子串则从头 开始

顺序表、链表、KMP算法-数据结构报告

顺序表、链表、KMP算法-数据结构报告

目录需求分析-------------------------------------------------P3 概要设计-------------------------------------------------P4 详细设计-------------------------------------------------P9 调试分析-------------------------------------------------P11 测试结果-------------------------------------------------P11 课程设计总结---------------------------------------------P15 参考文献-------------------------------------------------P16 附录------------------------------------------------------P16一:需求分析:顺序表的插入、删除、合并等操作:涉及顺序表的创建、插入、删除、查找、合并、显示等。

采用数组作为顺序表的结构,定义一个MAXSIZE 作为数组的最大长度。

从键盘接收用户输入的基本数据类型(这里以char为例,也可以是int等其他类型)为例演示方便,系统自带一个已经含有元素的顺序表。

然后接收用户输入指令进行相应的操作。

插入演示时,要求用户输入插入的字符串和要插入的位置;删除演示时要求用户输入要删除的长度和起始位置;合并操作演示时,要求用户输入一个字符串用来建立新的顺序表,然后两个顺序表合并并输出新的顺序表。

链表的查找、删除、计数、输出等功能以及有序链表的合并:涉及链表的创建、删除、查找、合并、显示等。

需要动态分配内存,用指针连接各节点。

先自定义一个ListNode节点用户存储数据和指针。

为了演示方便,系统依然自带了一个创建好并且含有若干元素的链表,用户可以在此基础上进行操作。

严蔚敏版大数据结构所有算法代码

严蔚敏版大数据结构所有算法代码

严蔚敏版数据结构所有算法代码------------------------线性数据结构-----------------------------2013年9月//线性表、链表//栈、队列//数组、广义表//串-------------------------线性表----------------------typedef struct{char name[20];//注意如果应用指针的形式//在初始化每个结点时一定要先为结点中的每个变量开辟内存空间char sex;char addr[100];unsigned int age;char phonenum[20];}node;//结点描述typedef struct{node *p;int length;//当前顺序表长度int listsize;//当前分配的线性表长度}list;//线性表描述list L;//定义一个线性表int initlist(list &l)//构造一个空的线性表{l.p=(node*)malloc(LIST_INIT_SIZE*sizeof(node));if(!(l.p))exit(1);l.length=0;l.listsize=LIST_INIT_SIZE;return true;}void destroylist(list &l)//销毁线性表操作{if(l.p!=NULL){free(l.p);printf("销毁成功!\n");}elseprintf("线性表不存在!\n");}int clearlist(list &l)//将线性表置空操作{if(l.p==NULL){printf("线性表不存在!\n");return false;}else{free(l.p);l.p=(node*)malloc(l.listsize*sizeof(node));l.length=0;}return true;}int listempty(list &l)//判断线性表是否为空表{if(l.p==NULL)return true;elsereturn false;}int getelem(list &l,int i,node &e)//用e返回表中第i个数据元素{if(l.p==NULL)return false;elsee=l.p[i-1];return true;}int priorelem(list &l,int i,node &pre_e)//得到第i个元素的前驱元素{if(i==0||l.p==NULL)return false;elsepre_e=l.p[i-1];return true;}int nextelem(list &l,int i,node &next_e)//得到表中第i个元素的后继元素{if(i>=l.length||l.p==NULL)return false;elsenext_e=l.p[i+1];return true;}int insertlist(list &l,int i,node &e)//将元素e插入到表l中第i个元素的后面{node *q,*k;if(i<1||i>l.length+1)return false;if(l.length>=l.listsize){l.p=(node*)realloc(l.p,(l.listsize+LISTINCREMENT)*sizeof(node));if(!l.p)exit(1);l.listsize+=LISTINCREMENT;}k=&l.p[i-1];for(q=&l.p[l.length-1];q>k;q--)*(q+1)=*q;*k=e;l.length++;return true;}int deletelist(list &l,int i,node &e)//删除表中第i个元素并用e返回其值{node *q;int j=i-1;if(i<1||i>l.length)return false;e=l.p[i-1];for(q=&l.p[i-1];j<l.length-1;j++)*q=*(++q);l.length--;return true;}void mergerlist(list la,list lb,list &lc)//归并两个按非递减排列的线性表{int la_len,lb_len,i=1,j=1,k=0;node ai,bj;la_len=la.length;lb_len=lb.length;while(i<=la_len&&j<=lb_len){getelem(la,i,ai);getelem(lb,j,bj);if(ai.a<=bj.a){insertlist(lc,++k,ai);i++;}else{insertlist(lc,++k,bj);j++;}}while(i<=la_len){getelem(la,i,ai);insertlist(lc,++k,ai);i++;}while(j<=lb_len){getelem(lb,j,bj);insertlist(lc,++k,bj);j++;}}int ListAscendingOrder(list &l)//按结点中某一元素的比较升序排列线性表中的结点{node e;int i,j;if(l.p==NULL||l.length==1)return ERROR;for(i=0;i<l.length-1;i++)for(j=i+1;j<l.length;j++)if(l.p[i].num>=l.p[j].num){e=l.p[i];l.p[i]=l.p[j];l.p[j]=e;}return OK;}//省略降序排列void MergerList(list la,list lb,list &lc)//将两线性表升序排列后再归并{node *q,*k,e1;int i=0,j=0,m=0,n;ListAscendingOrder(la);ListAscendingOrder(lb);printf("表a升序排列后为:\n");for(i=0;i<la.length;i++)printf("%d ",la.p[i].num);printf("\n");printf("表b升序排列后为:\n");for(i=0;i<lb.length;i++)printf("%d ",lb.p[i].num);printf("\n");i=0;while(i<la.length&&j<lb.length){if(la.p[i].num<=lb.p[j].num){e1=la.p[i];i++;}else{e1=lb.p[j];j++;}if(e1.num!=lc.p[lc.length-1].num)InsertList(lc,++m,e1);}if(i<la.length)while(i<la.length){if(la.p[i].num!=lc.p[lc.length-1].num)InsertList(lc,++m,la.p[i]);i++;}if(j<lb.length)while(j<lb.length){if(lb.p[j].num!=lc.p[lc.length-1].num)InsertList(lc,++m,lb.p[j]);j++;}printf("按升序排列再归并两表为:\n");for(n=0;n<lc.length;n++)printf("%d ",lc.p[n].num);printf("\n");}----------------------链表----------------------------- typedef struct{int num;}node;typedef struct LIST{node data;struct LIST *next;}list,*slist;int CreatList(slist &head)//此处应为只针对的引用{head=(list *)malloc(sizeof(list));if(!head)return ERROR;head->next=NULL;return OK;}void InvertedList(slist &head1,slist &head2){//构造新表逆置单链表函数list *p,*q;p=head1->next;q=p->next;if(p==NULL)printf("链表为空无法实现逆置操作\n");else{while(q!=NULL){p->next=head2->next;head2->next=p;p=q;q=q->next;}p->next=head2->next;head2->next=p;printf("逆置成功!?\n");}}void InsertList(slist &head,node &e)//此处应为指针的引用{//而不应该是list *headlist *p,*q;p=(list *)malloc(sizeof(list));q=head;while(q->next!=NULL)q=q->next;p->next=q->next;q->next=p;p->data=e;}void InvertedList(sqlist &head){//-------不构造新表逆置单链表函数---------// list *p,*q,*k;p=head->next;q=p->next;k=q->next;p->next=NULL;while(k!=NULL){q->next=p;p=q;q=k;k=k->next;}q->next=p;head->next=q;}//----交换链表中第i个和第j个结点,函数实现如下——// int SwapListNode(sqlist &head,int i,int j){int m,n,m1,n1,sum=0;list *p,*q,*k,*c,*d,*ba;ba=head->next;while(ba!=NULL){sum++;ba=ba->next;}if(i==j||i>sum||j>sum||i<1||j<1){printf("所要交换的两个结点有误!\n");return ERROR;}if(i<j){ m=i; n=j;}else{ m=j;n=i;}p=head;q=head;for(m1=1;m1<=m;m1++)p=p->next;for(n1=1;n1<=n;n1++)q=q->next;if(p->next==q){//如果结点相邻k=head;while(k->next!=p)k=k->next;//相邻两结点的交换p->next=q->next;q->next=p;k->next=q;}else{//如果结点不相邻k=head;c=head;while(k->next!=p)k=k->next;while(c->next!=q)c=c->next;d=p->next;//不相邻两结点之间的交换p->next=q->next;c->next=p;k->next=q;q->next=d;}return OK;}//-----将链表中结点按结点中某一项大小升序排列,函数实现如下-----// int AscendingList(sqlist &head){int m,n,sum=0,i,j;list *p,*q,*k;k=head->next;while(k!=NULL){sum++;k=k->next;}for(i=1;i<sum;i++)for(j=i+1;j<=sum;j++){p=head->next;m=1;while(m!=i){m++;p=p->next;}q=head->next;n=1;while(n!=j){n++;q=q->next;}if(p->data.exp>q->data.exp)//如果按exp降序排列,则应将>改为<;SwapListNode(head,i,j);}return OK;}//-----将两链表合并为一个链表------//int AddList(sqlist &head1,sqlist &head2,sqlist &head3){//已将表head1和表head2按某一项升序排列过sqlist p,q;node e;p=head1->next;q=head2->next;while(p!=NULL&&q!=NULL){if(p->data.exp<q->data.exp){InsertList(head3,p->data);p=p->next;}elseif(p->data.exp>q->data.exp){InsertList(head3,q->data);q=q->next;}elseif(p->data.exp==q->data.exp){e.coefficient=p->data.coefficient+q->data.coefficient;e.exp=p->data.exp;//e.exp=q->data.exp;InsertList(head3,e);p=p->next;q=q->next;}}if(p!=NULL)while(p!=NULL){InsertList(head3,p->data);p=p->next;}//如果p中有剩余,则直接将p中剩余元素插入head3中if(q!=NULL)while(q!=NULL){InsertList(head3,q->data);q=q->next;}//如果q中有剩余,则直接将q中的剩余元素插入head3中return 0;}-----------------------栈------------------------------//---------利用栈结构实现数制之间的转换------书3.2.1//typedef struct{int num;}node;typedef struct{node *base;node *top;int stacksize;}stack;//顺序栈结构定义int CreatStack(stack &stackll){stackll.base=(node *)malloc(INITSTACKSIZE*sizeof(node));if(!stackll.base)exit(OVERFLOW);stackll.top=stackll.base;stackll.stacksize=INITSTACKSIZE;return OK;}void push(stack &s,node e){//进栈操作if(s.top-s.base>=s.stacksize){ s.base=(node*)realloc(s.base,(s.stacksize+INCRESTACKMENT)*sizeof(node));if(!s.base)exit(OVERFLOW);s.top=s.base+s.stacksize;//可以不写此语句;s.stacksize+=INCRESTACKMENT;}*(s.top++)=e;//*s.top++=e;}void pop(stack &s,node &e){//出栈操作if(s.top==s.base||s.base==NULL)printf("信息有误!\n");elsee=*--s.top;}//-------取栈顶元素函数------//void gettop(stack &s,node &e){if(s.base==s.top)printf("栈为空,无法取得栈顶元素!\n");else{e=*(s.top-1);}}//-----栈的应用:括号匹配的检验------书3.2.2////省略了大部分上述已有代码//int zypd(char c){//判断是否为左括号字符if(c=='['||c=='{'||c=='(')return OK;elsereturn ERROR;}int main(void){stack s;node e1,e2,e3;char st[INITSTACKSIZE];int i=0,j;CreatStack(s);printf("请输入括号字符,以'#'做结束符:");scanf("%c",&st[i]);while(st[i]!='#'){i++;scanf("%c",&st[i]);}if(!zypd(st[0]))printf("输入字符不合法!\n");else{for(j=0;j<i;j++){if(zypd(st[j])){//如果是左括号则将此字符压入栈中e1.c=st[j];push(s,e1);}else{//如果当前st[j]元素不是左括号//则取出栈顶元素,比较栈顶元素与当前st[j]元素是否项匹配//如果匹配,则栈顶元素出栈gettop(s,e2);if(e2.c=='['&&st[j]==']'||e2.c=='{'&&st[j]=='}'||e2.c=='('&&st[j] ==')')pop(s,e3);else{printf("括号验证失败!\n");break;}}}if(s.top==s.base)//当循环结束时,如果栈为空栈说明输入的括号合法printf("括号验证成功!\n");}getchar();system("pause");return 0;}//----------链栈描述---------//typedef struct Node{int num;struct Node *next;}node;typedef struct{Node *top;Node *base;}stack;void InitStack(stack &s){s.base=(Node *)malloc(sizeof(node));if(!s.base)exit(1);else{s.base->next=NULL;s.top=s.base;}}void InsertStack(stack &s,node e){node *p;p=(node *)malloc(sizeof(node));if(!p)exit(1);else{*p=e;p->next=s.top;s.top=p;}}void DeleteStack(stack &s,node &e){node *p;if(s.top==s.base)printf("栈为空!\n");else{p=s.top;s.top=s.top->next;e=*p;free(p);}}--------------------队列---------------------- //------链队列的描述及操作-------//typedef struct Node{int a;struct Node *next;}Qnode,*QueuePtr;typedef struct{QueuePtr front;QueuePtr rear;}LinkQueue;void InitQueue(LinkQueue &Q){Q.front=(Qnode *)malloc(sizeof(Qnode));if(!Q.front)exit(1);Q.rear=Q.front;Q.front->next=NULL;}void InsertQueue(LinkQueue &Q,Qnode e){QueuePtr p;p=(Qnode *)malloc(sizeof(Qnode));if(!p)exit(1);*p=e;p->next=NULL;Q.rear->next=p;Q.rear=p;}void DeleteQueue(LinkQueue &Q,Qnode &e){Qnode *p;if(Q.front==Q.rear)printf("队列为空!\n");else{p=Q.front->next;e=*p;Q.front->next=p->next;if(p==Q.rear)Q.rear=Q.front;free(p);}}//-------------循环队列---------------// typedef struct node{int data;struct node *next;}node;typedef struct queue{node *base;int front;int rear;}Queue;int tag;void InitQueue(Queue &Q){Q.base=(node *)malloc(MAX*sizeof(node));if(!Q.base)exit(1);Q.front=Q.rear=0;tag=0;}void InsertQueue(Queue &Q,node e){if(tag==1&&Q.front==Q.rear)printf("循环队列已满!\n");else{Q.base[Q.rear]=e;Q.rear=(Q.rear+1)%MAX;if(Q.rear==Q.front)tag=1;}}void DeleteQueue(Queue &Q,node &e){if(tag==0&&Q.front==Q.rear)printf("队列为空!\n");else{e=Q.base[Q.front];Q.front=(Q.front+1)%MAX;if(Q.front==Q.rear)tag=0;}}int EmptyQueue(Queue &Q){if(Q.front==Q.rear&&tag==0)return 1;elsereturn 0;}--------------------------串----------------------------------- //-----------串:堆分配存储形式的一些操作------------------// typedef struct string{char *ch;int length;}sstring;void CreatString(sstring &T){T.ch=(char*)malloc(sizeof(char));T.length=0;}void StringAssign(sstring &T,char *s){//将串s的值赋值给串Tif(T.ch)free(T.ch);T.ch=(char*)malloc(strlen(s)*sizeof(char));//或者T.ch=(char*)malloc(sizeof(char));//动态开辟空间不同于静态内存开辟之处if(!T.ch){printf("ERROR");exit(1);}strcpy(T.ch,s);T.length=strlen(s);}void ClearString(sstring &T){if(T.ch)free(T.ch);T.length=0;}void ConcatString(sstring &T,sstring s1,sstring s2){//串连接if(T.ch)free(T.ch);T.ch=(char*)malloc((strlen(s1.ch)+strlen(s2.ch))*sizeof(char));if(!T.ch){printf("ERROR\n");exit(1);}strcpy(T.ch,s1.ch);strcat(T.ch,s2.ch);T.length=strlen(s1.ch)+strlen(s2.ch);}void SubString(sstring &sub,sstring s,int pos,int len){//取子串操作,取串s中位置从pos至len处的子串于sub中int i,j=0;if(sub.ch)free(sub.ch);sub.ch=(char *)malloc((len-pos+1+1)*sizeof(char));if(!sub.ch){printf("ERROR\n");exit(1);}for(i=pos-1;i<len;i++)sub.ch[j++]=s.ch[i];sub.ch[j]='\0';sub.length=strlen(sub.ch);}int CountString(sstring s1,sstring s2){//判断子串s2在母串s1中出现的次数int i,j,k,count=0;if(s1.length==0||s2.length==0||s2.length>s1.length){printf("ERROR\n");return 0;}else{for(i=0;i<s1.length;i++){k=1;for(j=0;j<s2.length;j++){if(s2.ch[j]!=s1.ch[i+j]){k=0;break;}}if(k)count++;}}return count;}void Deletestring(sstring &s,int pos,int len) {//删除s串中位置从pos到len处的元素int i,j,k;if(s.length==0)printf("ERROR\n");else{for(i=pos-1,j=len;j<s.length;i++,j++) s.ch[i]=s.ch[j];s.ch[i]='\0';s.length-=(len-pos)+1;}}void DeleteSub(sstring &s1,sstring s2){//删除母串s1中的子串s2int i,j,k,tag=0;for(i=0;i<s1.length;i++){k=1;if(tag)i--;for(j=0;j<s2.length;j++)if(s2.ch[j]!=s1.ch[i+j]){k=0;break;}if(k){Deletestring(s1,i+1,i+s2.length);tag=1;}}}----------------KMP算法----------------int index_kmp(string T,string S,int pos){int i=pos,j=1;while(i<=S.length&&j<=T.length){if(S.ch[i]==T.ch[j]){i++;j++;}elsej=next[j+1];}if(j>T.length)return i-T.length;elsereturn 0;}void get_next(string T){int i=1,j=0;next[1]=0;while(i<=T.length){if(j-1==0||T.ch[i]==T.ch[j]){i++;j++;if(T.ch[i]!=T.ch[j])next[i]=j;elsenext[i]=next[j];}elsej=next[j];}}———————----------------数组——————————————-----------矩阵转置的经典算法---------for(i=0;i<row;i++)for(j=0;j<col;j++)b[j][i]=a[i][j];时间复杂度为O(row*col),每个元素都要存储,相对于稀疏矩阵来说比较浪费存储空间。

KMP算法(超容易理解的next数组求法)

KMP算法(超容易理解的next数组求法)

KMP算法(超容易理解的next数组求法)我想,既然知道KMP算法了,⾃然对于其具体如何运作也是有⼀定的了解的,我也没必要再⼤费⼝⾆讲废话,⼤家⼀定查过很多资料,但是其实也都看的⼀知半解,好像⾃⼰懂了但是⼜没有理解透,特别是next数组的求法感觉很蛋疼,那么我就来给⼤家解决⼀下这个next数组的问题吧,如果对KMP算法没有概念的同学请先去查清楚再过来看看。

先讲⼀下我们后续讲解的⼀些规范,我们next数组、以及模式串都是从数组的下标1开始储存的,注意不是0!(这样操作⽅便⼀点)下⾯列出⼀个next数组:模式串A B A B A B Bj1234567next[j]0112345很多⼈可能对这个next数组的理解就是不正确的,我们来好好解释⼀下:第⼀个元素的next值,即next[1],默认是0,这是规定的,0则表⽰从头开始⽐较那么很多⼈会疑惑,那上⾯这个例⼦中,j=2的时候如果不匹配,不也是应该从头开始⽐较吗?那么为什么next[2]不是0⽽是1呢?好,你可能会机智地说,next[1]=0是你规定死的,⽽你的模式串⼜是从下标为1的地⽅保存起来的,当然next[2] = 1啦!我们知道KMP算法的匹配是通过next数组慢慢往前回复(如果匹配失败的话)的⼀个过程,那么经过next数组返回后,我们究竟是从哪⾥开始⽐较呢?是从返回的那⼀个开始,还是它的前⼀个,亦或是后⼀个?这就涉及对next数组的真正的理解了,我们来好好理⼀理,这个next数组到底是怎么⼀回事!所谓next数组的返回,其实是前⾯有⼀部分是重叠的,才能这样回退。

⽤⽂字可能很难描述,我们直接拿上⾯的来举例。

j = 4的时候,这个B前⾯的A和第⼀个A是相同的,因此next[4]=2,这个2是怎么来的呢?这个2不是因为j=2的那个B,⽽是因为j=1的那个A,因为j=1的A和j=3的A相匹配了,因此next[4] = 1 + 1,这才等于的2。

等号右边第⼀个1是j=1的1,第⼆个1是往右边⾛⼀位的1。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
max{k|0<k<j,且“t0t1…tk-1”=“tj-ktj-k+1…tj-1” }
当此集合非空时
next[j]= -1 0 当j=0时 其他情况
t=“abab”对应的next数组如下:
j t[j] next[j] 0 a -1 1 b 0 2 a 0 3 b 1
void GetNext(SqString t,int next[]) { int j,k; j=0;k=-1;next[0]=-1; while (j<t.len-1)
既然如此,回溯到si-j+1开始与t匹配可以不做。那 么,回溯到si-j+2 开始与t匹配又怎么样?从上面推理 可知,如果 "t0t1…tj-2"≠"t2t3…tj"
仍然有
"t0t1…tj-2"≠"si-j+2si-j+3…si"
这样的比较仍然“失配”。依此类推,直到对于 某一个值k,使得: "t0t1…tk-2"≠" tj-k+1tj-k+2…tj-1"
b 3
第 1 次匹配
第 2 次匹配
s=aaabaaaa b t=aaaab
第 3 次匹配
s=aaabaaaa b t=aaaab
第 4 次匹配
s=aaabaaaa b t=aaaab
第 5 次匹配
s=aaabaaaa b t=aaaab
上述定义的next[]在某些情况下尚有缺陷。 例如,模式“aaaab”在和主串“aaabaaaab”匹配时, 当i=3,j=3时,s.data[3]≠t.data[3],由next[j]的指示还需 进行i=3、j=2,i=3、j=1,i=3、j=0等三次比较。实际上, 因为模式中的第1、2、3个字符和第4个字符都相等, 因此,不需要再和主串中第4个字符相比较,而可以将模 式一次向右滑动4个字符的位置直接进行i=4,j=0时的 字符比较。
第 1 次匹配 s=a b a c a b a b t=a b a b i=3 j=3 失败
此时不必从i=1(i=i-j+1=1),j=0重新开始第二次匹 配。因t0≠t1,s1=t1,必有s1≠t0,又因t0 =t2,s2=t2,所以必有 s2=t0。因此,第二次匹配可直接从i=3,j=1开始。
为此,定义next[j]函数如下:
else v=-1; return v; } /*返回不匹配标志*/
设主串s的长度为n,子串t长度为m。
在 KMP 算 法 中 求 next 数 组 的 时 间 复 杂 度 为
O(m),在后面的匹配中因主串s的下标不减即不回溯,
比较次数可记为n,所以KMP算法总的时间复杂度为
O(n+m)。
例如,设目标串s=“aaabaaaab”,模式串t=“aaaab”。
4.3.2
KMP算法
KMP算法是D.E.Knuth、J.H.Morris和V.R.Pratt
共同提出的,简称KMP算法。该算法较BF算法有较 大改进,主要是消除了主串指针的回溯,从而使算法效 率有了某种程度的提高。
所谓真子串是指模式串t存在某个k(0<k<j),使 得"t0t1…tk " = " tj-ktj-k+1…tj "成立。
这时,应有 "t0t1…tj-1"="si-jsi-j+1…si-1" (4.1) 如果在模式t中,
"t0t1…tj-1"≠"t1t2…tj"
(4.2)
则回溯到si-j+1 开始与t匹配,必然“失配”,理由 很简单:由(4.1)式和(4.2)式综合可知:
"t0t1…tj-1"≠"si-j+1si-j+2…si"
这就是说,若按上述定义得到next[j]=k,而模式中
pj=pk,则为主串中字符si和pj比较不等时,不需要再和pk
进行比较,而直接和pnext[k]进行比较,换句话说,此时的 next[j]应和next[k]相同。 为此将next[j]修正为nextval[j]:
比较t.data[j]和t.data[k],若不等,则 nextval[j]=next[j]; 若相等nextval[j]=nextval[k];
t: t0 t1 … tk-1 tk … tj-1 tj tj+1 … sm-1
说明下一次可直接比较si和tk,这样,我们可以 直接把第i趟比较“失配”时的模式t从当前位置直 接右滑j-k位。而这里的k即为next[j]。
例如t="abab",由于"t0t1" ="t2t3"(这里k=1,j=3),则 存在真子串。设s="abacabab",t="abab",第一次匹 配过程如下所示。
else nextval[j]=nextval[k];
int KMPIndex1(SqString s,SqString t) { int nextval[MaxSize],i=0,j=0,v; GetNextval(t,nextval); while (i<s.len && j<t.len) { if (j==-1 || s.data[i]==t.data[j]) { i++;j++; } else j=nextval[j]; /*i,j各增1*/ /*i不变,j后退*/ 修改后的KMP 算法
i=3 j=3,j=nextval[3]=-1 i=9 j=5,返回 9-5=4
失败
第 2 次匹配
s=aaabaaaa b t=aaaab
成功
}
if (j>=t.len) v=i-t.len; /*返回匹配模式串的首字符下标*/ else v=-1; return v; } /*返回不匹配标志*/
j
0
1
2
3
4
t[j] next[j] nextval[j]
第 1 次匹配
a -1 -1
a 0 -1
a 1 -1
a 2 -1
b 3 3
s=aaabaaaa b t=aaaab
例如,t= "abab",
即t0t1=t2t3 也就是说, “ab”是真子串。 真子串就是模式串中隐藏的信息,利用它来提高 模式匹配的效率。
一般情况:设主串s="s0s1…sn-1",模式t="t0t1…tm-1", 在进行第i趟匹配时,出现以下情况:
s: s0 s1 … si-j si-j+1 … si-1 si si+1 … sn-1 t: t0 t1 … tj-1 tj tj+1 … sm-1
void GetNextval(SqString t,int nextval[]) { int j=0,k=-1;
nextval[0]=-1;
由模式串t求 while (j<t.len) 出nextval值 { if (k==-1 || t.data[j]==t.data[k]) { j++;k++; (t.data[j]!=t.data[k]) if nextval[j]=k; } else k=nextval[k]; } }
由模式串t 求 出 next 值 的算法
{ if (k==-1 || t.data[j]==t.data[k])
/*k为-1或比较的字符相等时*/ { } else k=next[k]; j++;k++;
next[j]=k;
}
}
int KMPIndex(SqString s,SqString t) { int next[MaxSize],i=0,j=0,v; GetNext(t,next); KMP算法
while (i<s.len && j<t.len)
{ if (j==-1 || s.data[i]==t.data[j]) { i++;j++; } else j=next[j]; } /*i,j各增1*/ /*i不变,j后退*/
if (j>=t.len) v=i-t.len; /*返回匹配模式串的首字符下标*/

"t0t1…tk-1"="tj-ktj-k+1…tj-1“ 才有
"tj-ktj-k+1…tj-1"="si-ksi-k+1…si-1"="t0t1…tk-1"
s: s0 s1 … si-j si-j+1 … si-k si-k+1 … si-1
si si+1 … sn-1
t: t0 t1 … tk-1 tk … tj-1 tj tj+1 … sm-1 t 右滑 j-k 位 s: s0 s1 … si-j si-j+1 … si-k si-k+1 … si-1 si si+1 … sn-1
s的长度为n(n=9),t的长度为m(m=5)。用指针i指示目
标串s的当前比较字符位置,用指针j指示模式串t的当
前比较字符位置。KMP模式匹配过程如下所示。
j
0
1
2
3
4t[j] next[j]a -1a 0
s=aaabaaaa b t=aaaab
a 1
i=3
a 2
失败 j=3,j=next[3]=2 i=3 j=2,j=next[2]=1 i=3 j=1,j=next[1]=0 i=3 j=0,j=next[0]=-1 i=9 j=5,返回 10-5=4 成功 失败 失败 失败
相关文档
最新文档