diff --git a/Contents/06.String/01.String-Basic/01.String-Basic.md b/Contents/06.String/01.String-Basic/01.String-Basic.md index 9d0020f7..ea4b95d2 100644 --- a/Contents/06.String/01.String-Basic/01.String-Basic.md +++ b/Contents/06.String/01.String-Basic/01.String-Basic.md @@ -20,7 +20,7 @@ str = "Hello World" 在示例代码中,$str$ 是一个字符串的变量名称,`Hello World` 则是该字符串的值,字符串的长度为 $11$。该字符串的表示如下图所示: -![](https://qcdn.itcharge.cn/images/20220117141211.png) +![字符串](https://qcdn.itcharge.cn/images/20240511114722.png) 可以看出来,字符串和数组有很多相似之处。比如同样使用 **名称[下标]** 的方式来访问一个字符。 @@ -116,7 +116,7 @@ ASCII 编码可以解决以英语为主的语言,可是无法满足中文编 字符串的顺序存储结构如下图所示。 -![](https://qcdn.itcharge.cn/images/20220118151100.png) +![字符串的顺序存储](https://qcdn.itcharge.cn/images/20240511114747.png) 如上图所示,字符串的顺序存储中每一个字符元素都有自己的下标索引,下标所以从 $0$ 开始,到 $\text{字符串长度} - 1$ 结束。字符串中每一个「下标索引」,都有一个与之对应的「字符元素」。 @@ -130,7 +130,7 @@ ASCII 编码可以解决以英语为主的语言,可是无法满足中文编 字符串的链式存储结构图下图所示。 -![](https://qcdn.itcharge.cn/images/20220118152105.png) +![字符串的链式存储](https://qcdn.itcharge.cn/images/20240511114804.png) 如上图所示,字符串的链式存储将一组任意的存储单元串联在一起。链节点之间的逻辑关系是通过指针来间接反映的。 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md b/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md index 9ce5d689..bdabf56f 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md @@ -4,7 +4,7 @@ > > - **BF 算法思想**:对于给定文本串 $T$ 与模式串 $p$,从文本串的第一个字符开始与模式串 $p$ 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 $T$ 的第二个字符起重新和模式串 $p$ 进行比较。依次类推,直到模式串 $p$ 中每个字符依次与文本串 $T$ 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 -![](https://qcdn.itcharge.cn/images/20220205003716.png) +![朴素匹配算法](https://qcdn.itcharge.cn/images/20240511154456.png) ## 2. Brute Force 算法步骤 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md b/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md index 9adc6642..16239bff 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md @@ -32,17 +32,17 @@ RK 算法中的滚动哈希算法主要是利用了 **「Rabin fingerprint 思 比如 `"cat"` 的哈希值就可以表示为: -$\begin{aligned} Hash(cat) &= c \times 26 \times 26 + a \times 26 + t \times 1 \cr &= 2 \times 26 \times 26 + 0 \times 26 + 19 \times 1 \cr &= 1371 \end{aligned}$ +$$\begin{aligned} Hash(cat) &= c \times 26 \times 26 + a \times 26 + t \times 1 \cr &= 2 \times 26 \times 26 + 0 \times 26 + 19 \times 1 \cr &= 1371 \end{aligned}$$ 这种按位计算哈希值的哈希函数有一个特点:在计算相邻子串时,可以利用上一个子串的哈希值。 比如说 $cat$ 的相邻子串为 `"ate"`。按照刚才哈希函数计算,可以得出 `"ate"` 的哈希值为: -$\begin{aligned} Hash(ate) &= a \times 26 \times 26 + t \times 26 + e \times 1 \cr &= 0 \times 26 \times 26 + 19 \times 26 + 4 \times 1 \cr &= 498 \end{aligned}$ +$$\begin{aligned} Hash(ate) &= a \times 26 \times 26 + t \times 26 + e \times 1 \cr &= 0 \times 26 \times 26 + 19 \times 26 + 4 \times 1 \cr &= 498 \end{aligned}$$ 如果利用上一个子串 `"cat"` 的哈希值计算 `"ate"`,则 `"ate"` 的哈希值为: -$\begin{aligned} Hash(ate) &= (Hash(cat) - c \times 26 \times 26) * 26 + e \times 26 \cr &= (1371 - 2 \times 26 \times 26) \times 26 + 4 \times 1 \cr &= 498 \end{aligned}$ +$$\begin{aligned} Hash(ate) &= (Hash(cat) - c \times 26 \times 26) * 26 + e \times 26 \cr &= (1371 - 2 \times 26 \times 26) \times 26 + 4 \times 1 \cr &= 498 \end{aligned}$$ 可以看出,这两种方式计算出的哈希值是相同的。但是第二种计算方式不需要再遍历子串,只需要进行一位字符的计算即可得出整个子串的哈希值。这样每次计算子串哈希值的时间复杂度就降到了 $O(1)$。然后我们就可以通过滚动哈希算法快速计算出子串的哈希值了。 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md b/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md index 60e22075..c71bf049 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md @@ -8,7 +8,7 @@ 在朴素匹配算法的匹配过程中,我们分别用指针 $i$ 和指针 $j$ 指示文本串 $T$ 和模式串 $p$ 中当前正在对比的字符。当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,$j$ 回退到开始位置,$i$ 回退到之前匹配开始位置的下一个位置上,然后开启新一轮的匹配,如图所示。 -![](https://qcdn.itcharge.cn/images/20220205003716.png) +![朴素匹配算法](https://qcdn.itcharge.cn/images/20240511154456.png) 这样,在 Brute Force 算法中,如果从文本串 $T[i]$ 开始的这一趟字符串比较失败了,算法会直接开始尝试从 $T[i + 1]$ 开始比较。如果 $i$ 已经比较到了后边位置,则该操作相当于将指针 $i$ 进行了回退操作。 @@ -33,7 +33,7 @@ 那么我们就可以将文本串中的 $T[i + 5]$ 对准模式串中的 $p[2]$,继续进行对比。这样 $i$ 就不再需要回退了,可以一直向右移动匹配下去。在这个过程中,我们只需要将模式串 $j$ 进行回退操作即可。 -![](https://qcdn.itcharge.cn/images/20220205003701.png) +![KMP 匹配算法移动过程 1](https://qcdn.itcharge.cn/images/20240511155900.png) KMP 算法就是使用了这样的思路,对模式串 $p$ 进行了预处理,计算出一个 **「部分匹配表」**,用一个数组 $next$ 来记录。然后在每次失配发生时,不回退文本串的指针 $i$,而是根据「部分匹配表」中模式串失配位置 $j$ 的前一个位置的值,即 $next[j - 1]$ 的值来决定模式串可以向右移动的位数。 @@ -59,7 +59,7 @@ KMP 算法就是使用了这样的思路,对模式串 $p$ 进行了预处理 在之前的例子中,当 $p[5]$ 和 $T[i + 5]$ 匹配失败后,根据模式串失配位置 $j$ 的前一个位置的值,即 $next[4] = 2$,我们直接让 $T[i + 5]$ 直接对准了 $p[2]$,然后继续进行比对,如下图所示。 -![](https://qcdn.itcharge.cn/images/20220205003647.png) +![KMP 匹配算法移动过程 2](https://qcdn.itcharge.cn/images/20240511161310.png) **但是这样移动的原理是什么?** @@ -142,7 +142,7 @@ print(kmp("ababbbbaaabbbaaa", "bbbb")) - KMP 算法在构造前缀表阶段的时间复杂度为 $O(m)$,其中 $m$ 是模式串 $p$ 的长度。 - KMP 算法在匹配阶段,是根据前缀表不断调整匹配的位置,文本串的下标 $i$ 并没有进行回退,可以看出匹配阶段的时间复杂度是 $O(n)$,其中 $n$ 是文本串 $T$ 的长度。 -- 所以 KMP 整个算法的时间复杂度是 $O(n + m)$,相对于朴素匹配算法的 $O(n * m)$ 的时间复杂度,KMP 算法的效率有了很大的提升。 +- 所以 KMP 整个算法的时间复杂度是 $O(n + m)$,相对于朴素匹配算法的 $O(n \times m)$ 的时间复杂度,KMP 算法的效率有了很大的提升。 ## 参考资料 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md b/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md index dc54e5f2..d12149b9 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md @@ -2,11 +2,11 @@ > **Boyer Moore 算法**:简称为 BM 算法,是由它的两位发明者 Robert S. Boyer 和 J Strother Moore 的名字来命名的。BM 算法是他们在 1977 年提出的高效字符串搜索算法。在实际应用中,比 KMP 算法要快 3~5 倍。 > -> - **BM 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 +> - **BM 算法思想**:对于给定文本串 $T$ 与模式串 $p$,先对模式串 $p$ 进行预处理。然后在匹配的过程中,当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 BM 算法的精髓在于使用了两种不同的启发策略来计算后移位数:**「坏字符规则(The Bad Character Rule)」** 和 **「好后缀规则(The Good Suffix Shift Rule)」**。 -这两种启发策略的计算过程只与模式串 `p` 相关,而与文本串 `T` 无关。因此在对模式串 `p` 进行预处理时,可以预先生成「坏字符规则后移表」和「好后缀规则后移表」,然后在匹配的过程中,只需要比较一下两种策略下最大的后移位数进行后移即可。 +这两种启发策略的计算过程只与模式串 $p$ 相关,而与文本串 $T$ 无关。因此在对模式串 $p$ 进行预处理时,可以预先生成「坏字符规则后移表」和「好后缀规则后移表」,然后在匹配的过程中,只需要比较一下两种策略下最大的后移位数进行后移即可。 同时,还需要注意一点。BM 算法在移动模式串的时候和常规匹配算法一样是从左到右进行,但是在进行比较的时候是从右到左,即基于后缀进行比较。 @@ -16,25 +16,25 @@ BM 算法的精髓在于使用了两种不同的启发策略来计算后移位 ### 2.1 坏字符规则 -> **坏字符规则(The Bad Character Rule)**:当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,则称文本串 `T` 中这个失配字符为 **「坏字符」**,此时模式串 `p` 可以快速向右移动。 +> **坏字符规则(The Bad Character Rule)**:当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,则称文本串 $T$ 中这个失配字符为 **「坏字符」**,此时模式串 $p$ 可以快速向右移动。 「坏字符规则」的移动位数分为两种情况: -- **情况 1:坏字符出现在模式串 `p` 中**。 +- **情况 1:坏字符出现在模式串 $p$ 中**。 - 这种情况下,可将模式串中最后一次出现的坏字符与文本串中的坏字符对齐,如下图所示。 - **向右移动位数 = 坏字符在模式串中的失配位置 - 坏字符在模式串中最后一次出现的位置**。 -![](https://qcdn.itcharge.cn/images/20220128162720.png) +![情况 1:坏字符出现在模式串 p 中](https://qcdn.itcharge.cn/images/20240511164026.png) -- **情况 2:坏字符没有出现在模式串 `p` 中**。 +- **情况 2:坏字符没有出现在模式串 $p$ 中**。 - 这种情况下,可将模式串向右移动一位,如下图所示。 - **向右移动位数 = 坏字符在模式串中的失配位置 + 1**。 -![](https://qcdn.itcharge.cn/images/20220128162735.png) +![情况 2:坏字符没有出现在模式串 p 中](https://qcdn.itcharge.cn/images/20240511164048.png) ### 2.2 好后缀规则 -> **好后缀规则(The Good Suffix Shift Rule)**:当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,则称文本串 `T` 中已经匹配好的字符串为 **「好后缀」**,此时模式串 `p` 可以快速向右移动。 +> **好后缀规则(The Good Suffix Shift Rule)**:当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,则称文本串 $T$ 中已经匹配好的字符串为 **「好后缀」**,此时模式串 $p$ 可以快速向右移动。 「好后缀规则」的移动方式分为三种情况: @@ -42,92 +42,118 @@ BM 算法的精髓在于使用了两种不同的启发策略来计算后移位 - 这种情况下,移动模式串,让该子串和好后缀对齐即可。如果超过一个子串匹配上好后缀,则选择最右侧的子串对齐,如下图所示。 - **向右移动位数 = 好后缀的最后一个字符在模式串中的位置 - 匹配的子串最后一个字符出现的位置**。 -![](https://qcdn.itcharge.cn/images/20220128162537.png) +![情况 1:模式串中有子串匹配上好后缀](https://qcdn.itcharge.cn/images/20240511164101.png) - **情况 2:模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀**。 - 这种情况下,我们需要在模式串的前缀中寻找一个最长前缀,该前缀等于好后缀的后缀。找到该前缀后,让该前缀和好后缀的后缀对齐。 - **向右移动位数 = 好后缀的后缀的最后一个字符在模式串中的位置 - 最长前缀的最后一个字符出现的位置**。 -![](https://qcdn.itcharge.cn/images/20220128162600.png) +![情况 2:模式串中无子串匹配上好后缀, 但有最长前缀匹配好后缀的后缀](https://qcdn.itcharge.cn/images/20240511164112.png) - **情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配**。 - 可将模式串整个右移。 - **向右移动位数 = 模式串的长度**。 -![](https://qcdn.itcharge.cn/images/20220128162651.png) +![情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配](https://qcdn.itcharge.cn/images/20240511164124.png) ## 3. Boyer Moore 算法匹配过程示例 下面我们根据 J Strother Moore 教授给出的例子,先来介绍一下 BF 算法的匹配过程,顺便加深对 **「坏字符规则」** 和 **「好后缀规则」** 的理解。 -1. 假设文本串为 `"HERE IS A SIMPLE EXAMPLE"`,模式串为 `"EXAMPLE"`,如下图所示。 +::: tabs#Boyer-Moore -![](https://qcdn.itcharge.cn/images/20220127164130.png) +@tab <1> -2. 首先,令模式串与文本串的头部对齐,然后从模式串的尾部开始逐位比较,如下图所示。 +假设文本串为 `"HERE IS A SIMPLE EXAMPLE"`,模式串为 `"EXAMPLE"`,如下图所示。 -![](https://qcdn.itcharge.cn/images/20220127164140.png) +![Boyer Moore 算法步骤 1](https://qcdn.itcharge.cn/images/20220127164130.png) -可以看出来,`'S'` 与 `'E'` 不匹配。这时候,不匹配的字符 `'S'` 就被称为「坏字符(Bad Character)」,对应着模式串的第 `6` 位。并且 `'S'` 并不包含在模式串 `"EXAMPLE"` 中(相当于`'S'` 在模式串中最后一次出现的位置是 `-1`)。根据「坏字符规则」,可以把模式串直接向右移动 `6 - (-1) = 7` 位,即将文本串中 `'S'` 的后一位上。 +@tab <2> -3. 将模式串向右移动 `7` 位。然后依然从模式串尾部开始比较,发现 `'P'` 和 `'E'` 不匹配,则 `'P'` 是坏字符,如下图所示。 +首先,令模式串与文本串的头部对齐,然后从模式串的尾部开始逐位比较,如下图所示。 -![](https://qcdn.itcharge.cn/images/20220127164151.png) +![Boyer Moore 算法步骤 2](https://qcdn.itcharge.cn/images/20220127164140.png) -但是 `'P'` 包含在模式串 `"EXAMPLE"` 中,`'P'` 这个坏字符在模式串中的失配位置是第 `6` 位,并且在模式串中最后一次出现的位置是 `4`(编号从 `0` 开始)。 +可以看出来,`'S'` 与 `'E'` 不匹配。这时候,不匹配的字符 `'S'` 就被称为「坏字符(Bad Character)」,对应着模式串的第 $6$ 位。并且 `'S'` 并不包含在模式串 `"EXAMPLE"` 中(相当于`'S'` 在模式串中最后一次出现的位置是 $-1$)。根据「坏字符规则」,可以把模式串直接向右移动 $6 - (-1) = 7$​ 位,即将文本串中 `'S'` 的后一位上。 -4. 根据「坏字符规则」,可以将模式串直接向右移动 `6 - 4 = 2` 位,将文本串的 `'P'` 和模式串中的 `'P'` 对齐,如下图所示。 +@tab <3> -![](https://qcdn.itcharge.cn/images/20220127164202.png) +将模式串向右移动 $7$ 位。然后依然从模式串尾部开始比较,发现 `'P'` 和 `'E'` 不匹配,则 `'P'` 是坏字符,如下图所示。 -5. 我们继续从尾部开始逐位比较。先比较文本串的 `'E'` 和模式串的 `'E'`,如下图所示。可以看出文本串的 `'E'` 和模式串的 `'E'` 匹配,则 `"E"` 为好后缀,`"E"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 +![Boyer Moore 算法步骤 3](https://qcdn.itcharge.cn/images/20220127164151.png) -![](https://qcdn.itcharge.cn/images/20220127164212.png) +但是 `'P'` 包含在模式串 `"EXAMPLE"` 中,`'P'` 这个坏字符在模式串中的失配位置是第 $6$ 位,并且在模式串中最后一次出现的位置是 $4$(编号从 $0$​ 开始)。 -6. 继续比较前面一位,即文本串的 `'L'` 和模式串的 `'L'`,如下图所示。可以看出文本串的 `'L'` 和模式串的 `'L'` 匹配。则 `"LE"` 为好后缀,`"LE"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 +@tab <4> -![](https://qcdn.itcharge.cn/images/20220127164222.png) +根据「坏字符规则」,可以将模式串直接向右移动 $6 - 4 = 2$ 位,将文本串的 `'P'` 和模式串中的 `'P'` 对齐,如下图所示。 -7. 继续比较前面一位,即文本串中的 `'P'` 和模式串中的 `'P'`,如下图所示。可以看出文本串中的 `'P'` 和模式串中的 `'P'` 匹配,则 `"PLE"` 为好后缀,`"PLE"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 +![Boyer Moore 算法步骤 4](https://qcdn.itcharge.cn/images/20220127164202.png) -![](https://qcdn.itcharge.cn/images/20220127164232.png) +@tab <5> -8. 继续比较前面一位,即文本串中的 `'M'` 和模式串中的 `'M'`,如下图所示。可以看出文本串中的 `'M'` 和模式串中的 `'M'` 匹配,则 `"MPLE"` 为好后缀。`"MPLE"` 在模式串中的位置为 `6`(编号从 `0` 开始)。 +我们继续从尾部开始逐位比较。先比较文本串的 `'E'` 和模式串的 `'E'`,如下图所示。可以看出文本串的 `'E'` 和模式串的 `'E'` 匹配,则 `"E"` 为好后缀,`"E"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 -![](https://qcdn.itcharge.cn/images/20220127164241.png) +![Boyer Moore 算法步骤 5](https://qcdn.itcharge.cn/images/20220127164212.png) -9. 继续比较前面一位,即文本串中的 `'I'` 和模式串中的 `'A'`,如下图所示。可以看出文本串中的 `'I'` 和模式串中的 `'A'` 不匹配。 +@tab <6> -![](https://qcdn.itcharge.cn/images/20220127164251.png) +继续比较前面一位,即文本串的 `'L'` 和模式串的 `'L'`,如下图所示。可以看出文本串的 `'L'` 和模式串的 `'L'` 匹配。则 `"LE"` 为好后缀,`"LE"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 -此时,如果按照「坏字符规则」,模式串应该向右移动 `2 - (-1) = 3` 位。但是根据「好后缀规则」,我们还有更好的移动方法。 +![Boyer Moore 算法步骤 6](https://qcdn.itcharge.cn/images/20220127164222.png) -在好后缀 `"MPLE"` 和好后缀的后缀 `"PLE"`、`"LE"`、`"E"` 中,只有好后缀的后缀 `"E"` 和模式串中的前缀 `"E"` 相匹配,符合好规则的第二种情况。好后缀的后缀 `"E"` 的最后一个字符在模式串中的位置为 `6`,最长前缀 `"E"`的最后一个字符出现的位置为 `0`,则根据「好后缀规则」,可以将模式串直接向右移动 `6 - 0 = 6` 位。如下图所示。 +@tab <7> -![](https://qcdn.itcharge.cn/images/20220127164301.png) +继续比较前面一位,即文本串中的 `'P'` 和模式串中的 `'P'`,如下图所示。可以看出文本串中的 `'P'` 和模式串中的 `'P'` 匹配,则 `"PLE"` 为好后缀,`"PLE"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 -10. 继续从模式串的尾部开始逐位比较,如下图所示。 +![Boyer Moore 算法步骤 7](https://qcdn.itcharge.cn/images/20220127164232.png) -可以看出,`'P'` 与`'E'` 不匹配,`'P'` 是坏字符。根据「坏字符规则」,可以将模式串直接向右移动 `6 - 4 = 2` 位,如下图所示。 +@tab <8> -![](https://qcdn.itcharge.cn/images/20220127164312.png) +继续比较前面一位,即文本串中的 `'M'` 和模式串中的 `'M'`,如下图所示。可以看出文本串中的 `'M'` 和模式串中的 `'M'` 匹配,则 `"MPLE"` 为好后缀。`"MPLE"` 在模式串中的位置为 $6$(编号从 $0$ 开始)。 -11. 继续从模式串的尾部开始逐位比较,发现模式串全部匹配,于是搜索结束,返回模式串在文本串中的位置。 +![Boyer Moore 算法步骤 8](https://qcdn.itcharge.cn/images/20220127164241.png) + +@tab <9> + +继续比较前面一位,即文本串中的 `'I'` 和模式串中的 `'A'`,如下图所示。可以看出文本串中的 `'I'` 和模式串中的 `'A'` 不匹配。 + +![Boyer Moore 算法步骤 9-1](https://qcdn.itcharge.cn/images/20220127164251.png) + +此时,如果按照「坏字符规则」,模式串应该向右移动 $2 - (-1) = 3$ 位。但是根据「好后缀规则」,我们还有更好的移动方法。 + +在好后缀 `"MPLE"` 和好后缀的后缀 `"PLE"`、`"LE"`、`"E"` 中,只有好后缀的后缀 `"E"` 和模式串中的前缀 `"E"` 相匹配,符合好规则的第二种情况。好后缀的后缀 `"E"` 的最后一个字符在模式串中的位置为 $6$,最长前缀 `"E"`的最后一个字符出现的位置为 $0$,则根据「好后缀规则」,可以将模式串直接向右移动 $6 - 0 = 6$ 位。如下图所示。 + +![Boyer Moore 算法步骤 9-2](https://qcdn.itcharge.cn/images/20220127164301.png) + +@tab <10> + +继续从模式串的尾部开始逐位比较,如下图所示。 + +可以看出,`'P'` 与`'E'` 不匹配,`'P'` 是坏字符。根据「坏字符规则」,可以将模式串直接向右移动 $6 - 4 = 2$ 位,如下图所示。 + +![Boyer Moore 算法步骤 10](https://qcdn.itcharge.cn/images/20220127164312.png) + +@tab <11> + +继续从模式串的尾部开始逐位比较,发现模式串全部匹配,于是搜索结束,返回模式串在文本串中的位置。 + +::: ## 4. Boyer Moore 算法步骤 整个 BM 算法步骤描述如下: -1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 先对模式串 `p` 进行预处理,生成坏字符位置表 `bc_table` 和好后缀规则后移位数表 `gs_talbe`。 -3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始进行逐位比较。 - 1. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 相同,则继续比较前一位字符。 - 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - 2. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 不相同,则: - 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 `bad_move`。 - 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 `good_mode`。 - 3. 取两种移动距离的最大值,然后对模式串进行移动,即 `i += max(bad_move, good_move)`。 -4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 +1. 计算出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +2. 先对模式串 $p$ 进行预处理,生成坏字符位置表 $bc\underline{\hspace{0.5em}}table$ 和好后缀规则后移位数表 $gs\underline{\hspace{0.5em}}talbe$。 +3. 将模式串 $p$ 的头部与文本串 $T$ 对齐,将 $i$ 指向文本串开始位置,即 $i = 0$。$j$ 指向模式串末尾位置,即 $j = m - 1$,然后从模式串末尾位置开始进行逐位比较。 + 1. 如果文本串对应位置 $T[i + j]$ 上的字符与 $p[j]$ 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 $p$ 在文本串中的开始位置 $i$。 + 2. 如果文本串对应位置 $T[i + j]$ 上的字符与 $p[j]$ 不相同,则: + 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 $bad\underline{\hspace{0.5em}}move$。 + 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 $good\underline{\hspace{0.5em}}mode$。 + 3. 取两种移动距离的最大值,然后对模式串进行移动,即 $i += max(bad\underline{\hspace{0.5em}}move, good\underline{\hspace{0.5em}}move)$。 +4. 如果移动到末尾也没有找到匹配情况,则返回 $-1$。 ## 5. Boyer Moore 算法代码实现 @@ -137,11 +163,11 @@ BM 算法的匹配过程实现起来并不是很难,而整个算法实现的 生成坏字符位置表的代码实现比较简单。具体步骤如下: -- 使用一个哈希表 `bc_table`, `bc_table[bad_char]` 表示坏字符 `bad_char` 在模式串中出现的最右位置。 +- 使用一个哈希表 $bc\underline{\hspace{0.5em}}table$, $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$ 表示坏字符 $bad\underline{\hspace{0.5em}}char$ 在模式串中出现的最右位置。 -- 遍历模式串,以当前字符 `p[i]` 为键,所在位置下标为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现的最右侧位置。 +- 遍历模式串,以当前字符 $p[i]$ 为键,所在位置下标为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现的最右侧位置。 -这样如果在 BM 算法的匹配过程中,如果 `bad_char` 不在 `bc_table` 中时,可令 `bad_char` 在模式串中出现的最右侧位置为 `-1`。如果 `bad_char` 在 `bc_table` 中时,`bad_char` 在模式串中出现的最右侧位置就是 `bc_table[bad_char]`。这样就可以根据公式计算出可以向右移动的位数了。 +这样如果在 BM 算法的匹配过程中,如果 $bad\underline{\hspace{0.5em}}char$ 不在 $bc\underline{\hspace{0.5em}}table$ 中时,可令 $bad\underline{\hspace{0.5em}}char$ 在模式串中出现的最右侧位置为 $-1$。如果 $bad\underline{\hspace{0.5em}}char$ 在 $bc\underline{\hspace{0.5em}}table$ 中时,$bad\underline{\hspace{0.5em}}char$ 在模式串中出现的最右侧位置就是 $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$。这样就可以根据公式计算出可以向右移动的位数了。 生成坏字符位置表的代码如下: @@ -158,9 +184,9 @@ def generateBadCharTable(p: str): ### 5.2 生成好后缀规则后移位数表代码实现 -为了生成好后缀规则后移位数表,我们需要先定义一个后缀数组 `suffix`,其中 `suffix[i] = s` 表示为以下标 `i` 为结尾的子串与模式串后缀匹配的最大长度为 `s`。即满足 `p[i-s...i] == p[m-1-s, m-1]` 的最大长度为 `s`。 +为了生成好后缀规则后移位数表,我们需要先定义一个后缀数组 $suffix$,其中 $suffix[i] = s$ 表示为以下标 $i$ 为结尾的子串与模式串后缀匹配的最大长度为 $s$。即满足 $p[i-s...i] == p[m-1-s, m-1]$ 的最大长度为 $s$。 -构建 `suffix` 数组的代码如下: +构建 $suffix$ 数组的代码如下: ```python # 生成 suffix 数组 @@ -176,21 +202,21 @@ def generageSuffixArray(p: str): return suffix ``` -有了 `suffix` 数组,我们就可以在此基础上定义好后缀规则后移位数表 `gs_list`。我们使用一个数组来表示好后缀规则后移位数表。其中 `gs_list[j]` 表示在 `j` 下标处遇到坏字符时,可根据好规则向右移动的距离。 +有了 $suffix$ 数组,我们就可以在此基础上定义好后缀规则后移位数表 $gs\underline{\hspace{0.5em}}list$。我们使用一个数组来表示好后缀规则后移位数表。其中 $gs\underline{\hspace{0.5em}}list[j]$ 表示在 $j$ 下标处遇到坏字符时,可根据好规则向右移动的距离。 -由 `2.2 好后缀规则` 中可知,好后缀规则的移动方式可以分为三种情况。 +由 「2.2 好后缀规则」 中可知,好后缀规则的移动方式可以分为三种情况。 - 情况 1:模式串中有子串匹配上好后缀。 - 情况 2:模式串中无子串匹配上好后缀,但有最长前缀匹配好后缀的后缀。 - 情况 3:模式串中无子串匹配上好后缀,也找不到前缀匹配。 -这 3 种情况中,情况 2 和情况 3 可以合并,因为情况 3 可以看做是匹配到的最长前缀长度为 `0`。而如果遇到一个坏字符同时满足多种情况,则我们应该选择满足情况中最小的移动距离才不会漏掉可能匹配的情况,比如说当模式串中既有子串可以匹配上好后缀,又有前缀可以匹配上好后缀的后缀,则应该按照前者的方式移动模式串。 +这 3 种情况中,情况 2 和情况 3 可以合并,因为情况 3 可以看做是匹配到的最长前缀长度为 $0$。而如果遇到一个坏字符同时满足多种情况,则我们应该选择满足情况中最小的移动距离才不会漏掉可能匹配的情况,比如说当模式串中既有子串可以匹配上好后缀,又有前缀可以匹配上好后缀的后缀,则应该按照前者的方式移动模式串。 -- 为了得到精确的 `gs_list[j]`,我们可以先假定所有情况都为情况 3,即 `gs_list[i] = m`。 -- 然后通过后缀和前缀匹配的方法,更新情况 2 下 `gs_list` 中坏字符位置处的值,即 `gs_list[j] = m - 1 - i`,其中 `j` 是好后缀前的坏字符位置,`i` 是最长前缀的末尾位置,`m - 1 - i` 是可向右移动的距离。 -- 最后再计算情况 1 下 `gs_list` 中坏字符位置处的值,更新在好后缀的左端点处(`m - 1 - suffix[i]` 处)遇到坏字符可向后移动位数,即 `gs_list[m - 1 - suffix[i]] = m - 1 - i`。 +- 为了得到精确的 $gs\underline{\hspace{0.5em}}list[j]$​,我们可以先假定所有情况都为情况 3,即 $gs\underline{\hspace{0.5em}}list[i] = m$​。 +- 然后通过后缀和前缀匹配的方法,更新情况 2 下 $gs\underline{\hspace{0.5em}}list$ 中坏字符位置处的值,即 $gs\underline{\hspace{0.5em}}list[j] = m - 1 - i$,其中 $j$ 是好后缀前的坏字符位置,$i$ 是最长前缀的末尾位置,$m - 1 - i$ 是可向右移动的距离。 +- 最后再计算情况 1 下 $gs\underline{\hspace{0.5em}}list$ 中坏字符位置处的值,更新在好后缀的左端点处($m - 1 - suffix[i]$ 处)遇到坏字符可向后移动位数,即 $gs\underline{\hspace{0.5em}}list[m - 1 - suffix[i]] = m - 1 - i$。 -生成好后缀规则后移位数表 `gs_list` 代码如下: +生成好后缀规则后移位数表 $gs\underline{\hspace{0.5em}}list$ 代码如下: ```python # 生成好后缀规则后移位数表 @@ -293,9 +319,9 @@ print(boyerMoore("", "")) ## 6. Boyer Moore 算法分析 - BM 算法在预处理阶段的时间复杂度为 $O(n + \sigma)$,其中 $\sigma$ 是字符集的大小。 -- BM 算法在搜索阶段最好情况是每次匹配时,模式串 `p` 中不存在与文本串 `T` 中第一个匹配的字符。这时的时间复杂度为 $O(n / m)$。 -- BM 算法在搜索阶段最差情况是文本串 `T` 中有多个重复的字符,并且模式串 `p` 中有 `m - 1` 个相同字符前加一个不同的字符组成。这时的时间复杂度为 $O(m * n)$。 -- 当模式串 `p` 是非周期性的,在最坏情况下,BM 算法最多需要进行 $3 * n$ 次字符比较操作。 +- BM 算法在搜索阶段最好情况是每次匹配时,模式串 $p$ 中不存在与文本串 $T$ 中第一个匹配的字符。这时的时间复杂度为 $O(n / m)$。 +- BM 算法在搜索阶段最差情况是文本串 $T$ 中有多个重复的字符,并且模式串 $p$ 中有 $m - 1$ 个相同字符前加一个不同的字符组成。这时的时间复杂度为 $O(m * n)$。 +- 当模式串 $p$ 是非周期性的,在最坏情况下,BM 算法最多需要进行 $3 * n$ 次字符比较操作。 ## 参考资料 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md b/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md index be831fc1..fa272ee8 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md @@ -2,37 +2,37 @@ > **Horspool 算法**:是一种在字符串中查找子串的算法,它是由 Nigel Horspool 教授于 1980 年出版的,是首个对 Boyer Moore 算法进行简化的算法。 > -> - **Horspool 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 +> - **Horspool 算法思想**:对于给定文本串 $T$ 与模式串 $p$,先对模式串 $p$ 进行预处理。然后在匹配的过程中,当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 -可以看出,Horspool 算法思想和 Boyer Moore 算法思想是一致的。Horspool 算法是在 Boyer Moore 算法思想基础上改进了「坏字符规则」。当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,可以模式串 `p` 快速向右移动。 +可以看出,Horspool 算法思想和 Boyer Moore 算法思想是一致的。Horspool 算法是在 Boyer Moore 算法思想基础上改进了「坏字符规则」。当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,可以模式串 $p$ 快速向右移动。 遇到不匹配字符时,可以根据以下两种情况向右快速进行移动: -- **情况 1:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符 `T[i + m - 1]` 出现在模式串 `p` 中**。 - - 这种情况下,可将 `T[i + m - 1]` 与模式串中最后一次出现的该字符对齐,如下图所示。 +- **情况 1:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符 $T[i + m - 1]$ 出现在模式串 $p$ 中**。 + - 这种情况下,可将 $T[i + m - 1]$ 与模式串中最后一次出现的该字符对齐,如下图所示。 - **向右移动位数 = 模式串最后一个字符的位置 - T[i + m - 1] 在模式串中最后一次出现的位置**。 - 注意:模式串最后一个字符的位置其实就是「模式串长度 - 1」。 -![](https://qcdn.itcharge.cn/images/20220128164320.png) +![Horspool 算法情况 1](https://qcdn.itcharge.cn/images/20240511165106.png) -- **情况 2:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符 `T[i + m - 1]` 没有出现在模式串 `p` 中**。 +- **情况 2:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符 $T[i + m - 1]$ 没有出现在模式串 $p$ 中**。 - 这种情况下,可将模式串整个右移,如下图所示。 - **向右移动位数 = 整个模式串长度**。 -![](https://qcdn.itcharge.cn/images/20220128164333.png) +![Horspool 算法情况 2](https://qcdn.itcharge.cn/images/20240511165122.png) ## 2. Horspool 算法步骤 整个 Horspool 算法步骤描述如下: -1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 -3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 - 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 - 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: - 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 -4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 +1. 计算出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +2. 先对模式串 $p$ 进行预处理,生成后移位数表 $bc\underline{\hspace{0.5em}}table$。 +3. 将模式串 $p$ 的头部与文本串 $T$ 对齐,将 $i$ 指向文本串开始位置,即 $i = 0$。$j$ 指向模式串末尾位置,即 $j = m - 1$,然后从模式串末尾位置开始比较。 + 1. 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 $p$ 在文本串中的开始位置 $i$。 + 2. 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 不同,则: + 1. 根据后移位数表 $bc\underline{\hspace{0.5em}}table$ 和模式串末尾位置对应的文本串上的字符 $T[i + m - 1]$ ,计算出可移动距离 $bc\underline{\hspace{0.5em}}table[T[i + m - 1]]$,然后将模式串进行后移。 +4. 如果移动到末尾也没有找到匹配情况,则返回 $-1$。 ## 3. Horspool 算法代码实现 @@ -40,10 +40,10 @@ 生成后移位数表的代码实现比较简单,跟 Boyer Moore 算法中生成坏字符位置表的代码差不多。具体步骤如下: -- 使用一个哈希表 `bc_table`, `bc_table[bad_char]` 表示表示遇到坏字符可以向右移动的距离。 -- 遍历模式串,以当前字符 `p[i]` 为键,可以向右移动的距离(`m - 1 - i`)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 +- 使用一个哈希表 $bc\underline{\hspace{0.5em}}table$, $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$ 表示表示遇到坏字符可以向右移动的距离。 +- 遍历模式串,以当前字符 $p[i]$ 为键,可以向右移动的距离($m - 1 - i$)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 -如果在 Horspool 算法的匹配过程中,如果 `T[i + m - 1]` 不在 `bc_table` 中时,可令其为 `m`,表示可以将模式串整个右移。如果 `T[i + m - 1]` 在 `bc_table` 中时,可移动距离就是 `bc_table[T[i + m - 1]]` 。这样就能计算出可以向右移动的位数了。 +如果在 Horspool 算法的匹配过程中,如果 $T[i + m - 1]$ 不在 $bc\underline{\hspace{0.5em}}table$ 中时,可令其为 $m$,表示可以将模式串整个右移。如果 $T[i + m - 1]$ 在 $bc\underline{\hspace{0.5em}}table$ 中时,可移动距离就是 $bc\underline{\hspace{0.5em}}table[T[i + m - 1]]$ 。这样就能计算出可以向右移动的位数了。 生成后移位数表的代码如下: diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md b/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md index 1b37ef55..984b72c8 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/06.String-Sunday.md @@ -2,37 +2,37 @@ **「Sunday 算法」** 是一种在字符串中查找子串的算法,是 Daniel M.Sunday 于1990年提出的字符串模式匹配算法。 -> **Sunday 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 +> **Sunday 算法思想**:对于给定文本串 $T$ 与模式串 $p$,先对模式串 $p$ 进行预处理。然后在匹配的过程中,当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 -Sunday 算法思想跟 Boyer Moore 算法思想类似。不同的是,Sunday 算法匹配顺序是从左向右,并且在模式串 `p` 匹配失败时关注的是文本串 `T` 中参加匹配的末尾字符的下一位字符。当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,可以将模式串 `p` 快速向右移动。 +Sunday 算法思想跟 Boyer Moore 算法思想类似。不同的是,Sunday 算法匹配顺序是从左向右,并且在模式串 $p$ 匹配失败时关注的是文本串 $T$ 中参加匹配的末尾字符的下一位字符。当文本串 $T$ 中某个字符跟模式串 $p$ 的某个字符不匹配时,可以将模式串 $p$ 快速向右移动。 遇到不匹配字符时,可以根据以下两种情况向右快速进行移动: -- **情况 1:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符下一个位置的字符 `T[i + m]` 出现在模式串 `p` 中**。 - - 这种情况下,可将`T[i + m]` 与模式串中最后一次出现的该字符对齐,如下图所示。 - - **向右移动位数 = 文本串 `T` 中与模式串 `p` 尾部位置的下一个位置 - T[i + m] 在模式串中最后一次出现的位置**。 - - 注意:文本串 `T` 中与模式串 `p` 尾部位置的下一个位置其实就是「模式串长度」。 +- **情况 1:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符下一个位置的字符 $T[i + m]$ 出现在模式串 $p$ 中**。 + - 这种情况下,可将$T[i + m]$ 与模式串中最后一次出现的该字符对齐,如下图所示。 + - **向右移动位数 = 文本串 $T$ 中与模式串 $p$ 尾部位置的下一个位置 $T[i + m]$ 在模式串中最后一次出现的位置**。 + - 注意:文本串 $T$ 中与模式串 $p$ 尾部位置的下一个位置其实就是「模式串长度」。 -![](https://qcdn.itcharge.cn/images/20220128165756.png) +![Sunday 算法情况 1](https://qcdn.itcharge.cn/images/20240511165526.png) -- **情况 2:文本串 `T` 中与模式串 `p` 尾部字符 `p[m - 1]` 对应的字符下一个位置的字符 `T[i + m]` 没有出现在模式串 `p` 中**。 +- **情况 2:文本串 $T$ 中与模式串 $p$ 尾部字符 $p[m - 1]$ 对应的字符下一个位置的字符 $T[i + m]$ 没有出现在模式串 $p$ 中**。 - 这种情况下,可将模式串整个右移,如下图所示。 - **向右移动位数 = 整个模式串长度 + 1**。 -![](https://qcdn.itcharge.cn/images/20220128165811.png) +![Sunday 算法情况 2](https://qcdn.itcharge.cn/images/20240511165540.png) ## 2. Sunday 算法步骤 整个 Horspool 算法步骤描述如下: -- 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -- 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 -- 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串开始,即 `j = 0`,然后从模式串开始位置开始比较。 - - 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较后一位字符。 - - 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - - 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: - - 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m]` ,计算出可移动距离 `bc_table[T[i + m]]`,然后将模式串进行后移。 -- 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 +- 计算出文本串 $T$ 的长度为 $n$,模式串 $p$ 的长度为 $m$。 +- 先对模式串 $p$ 进行预处理,生成后移位数表 $bc\underline{\hspace{0.5em}}table$。 +- 将模式串 $p$ 的头部与文本串 $T$ 对齐,将 $i$ 指向文本串开始位置,即 $i = 0$。$j$ 指向模式串开始,即 $j = 0$,然后从模式串开始位置开始比较。 + - 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 相同,则继续比较后一位字符。 + - 如果模式串全部匹配完毕,则返回模式串 $p$ 在文本串中的开始位置 $i$。 + - 如果文本串对应位置的字符 $T[i + j]$ 与模式串对应字符 $p[j]$ 不同,则: + - 根据后移位数表 $bc\underline{\hspace{0.5em}}table$ 和模式串末尾位置对应的文本串上的字符 $T[i + m]$ ,计算出可移动距离 $bc\underline{\hspace{0.5em}}table[T[i + m]]$,然后将模式串进行后移。 +- 如果移动到末尾也没有找到匹配情况,则返回 $-1$。 ## 3. Sunday 算法代码实现 @@ -40,10 +40,10 @@ Sunday 算法思想跟 Boyer Moore 算法思想类似。不同的是,Sunday 生成后移位数表的代码实现比较简单,跟 Horspool 算法中生成后移位数表的代码差不多。具体步骤如下: -- 使用一个哈希表 `bc_table`, `bc_table[bad_char]` 表示表示遇到坏字符可以向右移动的距离。 -- 遍历模式串,以当前字符 `p[i]` 为键,可以向右移动的距离(`m - i`)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 +- 使用一个哈希表 $bc\underline{\hspace{0.5em}}table$, $bc\underline{\hspace{0.5em}}table[bad\underline{\hspace{0.5em}}char]$ 表示表示遇到坏字符可以向右移动的距离。 +- 遍历模式串,以当前字符 $p[i]$ 为键,可以向右移动的距离($m - i$)为值存入字典中。如果出现重复字符,则新的位置下标值会将之前存放的值覆盖掉。这样哈希表中存放的就是该字符在模式串中出现最右侧位置上的可向右移动的距离。 -如果在 Sunday 算法的匹配过程中,如果 `T[i + m]` 不在 `bc_table` 中时,可令其为 `m + 1`,表示可以将模式串整个右移到上一次匹配末尾后边两个位置上。如果 `T[i + m]` 在 `bc_table` 中时,可移动距离就是 `bc_table[T[i + m]]` 。这样就能计算出可以向右移动的位数了。 +如果在 Sunday 算法的匹配过程中,如果 $T[i + m]$ 不在 $bc\underline{\hspace{0.5em}}table$ 中时,可令其为 $m + 1$,表示可以将模式串整个右移到上一次匹配末尾后边两个位置上。如果 $T[i + m]$ 在 $bc\underline{\hspace{0.5em}}table$ 中时,可移动距离就是 $bc\underline{\hspace{0.5em}}table[T[i + m]]$ 。这样就能计算出可以向右移动的位数了。 生成后移位数表的代码如下: