[算法系列之十四]字符串匹配之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

相关文章

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

Java中字符串转时间与时间转字符串的操作详解

《Java中字符串转时间与时间转字符串的操作详解》Java的java.time包提供了强大的日期和时间处理功能,通过DateTimeFormatter可以轻松地在日期时间对象和字符串之间进行转换,下面... 目录一、字符串转时间(一)使用预定义格式(二)自定义格式二、时间转字符串(一)使用预定义格式(二)自

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

一文详解如何在Python中从字符串中提取部分内容

《一文详解如何在Python中从字符串中提取部分内容》:本文主要介绍如何在Python中从字符串中提取部分内容的相关资料,包括使用正则表达式、Pyparsing库、AST(抽象语法树)、字符串操作... 目录前言解决方案方法一:使用正则表达式方法二:使用 Pyparsing方法三:使用 AST方法四:使用字

Java字符串处理全解析(String、StringBuilder与StringBuffer)

《Java字符串处理全解析(String、StringBuilder与StringBuffer)》:本文主要介绍Java字符串处理全解析(String、StringBuilder与StringBu... 目录Java字符串处理全解析:String、StringBuilder与StringBuffer一、St

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

MySQL更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -