文章目录

  • BF
    • 思路
    • 实现
  • RK
    • 思路
    • 实现

字符串匹配是计算机科学中最古老、研究最广泛的问题之一。一个字符串是一个定义在有限字母表∑上的字符序列。例如,ATCTAGAGA是字母表∑ = {A,C,G,T}上的一个字符串。字符串匹配问题就是在一个大的字符串T中搜索某个字符串P的所有出现位置。

为了方便后面讲解,在这里先提出几个概念。

  • 主串和模式串主串即字符串的本体,模式串即用于比对的子串,即我们将要在主串中查找模式串。例如我们要在字符串A中查找字符串B,则A为主串,B为模式串。
  • 单模式匹配算法即在一个主串中匹配一个模式串,例如BF、RK、BM、KMP等算法就是单模式
  • 多模式匹配算法即在一个主串中匹配多个字符串,例如之前讲过的Trie树,以及之后会写的AC自动机。

BF

思路

BF算法是Brute Force的缩写,中文名叫做暴力匹配算法,故名思意,其算法简单粗暴,但对应的性能也不高,算法逻辑如下。

如上图,BF算法的思路就是从主串的每一个位置进行对比,如果当前位置模式串无法匹配,就移动到下一个位置,继续匹配

实现

该算法的实现十分简单,代码如下

#include<string>
#include<iostream>using namespace std;int bruteForce(const string& str, const string& pattern)
{//不满足条件则直接返回falseif(str.empty() || pattern.empty() || str.size() < pattern.size()){return -1;}int i = 0, j = 0;int len1 = str.size(), len2 = pattern.size();while(i < str.size()){while(j < pattern.size()){//如果当前不匹配if(str[i + j] != pattern[j]){i++;    //主串移动到下一个位置j = 0;  //模式串回到起始位置break;}j++;    //如果模式串当前位置匹配,则继续匹配下一个位置}//如果模式串全部匹配,则返回匹配的位置if(j == pattern.size()){return i;}}return -1;
}

可以看出,这种算法存在着大量无意义的匹配,在最坏的情况下,我们需要依次匹配完主串中的所有位置,因此其时间复杂度高达O(N * M) N为主串长度,M为模式串长度。


RK

思路

那么有什么方法可以减少那些不必要的匹配,提高效率呢?这时候就可以巧妙地借助哈希算法来完成这个任务。

如果对于哈希不了解的可以查看我往期的博客
高级数据结构与算法 | 哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶

RK算法的全称是RabinKarp,是Rabin 和 Karp这两位科学家在BF算法的基础上,加上了哈希的思想后是实现的。

BF算法的主要缺陷在于模式串会去尝试匹配主串的任何一个位置,并且其中每次的匹配都会去一个一个字符进行对比,导致效率的下降。

而RK算法则想到了一个好方法,可以先进行一个预匹配,即哈希值的匹配,如果哈希值不同则说明字符串不同,没有比较的意义,而当哈希值相同时,为了避免哈希冲突的情况,再进行字符串的匹配即可。

由于哈希值只是一个整型,其比较起来的代价比起字符串大大的降低了

不过,哈希值的计算也需要通过遍历字符串来获得,那么效率不是又变低了吗?这时,就可以通过合理的设计哈希函数,来解决这个问题。

在这里,我选择使用最简单的按位相加来进行举例

首先,我们将模式串以及主串中第一个用来匹配的子串的哈希值计算出来,进行比较。

int hashFunc(const string& str)
{int hashCode = 0;for(int i = 0; i < str.size(); i++){hashCode += (str[i] - 'a');}return hashCode;
}

当其不匹配时,我们需要匹配主串的下一个位置,由于字符串发生变化,哈希值也应该发生变化。

考虑到匹配子串的变化起始就是从主串中向后移动一位,也就是删除了最高位,增加了最低位,所以我们可以借助这个性质,进行增量计算即减去首位的哈希值,加上末尾的哈希值,就可以避免每次都要重复计算哈希值的问题。

//获取移动到下一个位置后的字符串哈希值,即减去开头的哈希值,加上末尾的哈希值
int nextHash(const string& str, int hash, int begin, int end)
{hash -= (str[begin] - 'a');hash += (str[end] - 'a');return hash;
}

上面这种哈希算法的计算十分简单,但是也存在着大量的哈希冲突,所以选择一个合理的哈希算法,对效率的提升有很大的帮助,但是每种哈希算法也存在着一定的缺陷,例如26进制,虽然其几乎避免了哈希冲突,但是在字符串过长时会导致哈希值的溢出,所以要结合使用场景对哈希函数进行选择。
由于这里只是算法的一个介绍,就直接使用这种最简单的哈希函数。

此时,整个RK算法就包含两个部分,第一部分是进行哈希值的计算,时间复杂度为O(N),第二部分就是进行字符串的匹配,因为哈希值的比较的时间复杂度为O(1),而哈希值的更新也只是简单的增量计算,也是O(1),所以整个算法的时间复杂度为O(N),但是在最坏情况下,如存在大量的哈希冲突时,每次都需要将进行字符串的对比,这时的时间复杂度就会退化回O(N * M)

实现

算法实现如下

