字符串匹配算法BF,BM,KMP
字符串匹配bf算法:(暴力穷举算法)
在一个字符串中寻找另一字符串,最容易想到的,也是最简单的办法是:取主串和模式串/搜索串中的每一位依次比较,如果匹配则同时后移一位继续比较,直至匹配到模式串的最后一位;如果出现不匹配的字符,则模式串向后移动一位,继续比较。这种解决问题的思路简单暴力,也是这个算法被叫做BF(Brute Force)的原因。整个匹配的过程可以参考下图,我们假设主串为“abdea”,搜索串为“dea”:
这个算法的复杂度还是比较好分析的,我们假设主串的长度是 m,模式串的长度是 n,在最好的情况下,在第一个字符处的匹配就能够成功,例如主串是 a b c d ,模式串是a b c,这时只遍历了模式串的长度,因为时间复杂度是 O(n);
在最坏的情况下,每次都需要遍历整个模式串,但是又未能匹配成功,例如主串是 a a a a a ...a,模式串是 a a a a b,所以需要遍历 m - n + 1 次,时间复杂度是 O(m * n) 。
Java的indexOf 方法解析
在 Java 语言中,常用的字符串匹配的方法是 String 类中的 indexOf() 方法,它的设计思路又是怎么样的呢?其实 indexOf 方法的逻辑非常简单,看看源代码就知道它其实就是 BF 算法的实现。
主要的匹配逻辑在代码中的 for 循环这一段,可以看到匹配的过程主要分为了两步,第一步是找出第一个匹配的字符,
然后再依次遍历模式串看是否匹配。这和 BF 算法的思路是一致的。
BM算法:
各种文本编辑器的"查找"功能(Ctrl+F),大多采用Boyer[ˈbɔɪə]-Moore[mʊə]算法。
Boyer-Moore算法不仅效率高,而且构思巧妙,容易理解。1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了这种算法。BM算法里的总体思路是:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。
下面根据Moore教授自己的例子来解释这种算法。
1.
假定字符串为"HERE IS A SIMPLE EXAMPLE",搜索词为"EXAMPLE"。这里的EXAMPLE就是模式串。
首先,"字符串"与"搜索词"头部对齐,从尾部开始比较。
这是一个很聪明的想法,因为如果尾部字符不匹配,那么只要一次比较,就可以知道前7个字符(整体上)肯定不是要找的结果。
我们看到,"S"与"E"不匹配。这时,"S"就被称为"坏字符"(bad character),即不匹配的字符。我们还发现,"S"不包含在搜索词"EXAMPLE"之中,这意味着可以把搜索词直接移到"S"的后一位。
依然从尾部开始比较,发现"P"与"E"不匹配,所以"P"是"坏字符"。但是,"P"包含在搜索词"EXAMPLE"之中。所以,将搜索词后移两位,两个"P"对齐。
这里移动两位的依据:
后移位数 = 坏字符的位置 – 模式串中的上一次出现位置,如果"坏字符"不包含在搜索词之中,则上一次出现位置为 -1。也就是P这次在上面的主字符串是第六个位置,在模式串中是第四个位置 ,所以6-4 等于2 ,所以移动两位,通过这个也可以说明第一次的S和E不行等那就是6(S这个坏字符在字符串中的位置是6,以0下表开始计算)减去-1(S在模式串中不存在就是负1)(再以前面第二步的"S"为例,它出现在第6位,上一次出现位置是 -1(即未出现),则整个模式串后移 6 - (-1) = 7)位。
以"P"为例,它作为"坏字符",出现在主串的第6位(从0开始编号),也就是“空格,A,空格,S,I,M,P”,在模式串中的上一次出现位置为4,所以后移 6 - 4 = 2位。
如下图所示:
接着移动到这里:
依然从尾部开始比较,"E"与"E"匹配。
比较前面一位,"LE"与"LE"匹配。
比较前面一位,"PLE"与"PLE"匹配。
比较前面一位,"MPLE"与"MPLE"匹配。我们把这种情况称为"好后缀"(good suffix),即所有尾部匹配的字符串。注意,"MPLE"、"PLE"、"LE"、"E"都是好后缀。
比较前一位,发现"I"与"A"不匹配。所以,"I"是"坏字符"。
根据"坏字符规则",此时模式串应该后移 2 - (-1)= 3 位。问题是,此时有没有更好的移法?
我们知道,此时存在"好后缀"。所以,可以采用"好后缀规则":
后移位数 = 好后缀的位置 – 模式串中的上一次出现位置
举例来说,如果字符串"ABCDAB"的后一个"AB"是"好后缀"。那么它的位置是5(从0开始计算,取最后的"B"的值),在模式串中的上一次出现位置是1(第一个"B"的位置),所以后移 5 - 1 = 4位,前一个"AB"移到后一个"AB"的位置。
再举一个例子,如果字符串"ABCDEF"的"EF"是好后缀,则"EF"的位置是5 ,上一次出现的位置是 -1(即未出现),所以后移 5 - (-1) = 6位,即整个模式串移到"F"的后一位。
(1)"好后缀"的位置以最后一个字符为准。假定"ABCDEF"的"EF"是好后缀,则它的位置以"F"为准,即5(从0开始计算)。
(2)如果"好后缀"在模式串中只出现一次,则它的上一次出现位置为 -1。比如,"EF"在"ABCDEF"之中只出现一次,则它的上一次出现位置为-1(即未出现)。
(3)如果"好后缀"有多个,如果最长的那个"好后缀"在模式串中只出现一次,其他"好后缀"的上一次出现位置必须在头部。比如我们的例子中所有的"好后缀"(MPLE、PLE、LE、E)之中,只有"E"在"EXAMPLE"还出现在头部,所以后移 6 - 0 = 6位。
可以看到,"坏字符规则"只能移3位,"好后缀规则"可以移6位。所以,Boyer-Moore算法的基本思想是,每次后移这两个规则之中的较大值。
更巧妙的是,这两个规则的移动位数,只与模式串有关,与原字符串无关。因此,可以预先计算生成《坏字符规则表》和《好后缀规则表》。使用时,只要查表比较一下就可以了。
继续从尾部开始比较,"P"与"E"不匹配,因此"P"是"坏字符"。根据"坏字符规则",后移 6 - 4 = 2位。
从尾部开始逐位比较,发现全部匹配,于是搜索结束。如果还要继续查找(即找出全部匹配),则根据"好后缀规则",后移 6 - 0 = 6位,即头部的"E"移到尾部的"E"的位置。 这个也就是平时文本编辑器ctrl+f移动搜索的原理
最坏情况下找到模式所有出现的时间复杂度为O(mn),在最好情况下执行匹配找到模式所有出现的时间复杂度为O(m/n)。
所以,BM算法里的总体思路就是:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。同时可以预先计算生成《坏字符规则表》和《好后缀规则表》,大大加快查找匹配的速度。
字符串KMP算法:
KMP算法是三位学者在 Brute-Force算法的基础上同时提出的模式匹配的改进算法。它也是字符串匹配中的经典算法。它以三个发明者命名。
举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?。
首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。
因为B与A不匹配,搜索词再往后移。
就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。
接着比较字符串和搜索词的下一个字符,还是相同。
直到字符串有一个字符,与搜索词对应的字符不相同为止。
这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。
一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。
怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
因为 6 - 2 等于4,所以将搜索词向后移动4位。
因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。
因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。
因为空格与A不匹配,继续后移一位。
逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。
逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。
接着介绍部分匹配表的概念:
前缀:除了最后一个字符以外,剩下所有字符组合 br bre brea
后缀:除了第一个字符意外,剩下所有字符,read,ead,ad,d
首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
"AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。
KMP算法的时间复杂度O(m+n)。
BM算法和KMP算法两者比较来说,BM算法的执行效率要比KMP算法快3-5倍左右,并且十分容易理解。所以各种文本编辑器的“查找”功能(CTRL + F)一般都是采用的此算法。
字符串匹配算法BF,BM,KMP相关推荐
- 字符串匹配算法---BF及KMP
字符串匹配的一般算法(BF) 以 ABSABABCEF 与 ABCE 为例,求串2与串1匹配的第一个位置的下标(这里即输出 5),一般的,我们可以从串1的起始位置开始与串2比较,若相同则两串都 ...
- diff算法阮一峰_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法
前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...
- 大量的数据做字符串匹配_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法...
前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...
- 字符串匹配算法之BM算法
BM算法,全称是Boyer-Moore算法,1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法. BM算法定义了两个规则: ...
- python实现字符串匹配算法BF,BF改,KMP
包含:BF,BF改进版本,KMP BF:暴力搜索 BF改:当判断匹配失败的字符串是不是与首字母相同 若不同,继续BF算法: 若相同,直接将首字母移到当前位置 KMP:通过前缀与后缀发现待匹配字符串本身 ...
- 字符串匹配算法(三):KMP(KnuthMorrisPratt)算法
文章目录 KMP 原理 next数组的构建 代码实现 KMP 一提到字符串匹配算法,想必大家脑海中想到的第一个必然就是KMP算法,KMP算法的全称叫做KnuthMorrisPratt算法,与上一篇博客 ...
- 一看就懂的字符串匹配算法 之 BM算法
先来一个导读,关于BM算法开始有了难度,大家一定要静下心,咬着呀也得给我读下去,这样你才能有收获. 我们在文本编辑器中,我们经常用到查找及替换功能.比如说,在Word文件中,通过查找及替换功能,可以把 ...
- Go 语言实现字符串匹配算法 -- BF(Brute Force) 和 RK(Rabin Karp)
今天介绍两种基础的字符串匹配算法,当然核心还是熟悉一下Go的语法,巩固一下基础知识 BF(Brute Force) RK(Rabin Karp) 源字符串:src, 目标字符串:dest: 确认des ...
- 字符串匹配算法(BM)
文章目录 1. BM(Boyer-Moore)算法 1.1 坏字符规则 1.2 好后缀规则 1.3 两种规则如何选择 2. BM算法代码实现 2.1 坏字符 2.2 好后缀 2.3 完整代码 2.4 ...
最新文章
- 聊一聊多源最短路径问题(只有5行代码哦)
- 数组方法深入扩展(遍历forEach,filter,reduce等)
- Windows 修改hosts文件以及权限问题
- 修改Bootstrap的一些默认样式
- java实现人脸识别源码【含测试效果图】——实体类(Users)
- Swing和JavaFX:使用JFXPanel
- 中国联通:已率先开通国内40个城市的5G试验网络
- 前端面试题汇总(jQuery)
- 大数据杀熟行为10月1日起明令禁止;阿里一号工程“犀牛制造”正式亮相;iOS 14 正式版发布 | 极客头条...
- PHP运算符 - 对象的方法或者属性, =数组的元素值
- .NET下多线程初探
- java sdk怎么配置_Java SDK环境配置教程
- 《计算机组成原理》第五版(唐朔飞考研版) 全书知识梳理
- Android音频基础知识
- 使用db1小波做3层小波分解
- 深圳杯2020数学建模C题 遗传算法
- 你好Linux!第一篇——Linux的前世今生和应用
- c++一本通 1238一元三次方程求解
- CAD中的选择集过滤----DXFCode(一)
- SAP 客商之一次性供应商