数据结构小笔记-String

本文详细介绍了字符串匹配的几种算法,包括蛮力算法、KMP算法和Boyer-Moore(BM)算法。KMP算法通过构造next[]查询表,实现时间复杂度为O(n)。BM算法则分为BC坏字符策略和GS好后缀策略,通过坏字符和好后缀表实现快速右移,提高效率。在大规模字符集中,BM算法尤其适用。最后提到了Karp-Rabin算法,通过指纹和hash值比较进行字符串匹配,时间复杂度可达O(n)。
摘要由CSDN通过智能技术生成

学堂在线数据结构下-第十三章-串String
20220602

串(String):S[0,n)
字符串——有限字符序列。
String在结构上相当于vector。
字符的种类不多,但 串长n >> 字符种类。

串的术语——相等、子串、前缀、后缀、联系、空串。

空串 != 由空格组成的串。
空串 == 长度为0的串
空串是任何串的子串、前缀、后缀。

索引接口(indexOf(P))——判等接口(equal())的一般化推广
用于判断字符串P是否为当前字符串的子串(串匹配问题)。

-----------

串匹配——在某文本串中,查找特定模式串。

【1】蛮力(Brute-Force)匹配算法——文本串与模式串的首字符对齐,自左向右,以单字符为单位间隔,在文本串中依次移动模式串并进行比对,直到文本串与模式串在某位置完全匹配。只要某位置有一对字符失配,则模式串在该位置无法匹配。最好情况O(m);最坏情况O(nm) (其实是O((n-m)m),但n>>m>>2,所以等价于O(nm))。
文本串与模式串在蛮力算法下的每轮比对,在失配字符之前的多次单字符比对 均局部匹配——若充分利用这一信息(【记忆力】)可适当跳过(文本串中的)多个字符——将 模式串中的某字符 与 文本串中的失配字符 重新对齐(【预知力】),再重新开始下一轮比对……使模式串快速右移,减少总的比对次数,大幅提高效率。

---

20220603

【2】KMP算法——最坏情况时间复杂度也能O(n) (其实是O(n+m),但n>>m>>2,所以可记为 O(n))。
关键步骤—— 一旦失配,赋值 j = next[j];
本质——构造next[]查询表(仅取决于 模式串。模式串自身的匹配性(自匹配)——模式串的某前缀P[0,t) 与 模式串失配字符前的前缀P[0,j)中的后缀P[j-t,j) 相等—— P[0,t)==P[j-t,j) )。
模式串的自匹配性质——排除不必要的对齐位置,模式串快速右移。
模式串的最长自匹配 == j-t; 快速右移 + 避免回退(t越小越好)。
Next[0]=-1; ——在模式串首字符之前(秩为-1)增设一虚拟哨兵,其在逻辑上与任一字符均匹配(【通配符】)。Next[0]=-1; 在语义上是指,在模式串与文本串的某轮比对中,模式串的首字符就已失配,于是将 虚拟哨兵通配符 与 文本串失配字符 对齐,进行下一轮比对(通配符与任一字符均局部匹配,等效于模式串右移一位)。
递归构造next[]查询表——
next[0]=-1;
next[j] < j;
1 + next[0] == 0;
next[j+1]依次递归是
== 1 + next[j]; //若P[j] == P[next[j]]
== 1 + next[next[j]]; //若P[j] == P[next[next[j]]]
== 1 + next[next[next[j]]]; //若P[j] == P[P[next[next[j]]]]
== ......

-

buildNext() 的 时间复杂度 O(m)。
KMP算法中的 匹配失配循环体 的 时间复杂度 O(n)==2n-1。
KMP算法的整体时间复杂度 O(n)(其实是O(n+m),但n>>m>>2,所以可记为 O(n))。

-

KMP算法改进——模式串自相似(自匹配)+失配字符不相似。
本质是改进next[]查询表。等效于进一步加速右移。
某轮比对失配之后,在下一轮比对之前,模式串与文本串重新对齐的条件:(1)自相似:失配字符之前的模式串子串中,其某一前缀与其某一后缀相等。(2)不相似:模式串的失配字符P[j] != P[t]。满足这两个条件后,才可进行下一轮比对。

-

模式串与文本串匹配失败时的最好情况的时间复杂度也 = O(n)。而且,字符集规模越大,这种情况出现的概率也越大。因此,字符集规模越大,KMP算法与BF算法的性能差异越不明显。
相反地,字符集规模越小,KMP算法的性能优势越明显。因此,教材常用{0,1}二进制字符集来讲解KMP算法(因为字符集规模=2,非常小)。

---

【3】BM算法:适用于 快速失败 的 大规模字符集。
字符串匹配问题中,只要 字符集规模|Σ|>>2 ,即规模不太小,局部单字符匹配成功概率(1/(|Σ|)) 就远小于 失配概率。
只要 模式串中的任一字符 不同于 文本串中对应位置的字符,则可判定失配。因此,模式串与文本串的比对操作中,判断不等(比 判断相等)效率更高。

每轮比对,只需【在模式串中从右向左(从 模式串的末字符 向 模式串的首字符)】逐个比对单字符、并找到【模式串中最靠右(最靠近末尾)的】失配字符,即可排除 文本串中的若干对齐位置,从而重新确定下一轮比对的文本串中的对齐位置,实现模式串的快速右移(快速后移)。

字符集规模|Σ|越大,单字符匹配成功的概率越小,BM算法的优势越明显。所以,汉字(5000多个常用字符)或UniCode字符集 适用 BM算法 进行字符串匹配。

-