#include<string>
#include<iostream>using namespace std;//字符串哈希函数,这里使用的是最简单的按位相加
int hashFunc(const string& str)
{int hashCode = 0;for(int i = 0; i < str.size(); i++){hashCode += (str[i] - 'a');}return hashCode;
}//获取移动到下一个位置后的字符串哈希值,即减去开头的哈希值,加上末尾的哈希值
int nextHash(const string& str, int hash, int begin, int end)
{hash -= (str[begin] - 'a');hash += (str[end] - 'a');return hash;
}int rabinKarp(const string& str, const string& pattern)
{//不满足条件则直接返回falseif(str.empty() || pattern.empty() || str.size() < pattern.size()){return -1;}int len1 = str.size(), len2 = pattern.size();int patternHash = hashFunc(pattern);int subHash = hashFunc(str.substr(0, len2));for(int i = 0; i < (len1 - len2 + 1); i++){//如果当前的哈希值相同,则遍历比较字符串是否也相同,如果不同则没有必要进行比较if(patternHash == subHash && pattern == str.substr(i, len2)){return i;   //如果当前匹配,则返回匹配主串的起始位置}//如果主串中剩余字符还能与模式串进行对比,则更新哈希值if(len1 - i > len2){subHash = nextHash(str, subHash, i, i + len2);}}return -1;
}

字符串匹配算法(一):BF(BruteForce)算法和RK(RabinKarp)算法相关推荐

  1. 数据结构与算法之美笔记——基础篇(下):图、字符串匹配算法(BF 算法和 RK 算法、BM 算法和 KMP 算法 、Trie 树和 AC 自动机)

    图 如何存储微博.微信等社交网络中的好友关系?图.实际上,涉及图的算法有很多,也非常复杂,比如图的搜索.最短路径.最小生成树.二分图等等.我们今天聚焦在图存储这一方面,后面会分好几节来依次讲解图相关的 ...

  2. 最长不下降子序列的O(n^2)算法和O(nlogn)算法

    转帖 最长不下降子序列的O(n^2)算法和O(nlogn)算法 最长不下降子序列(LIS:Longest Increasing Subsequence) //用句通俗的话说,我讲的很通俗易懂~~ 问题 ...

  3. WordCount作业提交到FileInputFormat类中split切分算法和host选择算法过程源码分析

    参考 FileInputFormat类中split切分算法和host选择算法介绍  以及 Hadoop2.6.0的FileInputFormat的任务切分原理分析(即如何控制FileInputForm ...

  4. KNN算法和Kernel KNN算法的区别

    KNN算法和Kernel KNN算法的区别 KNN算法 KNN(K-Nearest Neighbor,简称KNN)算法,是一种常用的监督学习方法,其工作机制为:给定测试样本,基于某种距离度量找出训练集 ...

  5. 贪心算法和01背包算法

    贪心算法和01背包算法 实验报告 1.问题 2.解析 3.设计 4.分析 5.源码 实验报告 课程名称 <算法分析与设计> 实验名称 贪心算法和01背包算法 1.问题 [描述算法问题,首选 ...

  6. 并行sgd算法和min-batch gd算法

    sgd算法全称随机梯度下降法,有着比批梯度下降法更快收敛的优势,该算法名称中的"随机"二字是改算法的中心精神所在. sgd算法是一种天生的串行的算法,当数据量大的时候们希望通过使用 ...

  7. 【算法篇-字符串匹配算法】BF算法和KMP算法

    目录 前言 1. BF算法 1.1 画图分析 1.3 BF 算法的时间复杂度 2. KMP 算法 2.1 KMP 算法和 BF 算法 的区别 2.1.1 为什么主串不回退? 2. 2 next 数组 ...

  8. 数据结构之字符串匹配算法(BF算法和KMP算法)

    字符串匹配算法: 就是给定两个串,主串(s)和子串(sub), 查找子串是否在主串里面,如果找到便返回子串在主串中第一个元素的位置下标,否贼返回-1,. 在这里我 们讨论的时候主要用字符串来举例实现. ...

  9. 数据结构与算法分析(十六)--- 如何设计更高效的字符串匹配算法?(BF + RK + KMP + BMH)

    文章目录 一.Brute Force 匹配算法 二.Rabin–Karp 匹配算法 三.Knuth–Morris–Pratt 匹配算法 四.Boyer-Moore-Horspool 匹配算法 五.字符 ...

最新文章

  1. C# TTS-文本转语音
  2. java条码大小_java – 自定义条形码输入中缺少条形码高度
  3. ECharts - 地图
  4. 如果王思聪是产品经理
  5. 用命令导入导出MS SQL数据
  6. java中计算明年今天的日期_计算今天之后的下一个周年日
  7. 命名实体识别_命名实体识别的几种标注形式
  8. 鸿蒙车载智慧屏评测,华为智慧屏S Pro体验:告诉你鸿蒙OS有多优秀?
  9. 大数据分析,利用向外扩展技术深入挖掘商业价值
  10. 关系型数据库一致性的理解
  11. Linux meset
  12. 基于html的美食网站——速鲜站餐饮食品(HTML+CSS+JavaScript)大学生网页制作教程 表格布局网页模板 学生HTML静态美食网页设计作业成品 简单网页制作代码 学生美食网页作品
  13. matlab kdj,kdj指标详什么时候买入,kdj指标详解四大绝技
  14. Java8 JDK8
  15. python 豆瓣电影top250_python 爬豆瓣电影top250
  16. [Linux]进程概念以及进程状态
  17. python取元素_python 如何提取对象内的元素
  18. SSL集训 某OJ2021.08.14 提高B组 Luogu P7527 [USACO21OPEN] United Cows of Farmer John G【树状数组】
  19. 使用opencv+python识别七段数码显示器的数字识别
  20. 还想抄底?小心爆炸!

热门文章

  1. FastDFS集群部署
  2. redis(17)--集群
  3. servlet destroy 示例_KET答题卡怎么填写?2020年KET答题卡填涂示例
  4. 有n个人围成一圈,按顺序从1到n编号。从第一个人开始报数
  5. redis设置开机自启动
  6. 安卓--L2T虚拟连接
  7. Java(第十五章)
  8. Maven(四):定制库到Mave本地资源库 (Kaptcha)
  9. ASP中文件上传组件ASPUpload介绍和使用方法
  10. IOS视频播放器的制作