Skip to content

Commit 642d840

Browse files
committed
更新 03.01 哈希表、03.02 字符串与字符串匹配、03.03 二叉树、03.04 二叉搜索树 相关图片、图片标题
1 parent 2b3512e commit 642d840

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+50
-50
lines changed

docs/ch01/01.01/01.01.01-Data-Structures-Algorithms.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
> **解决方法**
134134
>
135135
> 1. 用计算器从 $1$ 开始,不断向右依次加上 $2$,再加上 $3$,...,依次加到 $100$,得出结果为 $5050$。
136-
> 2. 根据高斯求和公式:**和 = (首项 + 末项) × 项数 ÷ 2**,直接算出结果为:$\frac{(1+100) \times 100}{2} = 5050$。
136+
> 2. 根据高斯求和公式:**和 = (首项 + 末项) × 项数 ÷ 2**,直接算出结果为:$\frac{(1 + 100) \times 100}{2} = 5050$。
137137
138138
- 示例 3:
139139

docs/ch03/03.01/03.01.01-Hash-Table.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
哈希表的原理示例图如下所示:
1515

16-
![](../../images/20220114120000.png)
16+
![哈希表](../../images/202405092317578.png)
1717

1818
在上图例子中,我们使用 $value = Hash(key) = key // 1000$ 作为哈希函数。$//$ 符号代表整除。我们以这个例子来说明一下哈希表的插入和查找策略。
1919

@@ -27,7 +27,7 @@
2727

2828
比如为了查找 **「赞」** 这个字的具体意思,我们在字典中根据这个字的拼音索引 `zan`,查找到对应的页码为 $599$。然后我们就可以翻到字典的第 $599$ 页查看 **「赞」** 字相关的解释了。
2929

30-
![](../../images/20220111174223.png)
30+
![查字典](../../images/20220111174223.png)
3131

3232
在这个例子中:
3333

@@ -105,7 +105,7 @@ $343246_{13} = 3 \times 13^5 + 4 \times 13^4 + 3 \times 13^3 + 2 \times 13^2 + 4
105105

106106
> **开放地址法(Open Addressing)**:指的是将哈希表中的「空地址」向处理冲突开放。当哈希表未满时,处理冲突时需要尝试另外的单元,直到找到空的单元为止。
107107
108-
当发生冲突时,开放地址法按照下面的方法求得后继哈希地址:$H(i) = (Hash(key) + F(i)) \mod m$,$i = 1, 2, 3, ..., n (n \le m - 1)$。
108+
当发生冲突时,开放地址法按照下面的方法求得后继哈希地址:$H(i) = (Hash(key) + F(i)) \mod m$,$i = 1, 2, 3, ..., n (n m - 1)$。
109109
- $H(i)$ 是在处理冲突中得到的地址序列。即在第 1 次冲突($i = 1$)时经过处理得到一个新地址 $H(1)$,如果在 $H(1)$ 处仍然发生冲突($i = 2$)时经过处理时得到另一个新地址 $H(2)$ …… 如此下去,直到求得的 $H(n)$ 不再发生冲突。
110110
- $Hash(key)$ 是哈希函数,$m$ 是哈希表表长,对哈希表长取余的目的是为了使得到的下一个地址一定落在哈希表中。
111111
- $F(i)$ 是冲突解决方法,取法可以有以下几种:
@@ -122,7 +122,7 @@ $343246_{13} = 3 \times 13^5 + 4 \times 13^4 + 3 \times 13^3 + 2 \times 13^2 + 4
122122

123123
使用这三种方法处理冲突的结果如下图所示:
124124

125-
![](../../images/20220115162728.png)
125+
![开放地址法](../../images/202405092318809.png)
126126

127127
### 3.2 链地址法
128128

@@ -138,7 +138,7 @@ $343246_{13} = 3 \times 13^5 + 4 \times 13^4 + 3 \times 13^3 + 2 \times 13^2 + 4
138138