【3.1】BM_BC(Boyer-Moore_Bad-Character)【坏字符策略】:适用于 快速失败。
本质——构造BC[]查询表。
对于任一字符X:bc(X) = rank(X); 
(模式串中没有 文本串中的失配字符X 时,在模式串首字符之前增设虚拟哨兵(秩为-1),模式串的虚拟哨兵通配字符 与 文本串中的失配字符X 对齐后进行下一轮比对。)
(模式串中有多个 文本串中的失配字符X 时,取X的最大秩(更靠近模式串末尾)。)
(模式串中存在 文本串中的失配字符X 时,若模式串中的秩最大的X 出现在 模式串中的失配字符Y 的右边(更靠近末尾),则模式串直接右移一位、再进行下一轮比对(而不必将 模式串中的最大秩的X 回溯对齐于 文本串中的失配字符X)。)

bc[]查询表 的空间复杂度 = O(|Σ|)。
bc[]查询表 的时间复杂度 = O(|Σ|+m) (可改进为O(m))。

BM算法BC策略的最好情况的时间复杂度 = O(n/m)。

字符集规模|Σ|越大,单字符匹配成功的概率越小,BM算法的优势越明显。所以,汉字(5000多个常用字符)或UniCode字符集 适用 BM算法 进行字符串匹配。

BM算法BC策略的最坏情况的时间复杂度 = O(nm)(=O((n-m)m),因为n>>m,所以等价于O(nm)),退化为蛮力算法。此时,再额外添加一条【GS好后缀策略】,配合上述的【BC坏字符策略】,可使BM算法趋向完美。

-

20220604

【3.2】BM_GS(Boyer-Moore_Good-Suffix)【好后缀策略】:
互补于BC策略,弥补BC策略最坏情况的不足。
本质——构造GS[]查询表。

BM算法的某一轮比对中,模式串与文本串对齐后,【在模式串中从右向左(从 模式串的末字符 向 模式串的首字符)】逐个比对单字符、并找到【模式串中最靠右(最靠近末尾)的】失配字符Y(此时,该轮比对的文本串中的失配字符为X)。模式串中的失配字符Y右侧(向模式串末尾)的后缀即【好后缀】,因为这个好后缀在该轮比对中与文本串局部匹配。GS策略利用【记忆力】,预先在模式串中找出 与好后缀相等的子串,将该子串(一步到位)右移至该轮比对的好后缀的局部匹配处,实现快速右移。
若 模式串中与好后缀相等的子串 有多个,则为不遗漏可能的成功匹配、避免回溯,右移量越小越好,故应选择 模式串中最靠近末尾的 子串。
若模式串中没有 与好后缀相等的子串,则将模式串中的(与 模式串中的好后缀的某个后缀 相等的)最长前缀 对齐于 文本串中的(在该轮比对中 与 模式串中的好后缀的该后缀 相对应的)子串,再进行下一轮比对。

P[j] -> MS[j]子串 -> ss[j]==MS[j]的长度
ss[]查询表 -> GS[]查询表。

GS[]查询表的时间复杂度 O(m)。

-

BM算法 平行引入 BC策略、GS策略后,BM_BC+GS算法:
最好情况的时间复杂度 = O(n/m) + O(|Σ|+m);
最坏情况的时间复杂度 = O(n+m) + O(|Σ|+m)。

-

时间复杂度:字符集规模|Σ|越小,单字符匹配成功概率(1/|Σ|)越大,串匹配的三种算法均越费时、效率越低。

O(n+m) <= BF算法 <= O(nm); BF算法 效率很低。
O(n+m) == KMP算法 == O(n+m); KMP算法 适用于 小规模字符集,性能优势更明显。
O(n/m) <= BM_BC算法 <= O(nm); BM_BC算法 仅适用于 大规模字符集。
O(n/m) <= BM_BC+GS算法 <= O(n+m); BM_BC+GS算法 性能最佳,大小规模字符集通用。

综合性能——
BM_BC+GS算法 优于 KMP算法 优于 BF算法。
BM_BC算法不稳定。

---

【4】Karp-Rabin算法:
本质——把 字符串 视为 整数。

凡物皆数——哥德尔编号法Godel numbering(任一有限维自然数向量 唯一对应于 某一自然数,由哥德尔编号公式、素数定义可知,该自然数也唯一对应于 上述向量。)
向量的元素个数(维数) == 互异素因子的个数

字符集是有限字符序列,若字符集共有s个互异字符,则把 0到s-1这s个自然数 依次作为 字符集中每个字符的编号,于是,任一文本串和模式串都是s进制串,即s进制数。每个字符串对应的s进制数 即 指纹【fingerprint】。

若字符集规模和模式串长度越大,则fingerprint越大,越易导致 数位溢出,串匹配的比对效率越低,需用hash function将fingerprint压缩为 在存储器支持的数位范围之内的hash值。

于是,每一轮比对都是判断 模式串的hash值 与 相应对齐位置的文本串子串的hash值 的自然数值是否相等,相等即“初步”匹配成功。但hash function不可避免会导致“多对一”冲突(即多个fingerprint对应同一个value),所以,在初步匹配成功(模式串的hash值 与 相应对齐位置的文本串子串的hash值 相等)后,还须对 模式串 与 该文本串子串 进行严格比对,才可确认整体匹配成功。

每用hash function计算一次hash值,时间复杂度似乎 = O(m)?于是,串匹配整体的时间复杂度似乎 = O((n-m)m) = O(nm)?此时,可利用(相邻两轮比对各自对应的)文本串中的子串 之间的高相关性(即 在相邻两轮比对中,(模式串分别对应的文本串子串的)fingerprint 之间的高相关性),可借助(第4章介绍过的)进制转换算法,在O(1)时间内迭代得到 下一轮比对中的文本串对齐子串的hash值,最终使串匹配的整体时间复杂度 = O(n)。

---------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值