以下笔记为jjyaoao本人制作,欢迎借鉴学习和提出相关建议,转载需要标明出处www.jjyaoao.space

ADT

定义和特点

image-20220306170634614

术语

​ 统一约定:

联系:在i+k长度的前缀中,长度为k的后缀

任何串,也是他自身的子串,前缀,后缀,长度严格小于原串的子串,前缀,后缀,也成为了真子串,真前缀,真后缀

image-20220306171030224

接口定义

image-20220306171432850

image-20220306182130578

模式匹配

问题与需求

image-20220306182431409

算法评测

image-20220306182811667

蛮力匹配

构思

image-20220306183011853

版本一

image-20220306183117899

image-20220306183308824

版本二

image-20220306183620385

复杂度分析

image-20220306183951829

但是就期望而言,对于日常使用,往往可以每一个位置都达到o(1),总共累计不过线性O(n)复杂度

​ 我们不满足于次,想要把期望抹掉,好消息是这类算法的确存在,比如下面的知名的KMP

KMP算法:记忆法

阐述

蛮力为何低效

image-20220306184346901

不变性

image-20220306184523810

记忆力

image-20220306184806024

预知力

image-20220306185054203

查询表

制表备查

image-20220306185350918

主算法

image-20220306185642186

实例

image-20220306185936554

理解Next[]表

快速移动

image-20220306190353568

避免回溯

image-20220306191052467

t越小,对应的位移越大,反之,对应的位移越小,而位移量更小,就意味着更安全,这也暗示了另一个不变性,也就是由KMP舍弃的哪些位置,的确是不值得对齐的

通配哨兵

image-20220306190846843

image-20220306190857849

image-20220306190934893

image-20220306191119108

当j<0时,通过通配的哨兵结点,使得T[i] == T[j] 必然成功,从而使模板表位置回到第一位

构造Next[]表

递推

image-20220306191436202

算法

image-20220306191742476

显而易见,该过程也就是next自己与自己逐一比对的过程,(哨兵使得算法鲁棒性良好)我们可以通过修改KMP的框架简要得出模板表的代码

实现

image-20220306192047320

1
2
3
4
5
6
7
8
9
10
11
12
int* buildNext ( char* P ) { //构造模式串P的next表
0002 size_t m = strlen ( P ), j = 0; //“主”串指针
0003 int* N = new int[m]; //next表
0004 int t = N[0] = -1; //模式串指针
0005 while ( j < m - 1 )
0006 if ( 0 > t || P[j] == P[t] ) { //匹配
0007 j ++; t ++;
0008 N[j] = t; //此句可改进...
0009 } else //失配
0010 t = N[t];
0011 return N;
0012 }

实例

image-20220306192208095

image-20220306192219475

分摊分析

失之粗糙

通过过程分析

image-20220306192311144

image-20220306192405035

精准估计

根据代码主体分析

若对k为何这样取,看习题解析(下次一定

image-20220306192836058

再改进

美中不足

这种反例的情况,如果说第一次的比对还是有意义的话,那么后面的比对都是多余的。

image-20220306193131771

以卵击石

为何一错再错???Next[]表。Next[]表为整个算法的灵魂所在

把1比作石头,0比作鸡蛋

image-20220306193232648

前车之鉴

我们在if里增加一句判断语句,新的字符必须与此前那个字符不一样,新的字符不一定会比石头更硬,但他至少不应该仍然是一枚鸡蛋

image-20220306193505256

实例

image-20220306193648588

可视对比

每一个区间的计算成本,都可以用深色区域来度量

蛮力算法

image-20220306193818889

KMP

image-20220306193838100

BM_BC:以终为始

善待教训

image-20220306194117953

前轻后重

​ 我们在后面获得教训,利用价值往往更大(排除更多的位置)

​ 我们获得失败的概率也远远大于成功的概率

image-20220306194312544

以终为始

image-20220306194539691

image-20220306194442278

坏字符

这一策略,将我们之前称为的教训,转名为 坏字符

当出现坏字符,使我们在这次匹配能获得成功带来失望,所以说是坏字符

image-20220306194847747

特殊情况

image-20220306195002605

image-20220306195150472

实例

image-20220306195218779

构造BC[]

image-20220306195441497

性能分析

最好情况

适配概率越大越好

image-20220306195642494

image-20220306195650019

最坏情况

image-20220306195759014

BM_GS:好后缀

兼顾经验

image-20220306195845030

好后缀

制表

image-20220306195942513

image-20220306200040905

image-20220306200055696

实例体验

image-20220306200106699

构造GS表

构造MS[]

image-20220306200340192

构造SS[]

image-20220306200444089

构造GS[]

image-20220306200428293

性能分析

image-20220306200648501

各算法总览

KMP:适用于规模小的情况 适合容易匹配到的情况

BC:适用于大字符集 适合容易匹配不到的情况

BC+GS:无敌

image-20220306200835998

Karp-Rabin:串即是数

化串为数

image-20220306201038321

凡物皆数

image-20220306201244626

image-20220306201354047

串亦是数

image-20220306201521233

数位溢出

image-20220306201642192

散列压缩

image-20220306201814786

应对冲突

只要有散列,就会有冲突

因此,一旦检测到散列码一样,我们也不会贸然的选取,这里采用的是,散列码一样之后,在进行逐位比对

image-20220306201957626

指纹更新

image-20220306202213681