139139
举个例子来说明如何使用链地址法处理冲突。假设现在要存入的关键字集合 $keys = [88, 60, 65, 69, 90, 39, 07, 06, 14, 44, 52, 70, 21, 45, 19, 32]$。再假定哈希函数为 $Hash(key) = key \mod 13$,哈希表的表长 $m = 13$,哈希地址范围为 $[0, m - 1]$。将这些关键字使用链地址法处理冲突,并按顺序加入哈希表中(图示为插入链表表尾位置),最终得到的哈希表如下图所示。
140140

141-
![](../../images/20220115182535.png)
141+
![链地址法](../../images/202405092319327.png)
142142

143143
相对于开放地址法,采用链地址法处理冲突要多占用一些存储空间(主要是链节点占用空间)。但它可以减少在进行插入和查找具有相同哈希地址的关键字的操作过程中的平均查找长度。这是因为在链地址法中,待比较的关键字都是具有相同哈希地址的元素,而在开放地址法中,待比较的关键字不仅包含具有相同哈希地址的元素,而且还包含哈希地址不相同的元素。
144144

@@ -166,4 +166,4 @@ $343246_{13} = 3 \times 13^5 + 4 \times 13^4 + 3 \times 13^3 + 2 \times 13^2 + 4
166166
- 【博文】[散列表(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/64233)
167167
- 【书籍】数据结构(C 语言版)- 严蔚敏 著
168168
- 【书籍】数据结构教程(第 3 版)- 唐发根 著
169-
- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著
169+
- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著

docs/ch03/03.02/03.02.01-String-Basic.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ str = "Hello World"
2222

2323
在示例代码中,$str$ 是一个字符串的变量名称,`Hello World` 则是该字符串的值,字符串的长度为 $11$。该字符串的表示如下图所示:
2424

25-
![](../../images/20220117141211.png)
25+
![字符串](../../images/20240511114722.png)
2626

2727
可以看出来,字符串和数组有很多相似之处。比如同样使用 **名称[下标]** 的方式来访问一个字符。
2828

@@ -118,7 +118,7 @@ ASCII 编码可以解决以英语为主的语言,可是无法满足中文编
118118

119119
字符串的顺序存储结构如下图所示。
120120

121-
![](../../images/20220118151100.png)
121+
![字符串的顺序存储](../../images/20240511114747.png)
122122

123123
如上图所示,字符串的顺序存储中每一个字符元素都有自己的下标索引,下标所以从 $0$ 开始,到 $\text{字符串长度} - 1$ 结束。字符串中每一个「下标索引」,都有一个与之对应的「字符元素」。
124124

@@ -132,7 +132,7 @@ ASCII 编码可以解决以英语为主的语言,可是无法满足中文编
132132

133133
字符串的链式存储结构图下图所示。
134134

135-
![](../../images/20220118152105.png)
135+
![字符串的链式存储](../../images/20240511114804.png)
136136

137137
如上图所示,字符串的链式存储将一组任意的存储单元串联在一起。链节点之间的逻辑关系是通过指针来间接反映的。
138138

@@ -191,4 +191,4 @@ ASCII 编码可以解决以英语为主的语言,可是无法满足中文编
191191
- 【博文】[字符串和编码 - 廖雪峰的官方网站 ](https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896)
192192
- 【文章】[数组和字符串 - LeetBook - 力扣](https://leetcode.cn/leetbook/read/array-and-string/c9lnm/)
193193
- 【文章】[字符串部分简介 - OI Wiki](https://oi-wiki.org/string/)
194-
- 【文章】[解密 Python 中字符串的底层实现,以及相关操作 - 古明地盆 - 博客园](https://www.cnblogs.com/traditional/p/13455962.html)
194+
- 【文章】[解密 Python 中字符串的底层实现,以及相关操作 - 古明地盆 - 博客园](https://www.cnblogs.com/traditional/p/13455962.html)

docs/ch03/03.02/03.02.04-String-Brute-Force.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
>
77
> - **BF 算法思想**:对于给定文本串 $T$ 与模式串 $p$,从文本串的第一个字符开始与模式串 $p$ 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 $T$ 的第二个字符起重新和模式串 $p$ 进行比较。依次类推,直到模式串 $p$ 中每个字符依次与文本串 $T$ 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。
88
9-
![](../../images/20220205003716.png)
9+
![朴素匹配算法](../../images/20240511154456.png)
1010

1111
## 2. Brute Force 算法步骤
1212

@@ -52,4 +52,4 @@ BF 算法非常简单,容易理解,但其效率很低。主要是因为在
5252
- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著
5353
- 【文章】[动画:什么是 BF 算法 ?- 吴师兄学编程](https://www.cxyxiaowu.com/560.html)
5454
- 【文章】[BF 算法(普通模式匹配算法)及 C 语言实现 - 数据结构与算法教程](http://data.biancheng.net/view/12.html)
55-
- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187)
55+
- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187)

docs/ch03/03.02/03.02.05-String-Rabin-Karp.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
2. 通过滚动哈希算法求出模式串 $p$ 的哈希值 $hash\underline{\hspace{0.5em}}p$。
1515
3. 再通过滚动哈希算法对文本串 $T$ 中 $n - m + 1$ 个子串分别求哈希值 $hash\underline{\hspace{0.5em}}t$。
1616
4. 然后逐个与模式串的哈希值比较大小。
17-
1. 如果当前子串的哈希值 $hash\underline{\hspace{0.5em}}t$ 与模式串的哈希值 $hash_p$ 不同,则说明两者不匹配,则继续向后匹配。
18-
2. 如果当前子串的哈希值 $hash\underline{\hspace{0.5em}}t$ 与模式串的哈希值 $hash_p$ 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。
17+
1. 如果当前子串的哈希值 $hash\underline{\hspace{0.5em}}t$ 与模式串的哈希值 $hash\underline{\hspace{0.5em}}p$ 不同,则说明两者不匹配,则继续向后匹配。
18+
2. 如果当前子串的哈希值 $hash\underline{\hspace{0.5em}}t$ 与模式串的哈希值 $hash\underline{\hspace{0.5em}}p$ 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。
1919
1. 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。
2020
2. 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。
2121
5. 比较到末尾,如果仍未成功匹配,则说明文本串 $T$ 中不包含模式串 $p$,方法返回 $-1$。
@@ -34,17 +34,17 @@ RK 算法中的滚动哈希算法主要是利用了 **「Rabin fingerprint 思
3434

3535
比如 `"cat"` 的哈希值就可以表示为:
3636

37-
$\begin{align} 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{align}$
37+
$$\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}$$
3838

3939
这种按位计算哈希值的哈希函数有一个特点:在计算相邻子串时,可以利用上一个子串的哈希值。
4040

4141
比如说 $cat$ 的相邻子串为 `"ate"`。按照刚才哈希函数计算,可以得出 `"ate"` 的哈希值为:
4242

43-
$\begin{align} 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{align}$
43+
$$\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}$$
4444

4545
如果利用上一个子串 `"cat"` 的哈希值计算 `"ate"`,则 `"ate"` 的哈希值为:
4646

47-
$\begin{align} 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{align}$
47+
$$\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}$$
4848

4949
可以看出,这两种方式计算出的哈希值是相同的。但是第二种计算方式不需要再遍历子串,只需要进行一位字符的计算即可得出整个子串的哈希值。这样每次计算子串哈希值的时间复杂度就降到了 $O(1)$。然后我们就可以通过滚动哈希算法快速计算出子串的哈希值了。
5050

@@ -96,11 +96,11 @@ def rabinKarp(T: str, p: str, d, q) -> int:
9696

9797
RK 算法可以看做是 BF 算法的一种改进。在 BF 算法中,每一个字符都需要进行比较。而在 RK 算法中,判断模式串的哈希值与每个子串的哈希值之间是否相等的时间复杂度为 $O(1)$。总共需要比较 $n - m + 1$ 个子串的哈希值,所以 RK 算法的整体时间复杂度为 $O(n)$。跟 BF 算法相比,RK 算法的效率提高了很多。
9898

99-
但是如果存在冲突的情况下,算法的效率会降低。最坏情况是每一次比较模式串的哈希值和子串的哈希值时都相等,但是每一次都会出现冲突,那么每一次都需要验证模式串和子串每个字符是否完全相同,那么总的比较次数就是 $m \times (n - m + 1) $,时间复杂度就会退化为 $O(m \times n)$。
99+
但是如果存在冲突的情况下,算法的效率会降低。最坏情况是每一次比较模式串的哈希值和子串的哈希值时都相等,但是每一次都会出现冲突,那么每一次都需要验证模式串和子串每个字符是否完全相同,那么总的比较次数就是 $m \times (n - m + 1)$,时间复杂度就会退化为 $O(m \times n)$。
100100

101101
## 参考资料
102102

103103
- 【书籍】数据结构与算法 Python 语言描述 - 裘宗燕 著
104104
- 【文章】[字符串匹配基础(上)- 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/71187)
105105
- 【文章】[字符串匹配算法 - Rabin Karp 算法 - coolcao 的小站](https://coolcao.com/2020/08/20/rabin-karp/)
106-
- 【问答】[string - Python: Rabin-Karp algorithm hashing - Stack Overflow](https://stackoverflow.com/questions/22216948/python-rabin-karp-algorithm-hashing)
106+
- 【问答】[string - Python: Rabin-Karp algorithm hashing - Stack Overflow](https://stackoverflow.com/questions/22216948/python-rabin-karp-algorithm-hashing)

docs/ch03/03.02/03.02.06-String-KMP.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
在朴素匹配算法的匹配过程中,我们分别用指针 $i$ 和指针 $j$ 指示文本串 $T$ 和模式串 $p$ 中当前正在对比的字符。当发现文本串 $T$ 的某个字符与模式串 $p$ 不匹配的时候,$j$ 回退到开始位置,$i$ 回退到之前匹配开始位置的下一个位置上,然后开启新一轮的匹配,如图所示。
1212

13-
![](../../images/20220205003716.png)
13+
![朴素匹配算法](../../images/20240511154456.png)
1414

1515
这样,在 Brute Force 算法中,如果从文本串 $T[i]$ 开始的这一趟字符串比较失败了,算法会直接开始尝试从 $T[i + 1]$ 开始比较。如果 $i$ 已经比较到了后边位置,则该操作相当于将指针 $i$ 进行了回退操作。
1616

@@ -35,7 +35,7 @@
3535

3636
那么我们就可以将文本串中的 $T[i + 5]$ 对准模式串中的 $p[2]$,继续进行对比。这样 $i$ 就不再需要回退了,可以一直向右移动匹配下去。在这个过程中,我们只需要将模式串 $j$ 进行回退操作即可。
3737

38-
![](../../images/20220205003701.png)
38+
![KMP 匹配算法移动过程 1](../../images/20240511155900.png)
3939

4040
KMP 算法就是使用了这样的思路,对模式串 $p$ 进行了预处理,计算出一个 **「部分匹配表」**,用一个数组 $next$ 来记录。然后在每次失配发生时,不回退文本串的指针 $i$,而是根据「部分匹配表」中模式串失配位置 $j$ 的前一个位置的值,即 $next[j - 1]$ 的值来决定模式串可以向右移动的位数。
4141

@@ -61,7 +61,7 @@ KMP 算法就是使用了这样的思路,对模式串 $p$ 进行了预处理
6161

6262
在之前的例子中,当 $p[5]$ 和 $T[i + 5]$ 匹配失败后,根据模式串失配位置 $j$ 的前一个位置的值,即 $next[4] = 2$,我们直接让 $T[i + 5]$ 直接对准了 $p[2]$,然后继续进行比对,如下图所示。
6363

64-
![](../../images/20220205003647.png)
64+
![KMP 匹配算法移动过程 2](../../images/20240511161310.png)
6565

6666
**但是这样移动的原理是什么?**
6767

@@ -144,7 +144,7 @@ print(kmp("ababbbbaaabbbaaa", "bbbb"))
144144

145145
- KMP 算法在构造前缀表阶段的时间复杂度为 $O(m)$,其中 $m$ 是模式串 $p$ 的长度。
146146
- KMP 算法在匹配阶段,是根据前缀表不断调整匹配的位置,文本串的下标 $i$ 并没有进行回退,可以看出匹配阶段的时间复杂度是 $O(n)$,其中 $n$ 是文本串 $T$ 的长度。
147-
- 所以 KMP 整个算法的时间复杂度是 $O(n + m)$,相对于朴素匹配算法的 $O(n * m)$ 的时间复杂度,KMP 算法的效率有了很大的提升。
147+
- 所以 KMP 整个算法的时间复杂度是 $O(n + m)$,相对于朴素匹配算法的 $O(n \times m)$ 的时间复杂度,KMP 算法的效率有了很大的提升。
148148

149149
## 参考资料
150150

@@ -153,4 +153,4 @@ print(kmp("ababbbbaaabbbaaa", "bbbb"))
153153
- 【博文】[从头到尾彻底理解 KMP - 结构之法 算法之道 - CSDN博客](https://blog.csdn.net/v_JULY_v/article/details/7041827?spm=1001.2014.3001.5502)
154154
- 【博文】[字符串匹配的 KMP 算法 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html)
155155
- 【题解】[多图预警👊🏻详解 KMP 算法 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/duo-tu-yu-jing-xiang-jie-kmp-suan-fa-by-w3c9c/)
156-
- 【题解】[「代码随想录」KMP算法详解 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/dai-ma-sui-xiang-lu-kmpsuan-fa-xiang-jie-mfbs/)
156+
- 【题解】[「代码随想录」KMP算法详解 - 实现 strStr() - 力扣](https://leetcode.cn/problems/implement-strstr/solution/dai-ma-sui-xiang-lu-kmpsuan-fa-xiang-jie-mfbs/)

docs/ch03/03.02/03.02.10-Trie.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
例如下图就是一棵字典树,其中包含有 `"a"``"abc"``"acb"``"acc"``"ach"``"b"``"chb"` 这 7 个单词。
88

9-
![](../../images/20220210142321.png)
9+
![字典树](../../images/20240511165918.png)
1010

1111
从图中可以发现,这棵字典树用边来表示字母,从根节点到树上某一节点的路径就代表了一个单词。比如 $1 \rightarrow 2 \rightarrow 6 \rightarrow 10$ 表示的就是单词 `"acc"`。为了清楚地判断某节点路径是否表示一个单词,我们还可以在每个单词对应路径的结束位置增加一个结束标记 $end$(图中红色节点),表示从根节点到这里有一个单词。
1212

@@ -203,7 +203,7 @@ class Trie: # 字典树
203203

204204
例如下图,当我们输入「字典树」后,底下会出现一些以「字典树」为前缀的相关搜索内容。
205205

206-
![](../../images/20220210134829.png)
206+
![字典树的应用](../../images/20220210134829.png)
207207

208208
这个功能实现的基本原理就是字典树。当然,像 Google、必应、百度这样的搜索引擎,在这个功能能的背后肯定做了大量的改进和优化,但它的底层最基本的原理就是「字典树」这种数据结构。
209209

@@ -220,4 +220,4 @@ class Trie: # 字典树
220220
- 【书籍】ACM-ICPC 程序设计系列 算法设计与实现 陈宇 吴昊 主编
221221
- 【博文】[Trie 树 - 数据结构与算法之美 - 极客时间](https://time.geekbang.org/column/article/72414)
222222
- 【博文】[一文搞懂字典树](https://segmentfault.com/a/1190000040801084)
223-
- 【博文】[字典树 (Trie) - OI Wiki](https://oi-wiki.org/string/trie/)
223+
- 【博文】[字典树 (Trie) - OI Wiki](https://oi-wiki.org/string/trie/)

0 commit comments

Comments
 (0)