字符串查找匹配算法

概述

字符串匹配(查找)是字符串的一种基本操作:给定带匹配查询的文本串S和目标子串T,T也叫做模式串。在文本S中找到一个和模式T相符的子字符串,并返回该子字符串在文本中的位置。

暴力匹配

Brute Force Algorithm,也叫朴素字符串匹配算法,Naive String Matching Algorithm。
基本思路就是将字符一个一个地进行比较:

  • 如果S和T两个字符串的第一个字符相同就比较第二个字符,如果相同就一直继续;
  • 如果其中有某一个字符不同,则将T字符串向后移一位,将S字符串的第二个字符与T的字符串的第一个字符重新开始比较。
  • 循环往复,一直到结束

实现

public static int bf(String text, String pattern) {int m = text.length();int n = pattern.length();for (int i = 0; i <= m - n; i++) {boolean flag = true;for (int j = 0; j < n; j++) {if (text.charAt(i + j) != pattern.charAt(j)) {flag = false;break;}}if (flag) {return i;}}return -1;
}

KMP

D.E.Knuth,J.H.Morris 和 V.R.Pratt发明,一种字符串匹配的改进算法,利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。KMP算法只需要对文本串搜索一次,时间复杂度是O(n)

KMP 算法的关键是求 next 数组。next 数组的长度为模式串的长度。next 数组中每个值代表模式串中当前字符前面的字符串中,有多大长度的相同前缀后缀。

原理

暴力匹配
给定字符串A和B,判断B是否是A的子串。暴力匹配的方法:从A的第一个字符开始,比较A的第一个字符和B的第一个字符是否相同,相同则比较A的第二个字符和B的第二个字符,不相同,则从A的第二个字符开始,与B的第一个字符开始比较。以此类推。相当于一步步右移B的动作。

暴力匹配,效率低,体现在一步步移动,尤其是在B的前n-1个字符都能匹配成功,最后一个字符匹配失败,或B子串较长的情况下。KMP利用匹配表的概念来优化匹配效率。

匹配表
对于给定的字符串B,如abcab

  • 前缀:除了最后个字符外,所有的顺序组合方式:a、ab、abc、abca
  • 后缀:除了第一个字符外,所有的顺序组合方式:bcab、cab、ab、b

匹配值,对子串的每个字符组合寻找出前缀和后缀,比较是否有相同的,相同的字符组合有几位,匹配值就是几。

比如针对给定的字符串abcab,其匹配值字符串为00012

  • a,无前后缀,匹配值=0
  • ab,前缀{a},后缀{b},无共同字符,匹配值=0
  • abc,前缀{a}{ab},后缀{c}{bc},无共同字符,匹配值=0
  • abca,前缀{a}{ab}{abc},后缀{a}{ca}{bca},共同字符{a},匹配值=1
  • abcab,前缀{a}{ab}{abc}{abca},后缀{b}{ab}{cab}{bcab},共同字符{ab},匹配值=2

基于匹配表,不需要一个个移动,移动步数 = 成功匹配的位数 - 匹配表里面的匹配值

实现

给出一个KMP算法实现:

