File tree Expand file tree Collapse file tree 12 files changed +240
-228
lines changed
Expand file tree Collapse file tree 12 files changed +240
-228
lines changed Original file line number Diff line number Diff line change 3232- [ 跳表] ( docs/跳表.md )
3333- [ 散列表] ( docs/散列表.md )
3434- [ 树和二叉树] ( docs/树和二叉树.md )
35+ - [ 堆] ( docs/堆.md )
36+ - [ B+树] ( docs/B+树.md )
37+ - [ 字典树] ( docs/字典树.md )
3538- [ 图] ( docs/graph.md )
36- - [ 堆] ( docs/heap.md )
37- - [ 字典树] ( docs/trie.md )
3839- [ 算法代码模板] ( docs/algorithm-template.md )
3940
4041## 💻 刷题
Original file line number Diff line number Diff line change 1+ # B+树
2+
3+ ## 什么是 B+树
4+
5+ B+树是在二叉查找树的基础上进行了改造:树中的节点并不存储数据本身,而是只是作为索引。每个叶子节点串在一条链表上,链表中的数据是从小到大有序的。
6+
7+
8+
9+ ![ ] ( https://raw.githubusercontent.com/dunwu/images/dev/snap/20220311092926.jpg )
10+
11+ 改造之后,如果我们要求某个区间的数据。我们只需要拿区间的起始值,在树中进行查找,当查找到某个叶子节点之后,我们再顺着链表往后遍历,直到链表中的结点数据值大于区间的终止值为止。所有遍历到的数据,就是符合区间值的所有数据。
12+
13+ ![ ] ( https://raw.githubusercontent.com/dunwu/images/dev/snap/20220311092929.jpg )
14+
15+ 但是,我们要为几千万、上亿的数据构建索引,如果将索引存储在内存中,尽管内存访问的速度非常快,查询的效率非常高,但是,占用的内存会非常多。
16+
17+ 比如,我们给一亿个数据构建二叉查找树索引,那索引中会包含大约 1 亿个节点,每个节点假设占用 16 个字节,那就需要大约 1GB 的内存空间。给一张表建立索引,我们需要 1GB 的内存空间。如果我们要给 10 张表建立索引,那对内存的需求是无法满足的。如何解决这个索引占用太多内存的问题呢?
18+
19+ 我们可以借助时间换空间的思路,把索引存储在硬盘中,而非内存中。我们都知道,硬盘是一个非常慢速的存储设备。通常内存的访问速度是纳秒级别的,而磁盘访问的速度是毫秒级别的。读取同样大小的数据,从磁盘中读取花费的时间,是从内存中读取所花费时间的上万倍,甚至几十万倍。
20+
21+ 这种将索引存储在硬盘中的方案,尽管减少了内存消耗,但是在数据查找的过程中,需要读取磁盘中的索引,因此数据查询效率就相应降低很多。
22+
23+ 二叉查找树,经过改造之后,支持区间查找的功能就实现了。不过,为了节省内存,如果把树存储在硬盘中,那么每个节点的读取(或者访问),都对应一次磁盘 IO 操作。树的高度就等于每次查询数据时磁盘 IO 操作的次数。
24+
25+ 我们前面讲到,比起内存读写操作,磁盘 IO 操作非常耗时,所以我们优化的重点就是尽量减少磁盘 IO 操作,也就是,尽量降低树的高度。那如何降低树的高度呢?
26+
27+ 我们来看下,如果我们把索引构建成 m 叉树,高度是不是比二叉树要小呢?如图所示,给 16 个数据构建二叉树索引,树的高度是 4,查找一个数据,就需要 4 个磁盘 IO 操作(如果根节点存储在内存中,其他结点存储在磁盘中),如果对 16 个数据构建五叉树索引,那高度只有 2,查找一个数据,对应只需要 2 次磁盘操作。如果 m 叉树中的 m 是 100,那对一亿个数据构建索引,树的高度也只是 3,最多只要 3 次磁盘 IO 就能获取到数据。磁盘 IO 变少了,查找数据的效率也就提高了。
28+
29+ ## 为什么需要B+树
30+
31+ 关系型数据库中常用 B+ 树作为索引,这是为什么呢?
32+
33+ 思考以下经典应用场景
34+
35+ - 根据某个值查找数据,比如 ` select * from user where id=1234 ` 。
36+ - 根据区间值来查找某些数据,比如 ` select * from user where id > 1234 and id < 2345 ` 。
37+
38+ 为了提高查询效率,需要使用索引。而对于索引的性能要求,主要考察** 执行效率和存储空间** 。如果让你选择一种数据结构去存储索引,你会如何考虑?
39+
40+ 以一些常见数据结构为例:
41+
42+ - ** 散列表** :散列表的查询性能很好,时间复杂度是 ` O(1) ` 。但是,散列表不能支持按照区间快速查找数据。所以,散列表不能满足我们的需求。
43+ - ** 平衡二叉查找树** :尽管平衡二叉查找树查询的性能也很高,时间复杂度是 ` O(logn) ` 。而且,对树进行中序遍历,我们还可以得到一个从小到大有序的数据序列,但这仍然不足以支持按照区间快速查找数据。
44+ - ** 跳表** :跳表是在链表之上加上多层索引构成的。它支持快速地插入、查找、删除数据,对应的时间复杂度是 ` O(logn) ` 。并且,跳表也支持按照区间快速地查找数据。我们只需要定位到区间起点值对应在链表中的结点,然后从这个结点开始,顺序遍历链表,直到区间终点对应的结点为止,这期间遍历得到的数据就是满足区间值的数据。
45+
46+ 实际上,数据库索引所用到的数据结构跟跳表非常相似,叫作 B+ 树。不过,它是通过二叉查找树演化过来的,而非跳表。B+树的应用场景
47+
48+ ## 参考资料
49+
50+ - [ 数据结构与算法之美] ( https://time.geekbang.org/column/intro/100017301 )
Original file line number Diff line number Diff line change 2121- [ 跳表] ( 跳表.md )
2222- [ 散列表] ( 散列表.md )
2323- [ 树和二叉树] ( 树和二叉树.md )
24+ - [ 堆] ( 堆.md )
25+ - [ B+树] ( B+树.md )
26+ - [ 字典树] ( 字典树.md )
2427- [ 图] ( graph.md )
25- - [ 堆] ( heap.md )
26- - [ 字典树] ( trie.md )
2728- [ 算法代码模板] ( algorithm-template.md )
2829
2930## 💻 刷题
Load Diff This file was deleted.
Original file line number Diff line number Diff line change 77 - [ 线性表的排序] ( 线性表的排序.md )
88- 非线性结构
99 - [ 散列表] ( 散列表.md )
10- - [ 树 ] ( 算法练习-树 .md)
11- - [ 图 ] ( graph .md)
12- - [ 堆 ] ( heap .md)
10+ - [ 树和二叉树 ] ( 树和二叉树 .md)
11+ - [ 堆 ] ( 堆 .md)
12+ - [ B+树 ] ( B+树 .md)
1313 - [ 字典树] ( trie.md )
14+ - [ 图] ( graph.md )
Load Diff This file was deleted.
Original file line number Diff line number Diff line change 1+ # 堆
2+
3+ ## 什么是堆?
4+
5+ 堆(Heap)是一个可以被看成近似完全二叉树的数组。
6+
7+ - ** 堆是一个完全二叉树** 。完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。
8+ - ** 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值** 。
9+
10+ 堆可以分为大顶堆和小顶堆。
11+
12+ - 对于每个节点的值都大于等于子树中每个节点值的堆,叫作“** 大顶堆** ”。
13+
14+ - 对于每个节点的值都小于等于子树中每个节点值的堆,叫作“** 小顶堆** ”。
15+
16+
17+ ## 如何实现堆
18+
19+ 完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。
20+
21+ ![ ] ( https://raw.githubusercontent.com/dunwu/images/dev/snap/20220311112542.jpg )
22+
23+
24+
25+ 堆常见的操作:
26+
27+ - HEAPIFY 建堆:把一个乱序的数组变成堆结构的数组,时间复杂度为 $$ O(n) $$ 。
28+ - HEAPPUSH:把一个数值放进已经是堆结构的数组中,并保持堆结构,时间复杂度为 $$ O(log N) $$
29+ - HEAPPOP:从最大堆中取出最大值或从最小堆中取出最小值,并将剩余的数组保持堆结构,时间复杂度为 $$ O(log N) $$ 。
30+ - HEAPSORT:借由 HEAPFY 建堆和 HEAPPOP 堆数组进行排序,时间复杂度为$$ O(N log N) $$ ,空间复杂度为 $$ O(1) $$ 。
31+
32+ ## 堆的应用场景
33+
34+ ### 求 TOP N
35+
36+ 堆结构的一个常见应用是建立优先队列(Priority Queue)。
37+
38+ 求 Top K 的问题抽象成两类。一类是针对静态数据集合;另一类是针对动态数据集合
39+
40+ ### 优先级队列
41+
42+ 在优先级队列中,数据的出队顺序不是先进先出,而是按照优先级来,优先级最高的,最先出队。
43+
44+ 堆和优先级队列非常相似:往优先级队列中插入一个元素,就相当于往堆中插入一个元素;从优先级队列中取出优先级最高的元素,就相当于取出堆顶元素。
45+
46+ > 参考:Java 的 ` PriorityQueue ` 类
47+
48+ ### 求中位数
You can’t perform that action at this time.
0 commit comments