0%

字符串匹配算法

字符串匹配

字符串匹配(模式匹配)的分类

Untitled

Untitled

Untitled

BF算法(brute-force)

主要思想:

从左往右,依次比较,每次移动一个字符位置。比较方向可以任意选定。无预处理阶段。

也就是暴力比较。

时间复杂度为O(n*m)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
context = "GCACTGCAGCACAGCAGCAGTACG"
model = "GCAGCAG"

def BP(context, model):
model_length = len(model)
context_length = len(context)
error = 0
offset = context_length - model_length
if offset < 0:
return False
for i in range(0, offset):
for j in range(model_length):
error += 1
if context[i+j] != model[j]:
break
else:
if j == model_length-1:
print("error: ", error)
return i
print("error: ", error)
return False

result = BP(context, model)
print(result)

KMP算法

kmp算法核心思想:控制回溯尺度(不回溯思想)

Untitled

如此例子所示,当e和d发生失配的时候,BF算法是将已经匹配的字符全部回溯,从第一个重新开始匹配。

但是kmp则根据最长公共前后缀,此处为前缀ab和后缀ab,会将前缀移动到后缀处,控制了回溯尺度(也就相当于跳的更远了些。

显然关键在于求解next数组

Untitled

例子:

Untitled

nextval数组

nextval数组是next数组的修正版,主要解决了模式串中大量连续重复连续的字符,减少了主串的无用比较次数。

Untitled

BM算法

BM算法是另一种能够充分利用模式串特征,尽可能排除多的无效字符匹配的单模式匹配算法。

主要思想:

算法从正文左端开始于模式串对其并向右扫描,对齐后从模式最右端开始从右向左进行字符匹配。在发生不匹配的时候,用两个预先计算的函数**坏字符(bad character)好后缀(good suffix)**来确定模式串移动的距离。

Untitled

坏字符原则

坏字符原则1:模式串中不存在对应的坏字符:直接右移模式串到坏字符右侧。

坏字符原则2:模式串中存在对应的坏字符,找到最右的对应字符。

原则2a:失配位置在该字符右侧时:让模式串右移,使得最右的对应字符与坏字符相对。

原则2b:失配位置在该字符左侧时:模式串右移一步。

(移动过程中,模式串不能相对于文本串走回头路)

但是会出现字符风暴问题:

Untitled

好后缀原则

Untitled

好后缀原则1:模式串中有字串和好后缀完全匹配,则将最靠右的那个字串移动到好后缀的位置继续匹配。

注意:bmGs数组在找和好后缀相同的子串的时候,一般还需要往左多看一个字符,确保两个子串前一个字符不同,才算找到相同字串。

好后缀原则2:如果不存在和好后缀完全匹配的字串,但在好后缀中存在某个最长后缀,使得模式的整个前缀与这个后缀匹配,将这个最长后缀与前缀对齐。(和KMP类似)

好后缀原则3:如果完全不存在和好后缀满足以上两个原则的字串,则右移整个模式串。

坏字符数组bmBc数组的构造

Untitled

好后缀数组bmGs数组的构造

Untitled

Untitled

Untitled

suffix数组:

Untitled

Untitled

单模式匹配算法复杂度分析

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。

Untitled

AC算法—有限自动机的多模式匹配算法

形象地说就是kmp+trie树。

Untitled

Untitled

这里可以不看ppt了,因为实验实现的AC自动机是纯自己写的。这里复习一下。

书面上主要有三个函数,转向函数g,失效函数f,输出函数output,如图所示:

Untitled

转向函数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算法的缺点—内存占用问题

Untitled

Untitled

优化方法:

行压缩方法

Untitled

也就是用链表啦,方便实用。而且简单。

位图方法

Untitled

Untitled

这个压缩更狠一些(

双数组改进方法

转向函数中引入双数组,Base表和Check表。

Base表:当前状态的Base值+ASCII输入 = 下一个状态的偏移。

Check表:当前状态的父状态信息

  • 父状态是唯一的

自动机构建是建立在广度优先搜索的基础上。

Untitled

Untitled

Untitled

唉唉给图自己理解吧 有一点点复杂

Untitled

Untitled

Untitled

Untitled

Untitled

WM算法(Wu-Manber)-快速的多模式匹配算法

采用了跳跃的不可能匹配的字符策略和hash散列的方法,对处理大规模的多关键字匹配问题有很好的效果。有具体使用:agrep

匹配示例

Untitled

Untitled

Untitled

算法主要用了几个数据结构:

  • SHIFT表

  • HASH表

  • PREFIX表

  • PAT_PTR表

Untitled

预处理过程

算法的基本思想:在预处理阶段,主要使用了SHIFT表、HASH表和PREFIX表。

在搜索查找阶段,SHIFT表用于在扫描文本串的时候,根据读入字符串可以决定跳过的字符数,如果相应的跳跃值为0,则说明可能发生匹配,就要用到HASH表和PREFIX表进行进一步的判断,以决定有哪些匹配候选模式,并验证究竟哪个或者哪些某选模式完全匹配。

SHIFT表如何构造:

Untitled

有点像坏字符了(

Untitled

HASH表的构造:

Untitled

Untitled

PREFIX表的构造:

Untitled

Untitled

有点复杂,看个例图

Untitled

Untitled

Untitled

Untitled

Untitled

一般会取B=2/B=3

懂啦!

复杂度分析:

Untitled

AC算法和Wu-Manber算法比较

在模式串个数下对算法性能的比较:

WM完胜

模式串长度下

WM也完胜

原因在于WM算法具有并行性,性能更好。

什么时候用什么

AC并行化

任务并行就是我可以同时将m个模式中k个模式构建一个自动机

m-k个模式构建一个自动机,甚至是建一个wm

不同的数据同时走这两个自动机

这是当一个节点空间构建不了一个所有模式自动机的时候

对于数据并行,可以构建一个自动机,多个线程共用这个自动机,每个线程可以只保存自己的自动机状态,把数据分到每个线程

适合数据量较大的场景

模式不一定很多

就知道这两个就行了

Untitled

基于AC双数组算法的IP地址匹配

Untitled

Untitled

Untitled

最大公共字串问题(LCS问题 longest common substring)

方法:广义后缀树(Generalized Suffix Tree),简称GST算法。

把给定的N个源字符串的所有的后缀建成一棵树

例子:

Untitled

Untitled

Untitled

最长公共子序列问题

Untitled

Untitled

什么算法设计与分析

正则表达式匹配算法

Untitled

1.png

2.png

.

3.png

4.png

C* 表示的是匹配前面的表达式 C 零次或多次

如果你想匹配至少出现一次的 C,可以使用 C+。

d? 表示字符 d 是可选的,可以出现零次或一次。