字符串匹配
字符串匹配(模式匹配)的分类
BF算法(brute-force)
主要思想:
从左往右,依次比较,每次移动一个字符位置。比较方向可以任意选定。无预处理阶段。
也就是暴力比较。
时间复杂度为O(n*m)
1 | context = "GCACTGCAGCACAGCAGCAGTACG" |
KMP算法
kmp算法核心思想:控制回溯尺度(不回溯思想)
如此例子所示,当e和d发生失配的时候,BF算法是将已经匹配的字符全部回溯,从第一个重新开始匹配。
但是kmp则根据最长公共前后缀,此处为前缀ab和后缀ab,会将前缀移动到后缀处,控制了回溯尺度(也就相当于跳的更远了些。
显然关键在于求解next数组
例子:
nextval数组
nextval数组是next数组的修正版,主要解决了模式串中大量连续重复连续的字符,减少了主串的无用比较次数。
BM算法
BM算法是另一种能够充分利用模式串特征,尽可能排除多的无效字符匹配的单模式匹配算法。
主要思想:
算法从正文左端开始于模式串对其并向右扫描,对齐后从模式最右端开始从右向左进行字符匹配。在发生不匹配的时候,用两个预先计算的函数**坏字符(bad character)和好后缀(good suffix)**来确定模式串移动的距离。
坏字符原则
坏字符原则1:模式串中不存在对应的坏字符:直接右移模式串到坏字符右侧。
坏字符原则2:模式串中存在对应的坏字符,找到最右的对应字符。
原则2a:失配位置在该字符右侧时:让模式串右移,使得最右的对应字符与坏字符相对。
原则2b:失配位置在该字符左侧时:模式串右移一步。
(移动过程中,模式串不能相对于文本串走回头路)
但是会出现字符风暴问题:
好后缀原则
好后缀原则1:模式串中有字串和好后缀完全匹配,则将最靠右的那个字串移动到好后缀的位置继续匹配。
注意:bmGs数组在找和好后缀相同的子串的时候,一般还需要往左多看一个字符,确保两个子串前一个字符不同,才算找到相同字串。
好后缀原则2:如果不存在和好后缀完全匹配的字串,但在好后缀中存在某个最长后缀,使得模式的整个前缀与这个后缀匹配,将这个最长后缀与前缀对齐。(和KMP类似)
好后缀原则3:如果完全不存在和好后缀满足以上两个原则的字串,则右移整个模式串。
坏字符数组bmBc数组的构造
好后缀数组bmGs数组的构造
suffix数组:
单模式匹配算法复杂度分析
BP:O(n*m)
KMP:
KMP算法的时间复杂度为O(m+n),其中m是模式串的长度,n是文本串的长度。
具体来说,KMP算法的时间复杂度由两个部分构成:
1. 构造前缀表(也就是next数组)的时间复杂度为O(m),因为需要对模式串进行前缀匹配。
2. 匹配的时间复杂度为O(n),因为需要对文本串的每个字符都进行一次匹配操作。
这两个部分的时间复杂度相加即可得到KMP算法的总体时间复杂度为O(m+n)。
BM:
时间复杂度以上BM算法是个初级版本。这个版本,在极端情况下,预处理计算suffix数组、prefix数组的性能会比较差。比如模式串是aaaaaaa这种包含很多重复的字符的模式串,预处理的时间复杂度就是O(m^2)。如何优化这种极端情况下的时间复杂度退化,以后再找空研究。实际上,BM算法的时间复杂度分析起来是非常复杂,论文"A new proof of the linearity of the Boyer-Moore string searching algorithm"证明了在最坏情况下,BM算法的比较次数上限是5n。论文"Tight bounds on the complexity of the Boyer-
Moore string matching algorithm"证明了在最坏情况下,BM算法的比较次数上限是3n。
AC算法—有限自动机的多模式匹配算法
形象地说就是kmp+trie树。
这里可以不看ppt了,因为实验实现的AC自动机是纯自己写的。这里复习一下。
书面上主要有三个函数,转向函数g,失效函数f,输出函数output,如图所示:
转向函数g的构造
按照字典树trie的方式来构造即可,一个一个单词插入,字典树的插入和查询复杂度为O(L),L为字符串长度。
从根节点开始,如果没有通道,则fork出一个分支,比如存储she的时候,根节点没有到s的通道,则fork出一条新的来。
失效函数f的构造
一个递推公式:
f[i] = f[pre] if f[pre]→next ≠ i else f[pre]→next
哈哈有点复杂,就是先看f[pre],然后看有无路径,有就指向那里,没有就用f[pre]就行了。
比如这里的5,构造它的f,先看f[4],f[4]=1,所以看1通往哪里,发现有e的通道,所以直接指向2就行了。
失效函数output的构造
这个比较简单也就是当跑到这个节点的时候,应该输出什么东西。
比如这里到了5节点应该输入she,然后看看f[5] = f[2],恰好2也应该输出,所以就把he也加上去。
AC算法的缺点—内存占用问题
优化方法:
行压缩方法
也就是用链表啦,方便实用。而且简单。
位图方法
这个压缩更狠一些(
双数组改进方法
转向函数中引入双数组,Base表和Check表。
Base表:当前状态的Base值+ASCII输入 = 下一个状态的偏移。
Check表:当前状态的父状态信息
-
父状态是唯一的
自动机构建是建立在广度优先搜索的基础上。
唉唉给图自己理解吧 有一点点复杂
WM算法(Wu-Manber)-快速的多模式匹配算法
采用了跳跃的不可能匹配的字符策略和hash散列的方法,对处理大规模的多关键字匹配问题有很好的效果。有具体使用:agrep
匹配示例
算法主要用了几个数据结构:
-
SHIFT表
-
HASH表
-
PREFIX表
-
PAT_PTR表
预处理过程
算法的基本思想:在预处理阶段,主要使用了SHIFT表、HASH表和PREFIX表。
在搜索查找阶段,SHIFT表用于在扫描文本串的时候,根据读入字符串可以决定跳过的字符数,如果相应的跳跃值为0,则说明可能发生匹配,就要用到HASH表和PREFIX表进行进一步的判断,以决定有哪些匹配候选模式,并验证究竟哪个或者哪些某选模式完全匹配。
SHIFT表如何构造:
有点像坏字符了(
HASH表的构造:
PREFIX表的构造:
有点复杂,看个例图
一般会取B=2/B=3
懂啦!
复杂度分析:
AC算法和Wu-Manber算法比较
在模式串个数下对算法性能的比较:
WM完胜
模式串长度下
WM也完胜
原因在于WM算法具有并行性,性能更好。
什么时候用什么
AC并行化
任务并行就是我可以同时将m个模式中k个模式构建一个自动机
m-k个模式构建一个自动机,甚至是建一个wm
不同的数据同时走这两个自动机
这是当一个节点空间构建不了一个所有模式自动机的时候
对于数据并行,可以构建一个自动机,多个线程共用这个自动机,每个线程可以只保存自己的自动机状态,把数据分到每个线程
适合数据量较大的场景
模式不一定很多
就知道这两个就行了
基于AC双数组算法的IP地址匹配
最大公共字串问题(LCS问题 longest common substring)
方法:广义后缀树(Generalized Suffix Tree),简称GST算法。
把给定的N个源字符串的所有的后缀建成一棵树
例子:
最长公共子序列问题
什么算法设计与分析
正则表达式匹配算法
.
C* 表示的是匹配前面的表达式 C 零次或多次
如果你想匹配至少出现一次的 C,可以使用 C+。
d? 表示字符 d 是可选的,可以出现零次或一次。