[算法系列之十四]字符串匹配之Morris-Pratt字符串搜索算法

2024-03-22 11:10

本文主要是介绍[算法系列之十四]字符串匹配之Morris-Pratt字符串搜索算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

我们前面已经看到,蛮力字符串匹配算法和Rabin-Karp字符串匹配算法均非有效算法。不过,为了改进某种算法,首先需要详细理解其基本原理。我们已经知道,暴力字符串匹配的速度缓慢,并已尝试使用Rabin-Karp中的一个散列函数对其进行改进。问题是,Rabin-Karp的复杂度与强力字符串匹配相同,均为O(mn)。

我们显然需要采用一种不同方法,但为了提出这种不同方法,先来看看暴力字符串匹配有什么不妥之处。事实上,再深入地研究一下它的基本原理,就能找到问题的答案了。

在暴力匹配算法中,需要检查文本串中的每个字符是否与模式串的第一个字符匹配。如果匹配,就顺次比较模式串的第二个字符,判断是否与文本串的下一字符匹配。问题在于,当出现失配时,我们必须要在文本串中回退若干位置。嗯,这种方法事实上是无法优化的。

 

 

在暴力字符串匹配算法中,若出现失配,必须回退,并匹配已经匹配过的字符!

我们可以从上图可以看出问题所在:一旦出现失配,必须回退,从文本串中一个已经考察过的位置开始比较。在我们的例子中,我们已经检查了第一、二、三、四个字符,此时模式串与文本串之间出现失配,于是……于是我们就不得不得回退回去,从文本串的第二个字符重新开始比较。

这一过程显然没有任何作用,因为我们已经知道模式串从字符“a”起始,并且在位置1与位置3之间没有这一字符。那我们如何改善这种不必要的重复呢?

概述

James H. Morris和Vaughan Pratt在1977年回答了这一问题,并且对自己的算法进行了介绍,这种算法会跳过大量无用比较,所以其效率高于暴力字符串匹配。我们来详细地研究一下。唯一事情就是:利用在对模式串与可能匹配进行对比期间收集的信息(The only thing is to use the information gathered during the comparisons of the pattern and a possible match),如下图所示。

 

Morris-Pratt向前移动到下一可能匹配位置,从而跳过一些不必要的比较!

我们首先需要做的就是必须对模式串进行预处理,以获取后续匹配的可能位置。下一步,开始查找可能的匹配位置,在发生失配的情况下,我们可以准确地知道应当跳转到何处,从而跳过那些没有任何用处的比较。

 

生成后续对比位置表格

这是Morris-Pratt算法中最富有技巧性的地方,也是这种算法如何克服暴力字符串匹配算法缺陷的重要步骤。让我们来看几张图片。

 

很显然,如果模式串中仅包含不同字符,在发生失配时,我们应当将文本串中的下一字符与模式串的第一字符进行比较!

然而,当模式串中存在重复字符情况时,如果在该字符之后出现失配,则必须从这一重复字符开始查找可能的匹配,如下图所示。

 

如果模式串中包含重复字符,则“下一位置”表格会稍有不同!

最后,如果文本串中的重复字符不止1个,“下一个”表格将会给出其位置。

有了这个包含“后续”可能位置的表格之后,就可以开始在文本串中查找模式串了。

 

实现

Morris-Pratt算法的实现并不困难。首先,必须对模式串进行预处理,然后执行搜索。原文是使用PHP实现的,在这我们使用c++实现。