/*** 利用KMP算法求解pattern是否在text中出现过** @param text    文本串* @param pattern 模式串* @return pattern在text中出现,则返回true,否则返回false*/
public static boolean kmpSearch(String text, String pattern) {// 部分匹配数组int[] partMatchTable = kmpNext(pattern);// text中的指针int i = 0;// pattern中的指针int j = 0;while (i < text.length()) {if (text.charAt(i) == pattern.charAt(j)) {// 字符匹配,则两个指针同时后移i++;j++;} else if (j > 0) {// 字符失配,则利用next数组,移动j指针,避免i指针回退j = partMatchTable[j - 1];} else {// pattern中的第一个字符就失配i++;}if (j == pattern.length()) {// 搜索成功return true;}}return false;
}private static int[] kmpNext(String pattern) {int[] next = new int[pattern.length()];next[0] = 0;int j=0;for (int i = 1; i < pattern.length(); i++) {while (j > 0 && pattern.charAt(i) != pattern.charAt(j)){//前后缀相同j = next[j - 1];}if (pattern.charAt(i) == pattern.charAt(j)){//前后缀不相同j++;}next[i] = j;}return next;
}

Boyer-Moore

Boyer-Moore 算法在实际应用中比 KMP 算法效率高,据说各种文本编辑器的查找功能,包括Linux 里的 grep 命令,都是采用 Boyer-Moore 算法。该算法有坏字符好后缀两个概念,字符串从后往前匹配。 一般情况下,比KMP算法快3-5倍。

原理

假设文本串S长度为n,模式串T长度为m,BM算法的主要特征为:

  • 从右往左进行比较匹配(一般的字符串搜索算法如KMP都是从左往右进行匹配);
  • 算法分为两个阶段:预处理阶段和搜索阶段;
  • 预处理阶段时间和空间复杂度都是是O(m+),是字符集大小,一般为256;
  • 搜索阶段时间复杂度是O(mn)
  • 当模式串是非周期性的,在最坏的情况下算法需要进行3n次字符比较操作;
  • 算法在最好的情况下达到O(n/m),即只需要n/m次比较。

而BM算法在移动模式串的时候是从左到右,而进行比较的时候是从右到左的。

BM算法的精华就在于BM(text, pattern),BM算法当不匹配的时候一次性可以跳过不止一个字符。即它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。即它充分利用待搜索字符串的一些特征,加快搜索的步骤。

BM算法包含两个并行的算法(也就是两个启发策略):坏字符算法(bad-character shift)和好后缀算法(good-suffix shift)。这两种算法的目的就是让模式串每次向右移动尽可能大的距离(即BM()尽可能大)。

实现

/*** Boyer-Moore算法是一种基于后缀匹配的模式串匹配算法,后缀匹配就是模式串从右到左开始比较,但模式串的移动还是从左到右的。* 字符串匹配的关键就是模式串的如何移动才是最高效的,Boyer-Moore为了做到这点定义了两个规则:坏字符规则和好后缀规则<br>* 坏字符规则<bR>* 1.如果坏字符没有出现在模式字符中,则直接将模式串移动到坏字符的下一个字符:<br>* 2.如果坏字符出现在模式串中,则将模式串最靠近好后缀的坏字符(当然这个实现就有点繁琐)与母串的坏字符对齐:<br>* 好后缀规则<bR>* 1.模式串中有子串匹配上好后缀,此时移动模式串,让该子串和好后缀对齐即可,如果超过一个子串匹配上好后缀,则选择最靠靠近好后缀的子串对齐。<br>* 2.模式串中没有子串匹配上后后缀,此时需要寻找模式串的一个最长前缀,并让该前缀等于好后缀的后缀,寻找到该前缀后,让该前缀和好后缀对齐即可。<br>* 3.模式串中没有子串匹配上后后缀,并且在模式串中找不到最长前缀,让该前缀等于好后缀的后缀。此时,直接移动模式到好后缀的下一个字符。<br>*/
public static List<Integer> bmMatch(String text, String pattern) {List<Integer> matches = new ArrayList<>();int m = text.length();int n = pattern.length();// 生成模式字符串的坏字符移动结果Map<Character, Integer> rightMostIndexes = preprocessForBadCharacterShift(pattern);// 匹配的节点位置int alignedAt = 0;// 如果当前节点在可匹配范围内,即当前的A[k]必须在A[0, m-n-1)之间,否则没有必要做匹配while (alignedAt + (n - 1) < m) {// 循环模式组,查询模式组是否匹配 从模式串的最后面开始匹配,并逐渐往前匹配for (int indexInPattern = n - 1; indexInPattern >= 0; indexInPattern--) {// 1 定义待查询字符串中的当前匹配位置.int indexInText = alignedAt + indexInPattern;// 2 验证带查询字符串的当前位置是否已经超过最长字符,如果超过,则表示未查询到.if (indexInText >= m) {break;}// 3 获取到带查询字符串和模式字符串中对应的待匹配字符char x = text.charAt(indexInText);char y = pattern.charAt(indexInPattern);// 4 验证结果if (x != y) {// 4.1 如果两个字符串不相等,则寻找最坏字符串的结果,生成下次移动的队列位置Integer r = rightMostIndexes.get(x);if (r == null) {alignedAt = indexInText + 1;} else {// 当前坏字符串在模式串中存在,则将模式串最靠近好后缀的坏字符与母串的坏字符对齐,shift 实际为模式串总长度int shift = indexInText - (alignedAt + r);alignedAt += shift > 0 ? shift : 1;}// 退出匹配break;} else if (indexInPattern == 0) {// 4.2 匹配到的话 并且最终匹配到模式串第一个字符,便是已经找到匹配串,记录下当前的位置matches.add(alignedAt);alignedAt++;}}}return matches;
}/*** 坏字符串* 依据待匹配的模式字符串生成一个坏字符串的移动列,该移动列中表明当一个坏字符串出现时,需要移动的位数*/
private static Map<Character, Integer> preprocessForBadCharacterShift(String pattern) {Map<Character, Integer> map = new HashMap<>();for (int i = pattern.length() - 1; i >= 0; i--) {char c = pattern.charAt(i);if (!map.containsKey(c)) {map.put(c, i);}}return map;
}

参考:百度百科

Sunday

Daniel M.Sunday 于 1990 年提出的字符串模式匹配算法。其效率在匹配随机的字符串时比其他匹配算法更快。平均时间复杂度为O(n),最差情况的时间复杂度为O(n*m)

原理

Sunday 算法跟 KMP 算法一样,是从前往后匹配。在匹配失败时,关注文本串中参加匹配的最末位字符的下一位字符,如果该字符不在模式串中,则整个模式串移动到该字符之后。如果该字符在模式串中,将模式串右移使对应的字符对齐。

Sunday算法和BM算法稍有不同的是,Sunday算法是从前往后匹配,在匹配失败时关注的是主串中参加匹配的最末位字符的下一位字符。

  • 如果该字符没有在模式串中出现则直接跳过,即移动位数 = 模式串长度 + 1;
  • 否则,其移动位数 = 模式串长度 - 该字符最右出现的位置(以0开始) = 模式串中该字符最右出现的位置到尾部的距离 + 1。

举例说明Sunday算法。假定现在要在主串substring searching中查找模式串search

  • 刚开始时,把模式串与文主串左边对齐:
    在这里插入图片描述
  • 结果发现在第2个字符处发现不匹配,不匹配时关注主串中参加匹配的最末位字符的下一位字符,即标粗的字符i,模式串search中并不存在i,模式串直接跳过一大片,向右移动位数 = 匹配串长度 + 1 = 6 + 1 = 7,从 i 之后的那个字符(即字符n)开始下一步的匹配:
    在这里插入图片描述
  • 结果第一个字符就不匹配,再看主串中参加匹配的最末位字符的下一位字符r,它出现在模式串中的倒数第3位,于是把模式串向右移动3位(m - 3 = 6 - 3 = r 到模式串末尾的距离 + 1 = 2 + 1 =3),使两个r对齐:
    在这里插入图片描述
  • 匹配成功。

Sunday算法的缺点:算法核心依赖于move数组,而move数组的值则取决于模式串,那么就可能存在模式串构造出很差的move数组。

实现

/*** sunday 算法** @param text    文本串* @param pattern 模式串* @return 匹配失败返回-1,匹配成功返回文本串的索引(从0开始)*/
public static int sunday(char[] text, char[] pattern) {int tSize = text.length;int pSize = pattern.length;int[] move = new int[ASCII_SIZE];// 主串参与匹配最末位字符移动到该位需要移动的位数for (int i = 0; i < ASCII_SIZE; i++) {move[i] = pSize + 1;}for (int i = 0; i < pSize; i++) {move[pattern[i]] = pSize - i;}// 模式串头部在字符串位置int s = 0;// 模式串已经匹配的长度int j;// 到达末尾之前while (s <= tSize - pSize) {j = 0;while (text[s + j] == pattern[j]) {j++;if (j >= pSize) {return s;}}s += move[text[s + pSize]];}return -1;
}

对比

仅做示例用,不同的算法在不同情况下表现不一致。与待搜索的字符串文本和模式匹配字符串有关。

public static void main(String[] args) {String text = "abcagfacjkackeac";String pattern = "ackeac";Stopwatch stopwatch = Stopwatch.createStarted();int bfRes = bf(text, pattern);stopwatch.stop();log.info("bf result:{}, take {}ns", bfRes, stopwatch.elapsed(TimeUnit.NANOSECONDS));stopwatch.reset();stopwatch.start();boolean kmpRes = kmpSearch(text, pattern);stopwatch.stop();log.info("kmp result:{}, take {}ns", kmpRes, stopwatch.elapsed(TimeUnit.NANOSECONDS));stopwatch.reset();stopwatch.start();List<Integer> bmMatch = bmMatch(text, pattern);stopwatch.stop();log.info("bmMatch result:{}, take {}ns", bmMatch, stopwatch.elapsed(TimeUnit.NANOSECONDS));stopwatch.reset();stopwatch.start();int sunday = sunday(text.toCharArray(), pattern.toCharArray());stopwatch.stop();log.info("sunday result:{}, take {}ns", sunday, stopwatch.elapsed(TimeUnit.NANOSECONDS));
}

某次输出结果:

bf result:10, take 8833ns
kmp result:true, take 4541ns
bmMatch result:[10], take 90500ns
sunday result:10, take 5458ns

测试结果仅供参考。

参考

  • 动画解释KMP算法

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

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

相关文章

安全狗V3.512048版本绕过

安全狗安装 安全狗详细安装、遇见无此服务器解决、在windows中命令提示符中进入查看指定文件夹手动启动Apache_安全狗只支持 glibc_2.14 但是服务器是2.17_黑色地带(崛起)的博客-CSDN博客 安全狗 safedogwzApacheV3.5.exe 右键电脑右下角安全狗图标-->选择插件-->安装…

vscode中无法使用git解决方案

1 首先查看git安装目录 where git 2 找到bash.exe 的路径 比如&#xff1a;C:/Users/Wangzd/AppData/Local/Programs/Git/bin/bash 3 找到vscode的配置项setting.json 4 添加 "terminal.integrated.shell.windowns": "C:/Users/Wangzd/AppData/Local/Pr…

架构训练营学习笔记:6-2 微服务基础选型

基础选型 微服务基础设施架构 优先级 其中&#xff0c;核心 就是服务注册、服务发现、服务路由。 模式1-嵌入SDK 模式2-反向代理式 模式3-网络代理式&#xff08;Service Mesh&#xff09; 模式对比 常见微服务框架选择 嵌入SDK-dubbo Spring Cloud 反向代理式 APISIX …

跨境B2B2C多用户购物网站源码快速部署

​ 搭建跨境B2B2C多用户购物网站需要以下步骤&#xff1a; 1. 确定业务模式和定位&#xff1a;确定网站的业务模式&#xff0c;包括跨境B2B2C的商业模式以及目标用户定位。 2. 营业执照和域名注册&#xff1a;根据当地法律要求&#xff0c;注册一家具有法人资格的公司&#xff…

基于Citespace、vosviewer、R语言的文献计量学可视化分析技术及全流程文献可视化SCI论文高效写作方法

跨尺度预测模式&#xff08;The Model for Prediction Across Scales - MPAS&#xff09;是由洛斯阿拉莫斯实验室和美国国家大气研究中心(NCAR)共同开发&#xff0c;其由3个部分组成&#xff0c;分别称为 MPAS-A&#xff08;大气模型&#xff09;、MPAS-O&#xff08;海洋模型&…

一百四十一、Kettle——kettle8.2在Windows本地开启carte服务以及配置子服务器

一、目的 在kettle建好共享资源库后&#xff0c;为了给在服务器上部署kettle的carte服务躺雷&#xff0c;先在Windows本地测试一下怎么玩carte服务 二、Kettle版本以及在Windows本地安装路径 kettle版本是8.2 pdi-ce-8.2.0.0-342 kettle本地安装路径是D:\j…

全面讲解最小二乘法

常见的最小二乘法我们就不多说了&#xff0c;下面主要介绍一下最小二乘法的一些先进方法。 正则化的最小二乘法 在使用常见的最小二乘法进行回归分析时&#xff0c;常常会遇到过拟合的问题&#xff0c;也就是在训练数据集上表现的很好&#xff0c;但是在测试数据集上表现的很…

【Maven】常用命令、插件管理、私服nexus

【Maven】常用命令、插件管理、私服nexus 常用命令 插件管理 私服nexus Nexus3 配置私服 项目pom中的配置 发布时区分正式版、快照版 常用命令 Maven提供了一系列常用命令&#xff0c;用于构建、测试和管理项目。以下是一些常用的Maven命令示例&#xff1a; mvn clean:…

Cadence学习

Cadence学习 Cadence内容涵盖Cadence主要功能Cadence功能模块Allegro Design Entry CIS 和 OrCAD Capture CIS 的区别Cadence 公司简介Allegro Design Entry CISOrCAD Capture CIS OrCAD中part和database part区别OrCAD中不同页面的连接关系应该怎么处理&#xff08;1&#xff…

【Unity3D】消融特效

1 前言 选中物体消融特效中基于 Shader 实现了消融特效&#xff0c;本文将基于 Shader Graph 实现消融特效&#xff0c;两者原理一样&#xff0c;只是表达方式不同&#xff0c;另外&#xff0c;选中物体消融特效中通过 discard 丢弃片元&#xff0c;本文通过 alpha 测试丢弃片元…

【华秋推荐】物联网入门学习模块 ESP8266

随着全球信息技术的不断进步和普及&#xff0c;物联网成为当今备受关注的技术热点之一。通过物理和数字设备之间的连接来实现自动化和互联互通的网络。无线传感器、云计算和大数据分析等技术&#xff0c;物联网使设备能够相互交流和共享信息&#xff0c;实现智能化的自动化操作…

RocketMQ第二课-核心编程模型以及生产环境最佳实践

一、回顾RocketMQ的消息模型 ​ 上一章节我们从试验整理出了RocketMQ的消息模型&#xff0c;这也是我们使用RocketMQ时最直接的指导。 二、深入理解RocketMQ的消息模型 1、RocketMQ客户端基本流程 <dependency><groupId>org.apache.rocketmq</groupId>&…

数据结构 | 搜索和排序——搜索

目录 一、顺序搜索 二、分析顺序搜索算法 三、二分搜索 四、分析二分搜索算法 五、散列 5.1 散列函数 5.2 处理冲突 5.3 实现映射抽象数据类型 搜索是指从元素集合中找到某个特定元素的算法过程。搜索过程通常返回True或False&#xff0c;分别表示元素是否存在。有时&a…

gradle项目Connection timed out,build时先下载gradle问题download gradle-x.x-bin.zip

IDEA 导入 Gradle 项目&#xff0c;编译的时候会默认下载 配置版本的Gradle.zip问题&#xff0c;一般会下载失败&#xff0c;提示Connection timed out&#xff0c;连接超时。 解决办法&#xff1a; 修改项目根目录下gradle目录下的gradle-wrapper.properties文件&#xff0c;…

Kafka3.0.0版本——生产者如何提高吞吐量

目录 一、生产者提高吞吐量参数设置二、产者提高吞吐量代码示例 一、生产者提高吞吐量参数设置 batch.size&#xff1a;设置批次大小&#xff0c;默认16klinger.ms&#xff1a;设置等待时间&#xff0c;修改为5-100msbuffer.memory&#xff1a;设置缓冲区大小&#xff0c; 默认…

JGJ80-2016建筑施工高处作业安全技术规范

为规范建筑施工高处作业及其管理&#xff0c;做到防护安全、技术先进、经济合理&#xff0c;制定本规范。 本规范适用于建筑工程施工高处作业中的临边、洞口攀登、悬空、操作平台、交叉作业及安全网搭设等项作业。 本规范亦适用于其他高处作业的各类洞、坑、沟、槽等部位的施…

高性能计算集群使用

一、PuTTY的下载与安装 PuTTY是一款开源的连接软件&#xff0c;是 SSH、Telnet、Rlogin 和 SUPDUP 网络协议的客户端程序。 下载网址&#xff1a;Download PuTTY - a free SSH and telnet client for Windows 安装好后连接自己的服务器 输入用户名和密码&#xff0c;回车登录…

一些不错的VSCode设置和插件

设置 同步设置 我们做的各项设置&#xff0c;不希望再到其他机器的时候还得再重新配置一次。VSCode中我们可以登陆微软账号或者GitHub账号&#xff0c;登陆后我们可以开启同步设置。开启设置同步&#xff0c;根据提示登陆即可。 允许侧边栏水平滑动 在目录层次较深或者文件…

Docker-Compose编排与部署

目录 Docker Compose Compose的优点 编排和部署 Compose原理 Compose应用案例 安装docker-ce 阿里云镜像加速器 安装docker-compose docker-compose用法 Yaml简介 验证LNMP环境 Docker Compose Docker Compose 的前身是 Fig&#xff0c;它是一个定义及运行多个 Dock…

langchain-ChatGLM源码阅读:参数设置

文章目录 上下文关联对话轮数向量匹配 top k控制生成质量的参数参数设置心得 上下文关联 上下文关联相关参数&#xff1a; 知识相关度阈值score_threshold内容条数k是否启用上下文关联chunk_conent上下文最大长度chunk_size 其主要作用是在所在文档中扩展与当前query相似度较高…