Skip to content

Commit 9f6df6f

Browse files
Merge pull request #173 from Liam0205/notes
[notes] notes for courses.
2 parents dbe8801 + ed6d809 commit 9f6df6f

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed

notes/18_hashtable/.gitkeep

Whitespace-only changes.

notes/18_hashtable/readme.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# 散列表
2+
3+
散列表是数组的一种扩展,利用数组下标的随机访问特性。
4+
5+
## 散列思想
6+
7+
* 键/关键字/Key:用来标识一个数据
8+
* 散列函数/哈希函数/Hash:将 Key 映射到数组下标的函数
9+
* 散列值/哈希值:Key 经过散列函数得到的数值
10+
11+
![](https://static001.geekbang.org/resource/image/92/73/92c89a57e21f49d2f14f4424343a2773.jpg)
12+
13+
本质:利用散列函数将关键字映射到数组下标,而后利用数组随机访问时间复杂度为 $\Theta(1)$ 的特性快速访问。
14+
15+
## 散列函数
16+
17+
* 形式:`hash(key)`
18+
* 基本要求
19+
1. 散列值是非负整数
20+
1. 如果 `key1 == key2`,那么 `hash(key1) == hash(key2)`
21+
1. 如果 `key1 != key2`,那么 `hash(key1) != hash(key2)`
22+
23+
第 3 个要求,实际上不可能对任意的 `key1``key2` 都成立。因为通常散列函数的输出范围有限而输入范围无限。
24+
25+
## 散列冲突¡
26+
27+
* 散列冲突:`key1 != key2``hash(key1) == hash(key2)`
28+
29+
散列冲突会导致不同键值映射到散列表的同一个位置。为此,我们需要解决散列冲突带来的问题。
30+
31+
### 开放寻址法
32+
33+
如果遇到冲突,那就继续寻找下一个空闲的槽位。
34+
35+
#### 线性探测
36+
37+
插入时,如果遇到冲突,那就依次往下寻找下一个空闲的槽位。(橙色表示已被占用的槽位,黄色表示空闲槽位)
38+
39+
![](https://static001.geekbang.org/resource/image/5c/d5/5c31a3127cbc00f0c63409bbe1fbd0d5.jpg)
40+
41+
查找时,如果目标槽位上不是目标数据,则依次往下寻找;直至遇见目标数据或空槽位。
42+
43+
![](https://static001.geekbang.org/resource/image/91/ff/9126b0d33476777e7371b96e676e90ff.jpg)
44+
45+
删除时,标记为 `deleted`,而不是直接删除。
46+
47+
#### 平方探测(Quadratic probing)
48+
49+
插入时,如果遇到冲突,那就往后寻找下一个空闲的槽位,其步长为 $1^2$, $2^2$, $3^2$, $\ldots$。
50+
51+
查找时,如果目标槽位上不是目标数据,则依次往下寻找,其步长为 $1^2$, $2^2$, $3^2$, $\ldots$;直至遇见目标数据或空槽位。
52+
53+
删除时,标记为 `deleted`,而不是直接删除。
54+
55+
#### 装载因子(load factor)
56+
57+
$\text{load factor} = \frac{size()}{capacity()}$
58+
59+
### 链表法
60+
61+
所有散列值相同的 key 以链表的形式存储在同一个槽位中。
62+
63+
![](https://static001.geekbang.org/resource/image/a4/7f/a4b77d593e4cb76acb2b0689294ec17f.jpg)
64+
65+
插入时,不论是否有冲突,直接插入目标位置的链表。
66+
67+
查找时,遍历目标位置的链表来查询。
68+
69+
删除时,遍历目标位置的链表来删除。

notes/19_hashtable/.gitkeep

Whitespace-only changes.

notes/19_hashtable/readme.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# 散列表
2+
3+
核心:散列表的效率并不总是 $O(1)$,仅仅是在理论上能达到 $O(1)$。实际情况中,恶意攻击者可以通过精心构造数据,使得散列表的性能急剧下降。
4+
5+
如何设计一个工业级的散列表?
6+
7+
## 散列函数
8+
9+
* 不能过于复杂——避免散列过程耗时
10+
* 散列函数的结果要尽可能均匀——最小化散列冲突
11+
12+
## 装载因子过大怎么办
13+
14+
动态扩容。涉及到 rehash,效率可能很低。
15+
16+
![](https://static001.geekbang.org/resource/image/67/43/67d12e07a7d673a9c1d14354ad029443.jpg)
17+
18+
如何避免低效扩容?
19+
20+
——将 rehash 的步骤,均摊到每一次插入中去:
21+
22+
* 申请新的空间
23+
* 不立即使用
24+
* 每次来了新的数据,往新表插入数据
25+
* 同时,取出旧表的一个数据,插入新表
26+
27+
![](https://static001.geekbang.org/resource/image/6d/cb/6d6736f986ec4b75dabc5472965fb9cb.jpg)
28+
29+
## 解决冲突
30+
31+
开放寻址法,优点:
32+
33+
* 不需要额外空间
34+
* 有效利用 CPU 缓存
35+
* 方便序列化
36+
37+
开放寻址法,缺点:
38+
39+
* 查找、删除数据时,涉及到 `delete` 标志,相对麻烦
40+
* 冲突的代价更高
41+
* 对装载因子敏感
42+
43+
链表法,优点:
44+
45+
* 内存利用率较高——链表的优点
46+
* 对装载因子不敏感
47+
48+
链表法,缺点:
49+
50+
* 需要额外的空间(保存指针)
51+
* 对 CPU 缓存不友好
52+
53+
——将链表改造成更高效的数据结构,例如跳表、红黑树
54+
55+
## 举个栗子(JAVA 中的 HashMap)
56+
57+
* 初始大小:16
58+
* 装载因子:超过 0.75 时动态扩容
59+
* 散列冲突:优化版的链表法(当槽位冲突元素超过 8 时使用红黑树,否则使用链表)

0 commit comments

Comments
 (0)