/*--------------------------------
*   日期:2015-02-05
*   作者:SJF0115
*   题目: 字符串匹配之Morris-Pratt匹配算法
*   博客:
------------------------------------*/
#include <iostream>
using namespace std;// 预处理
void PreprocessMorrisPratt(string patttern,int nextTable[]){int i = 0;int j = nextTable[0] = -1;int size = patttern.size();while(i < size){while(j > -1 && patttern[i] != patttern[j]){j = nextTable[j];}//whilenextTable[++i] = ++j;}//while
}int SubString(string text,string pattern){int m = pattern.size();int n = text.size();int nextTable[m+1];// 预处理PreprocessMorrisPratt(pattern,nextTable);int i = 0,j = 0;while( j < n){while(i > -1 && pattern[i] != text[j]){i = nextTable[i];}//whilei++;j++;if(i >= m){return j  - i;}//if}//whilereturn -1;
}int main(){string text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eleifend nisi viverra ipsum elementum porttitor quis at justo. Aliquam ligula felis, dignissim sit amet lobortis eget, lacinia ac augue. Quisque nec est elit, nec ultricies magna. Ut mi libero, dictum sit amet mollis non, aliquam et augue!");string pattern("mollis");int result = SubString(text,pattern);// 275cout<<"下标位置->"<<result<<endl;return 0;
}

复杂度

这一算法需要一定的时间和空间进行预处理。模式串的预处理可以在O(m)内完成,其中m为模式串的长度,而搜索本身需要O(m+n)。好消息是预处理过程只需要完成一次,然后就可以根据需要执行任意次搜索了!

下面的图表给出了5字母模式串的O(n+m)复杂度,并将其与O(nm)进行对比。

 

应用

优点

其搜索复杂度为O(m+n),快于强力算法和Rabin-Karp算法 
其实现相当容易

缺点

需要额外的空间与时间-O(m)进行预处理 
可以稍加优化(Knuth-Morris-Pratt)

 

应用

优点

其搜索复杂度为O(m+n),快于强力算法和Rabin-Karp算法 
其实现相当容易

缺点

需要额外的空间与时间-O(m)进行预处理 
可以稍加优化(Knuth-Morris-Pratt)

 

 

本文原文地址:https://blog.csdn.net/SunnyYoona/article/details/43560619

这篇关于[算法系列之十四]字符串匹配之Morris-Pratt字符串搜索算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/835178

相关文章

C#数据结构之字符串(string)详解

《C#数据结构之字符串(string)详解》:本文主要介绍C#数据结构之字符串(string),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录转义字符序列字符串的创建字符串的声明null字符串与空字符串重复单字符字符串的构造字符串的属性和常用方法属性常用方法总结摘

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

python中字符串拼接的几种方法及优缺点对比详解

《python中字符串拼接的几种方法及优缺点对比详解》在Python中,字符串拼接是常见的操作,Python提供了多种方法来拼接字符串,每种方法有其优缺点和适用场景,以下是几种常见的字符串拼接方法,需... 目录1. 使用 + 运算符示例:优缺点:2. 使用&nbsjsp;join() 方法示例:优缺点:3

java字符串数字补齐位数详解

《java字符串数字补齐位数详解》:本文主要介绍java字符串数字补齐位数,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java字符串数字补齐位数一、使用String.format()方法二、Apache Commons Lang库方法三、Java 11+的St

C++字符串提取和分割的多种方法

《C++字符串提取和分割的多种方法》在C++编程中,字符串处理是一个常见的任务,尤其是在需要从字符串中提取特定数据时,本文将详细探讨如何使用C++标准库中的工具来提取和分割字符串,并分析不同方法的适用... 目录1. 字符串提取的基本方法1.1 使用 std::istringstream 和 >> 操作符示

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

Java反转字符串的五种方法总结

《Java反转字符串的五种方法总结》:本文主要介绍五种在Java中反转字符串的方法,包括使用StringBuilder的reverse()方法、字符数组、自定义StringBuilder方法、直接... 目录前言方法一:使用StringBuilder的reverse()方法方法二:使用字符数组方法三:使用自

Golang中拼接字符串的6种方式性能对比

《Golang中拼接字符串的6种方式性能对比》golang的string类型是不可修改的,对于拼接字符串来说,本质上还是创建一个新的对象将数据放进去,主要有6种拼接方式,下面小编就来为大家详细讲讲吧... 目录拼接方式介绍性能对比测试代码测试结果源码分析golang的string类型是不可修改的,对于拼接字