├── .DS_Store
├── .gitattributes
├── 1. DSA --- Summary from ' Geeks for Geeks'
└── .DS_Store
├── 2. 数据结构与算法之美 --- 王争专栏
├── .DS_Store
├── 64.jpg
├── Part1 概要及复杂度分析
│ ├── 01.概要说明.md
│ ├── 02.复杂度分析(上).md
│ └── 03.复杂度分析(下).md
├── Part2 数据结构(Data Structure)
│ ├── 01. 数组(Array).md
│ ├── 02. 链表(List).md
│ ├── 03. 栈(Stack).md
│ ├── 04. 队列(Queue).md
│ ├── 05. 跳表(Skip List).md
│ ├── 06. 散列表 I(Hash Table).md
│ ├── 07. 散列表 II (Hash Table).md
│ ├── 08. 散列表 III (Hash Table).md
│ ├── 09. 树(Tree).md
│ ├── 10. 二叉树(Binary Tree).md
│ ├── 11. 二叉查找树(Binary Search Tree).md
│ ├── 12. 红黑树 (Red - Black Tree).md
│ ├── 13. 递归树.md
│ ├── 14. 堆 (Heap).md
│ ├── 15. 图 (Graph).md
│ └── 16. B+树.md
├── Part3 算法(Algorithm)
│ ├── 00. 递归.md
│ ├── 01. 排序算法.md
│ ├── 02. 冒泡排序 (Bubble Sort).md
│ ├── 03. 插入排序 (Insertion Sort).md
│ ├── 04. 冒泡 VS 插入.md
│ ├── 05. 选择排序 (Selection Sort).md
│ ├── 06. 希尔排序?.md
│ ├── 07. 归并排序(Merge Sort).md
│ ├── 08. 快速排序(Quick Sort).md
│ ├── 09. 归并 VS 快排.md
│ ├── 10. 线形排序(Linear Sort).md
│ ├── 11. 桶排序(Bucket Sort).md
│ ├── 12. 计数排序(Counting Sort).md
│ ├── 13. 基数排序(Radix Sort).md
│ ├── 14. 排序优化.md
│ ├── 15. 二分查找 (Binary Search).md
│ ├── 16. 哈希算法 (Hash).md
│ ├── 17. 堆排序(Heap Sort).md
│ ├── 18. 深度和广度优先搜索.md
│ ├── 20. 字符串匹配算法 I :BF、RK 算法.md
│ ├── 21. 字符串匹配算法 II :BM 、KMP 算法.md
│ ├── 22. 字符串匹配算法 III :Trie 树.md
│ ├── 23. 字符串匹配算法 IIII :AC 自动机.md
│ ├── 24. 贪心算法 (Greedy Algorithm).md
│ ├── 25. 分治算法 (Divide and Conquer).md
│ ├── 26. 回溯算法.md
│ ├── 27. 动态规划 I :初识.md
│ ├── 28. 动态规划 II :理论.md
│ ├── 29. 动态规划 III :实战.md
│ ├── 30. 拓扑排序.md
│ ├── 31. 最短路径.md
│ ├── 32. 位图.md
│ ├── 33. 概率统计.md
│ ├── 34. 向量空间.md
│ ├── 35. A 星 搜索算法.md
│ ├── 36.索引.md
│ └── 37.并行算法.md.md
├── README.md
├── Resources
│ ├── 01.jpg
│ ├── 02.jpg
│ ├── 03.jpg
│ ├── 04.jpg
│ ├── 05.jpg
│ ├── 06.jpg
│ ├── 07.jpg
│ ├── 08.jpg
│ ├── 09.jpg
│ ├── 10.jpg
│ ├── 11.jpg
│ ├── 12.jpg
│ ├── 13.jpg
│ ├── 14.jpg
│ ├── 15.jpg
│ ├── 16.jpg
│ ├── 17.jpg
│ ├── 18.jpg
│ ├── 19.jpg
│ ├── 20.jpg
│ ├── 21.jpg
│ ├── 22.jpg
│ ├── 23.jpg
│ ├── 24.jpg
│ ├── 25.jpg
│ ├── 26.jpg
│ ├── 27.jpg
│ ├── 28.jpg
│ ├── 29.jpg
│ ├── 30.jpg
│ ├── 31.jpg
│ ├── 32.jpg
│ ├── 33.jpg
│ ├── 34.jpg
│ ├── 35.jpg
│ ├── 36.jpg
│ ├── 37.jpg
│ ├── 38.jpg
│ ├── 39.jpg
│ ├── 40.jpg
│ ├── 41.jpg
│ ├── 42.jpg
│ ├── 43.jpg
│ ├── 44.jpg
│ ├── 45.jpg
│ ├── 46.jpg
│ ├── 47.jpg
│ ├── 48.jpg
│ ├── 49.jpg
│ ├── 50.jpg
│ ├── 51.jpg
│ ├── 52.jpg
│ ├── 53.jpg
│ ├── 54.jpg
│ ├── 55.jpg
│ ├── 56.jpg
│ ├── 57.jpg
│ ├── 58.jpg
│ ├── 59.jpg
│ ├── 60.jpg
│ └── 61.jpg
└── Resources1
│ ├── 00.jpg
│ ├── 01.jpg
│ ├── 02.jpg
│ ├── 03.jpg
│ ├── 04.JPG
│ ├── 05.JPG
│ ├── 06.JPG
│ ├── 07.JPG
│ ├── 08.JPG
│ ├── 09.JPG
│ ├── 10.JPG
│ ├── 100.jpg
│ ├── 101.jpg
│ ├── 102.jpg
│ ├── 103.jpg
│ ├── 104.jpg
│ ├── 105.jpg
│ ├── 106.jpg
│ ├── 107.jpg
│ ├── 108.jpg
│ ├── 109.jpg
│ ├── 11.JPG
│ ├── 110.jpg
│ ├── 111.jpg
│ ├── 112.jpg
│ ├── 113.jpg
│ ├── 114.jpg
│ ├── 115.jpg
│ ├── 116.jpg
│ ├── 117.jpg
│ ├── 118.jpg
│ ├── 119.jpg
│ ├── 12.JPG
│ ├── 120.jpg
│ ├── 121.jpg
│ ├── 122.jpg
│ ├── 123.jpg
│ ├── 124.jpg
│ ├── 125.jpg
│ ├── 126.jpg
│ ├── 127.jpg
│ ├── 128.jpg
│ ├── 129.jpg
│ ├── 13.JPG
│ ├── 130.jpg
│ ├── 131.jpg
│ ├── 132.jpg
│ ├── 133.jpg
│ ├── 134.jpg
│ ├── 135.jpg
│ ├── 136.jpg
│ ├── 137.jpg
│ ├── 138.jpg
│ ├── 139.jpg
│ ├── 14.JPG
│ ├── 140.jpg
│ ├── 141.jpg
│ ├── 142.jpg
│ ├── 143.jpg
│ ├── 144.jpg
│ ├── 145.jpg
│ ├── 146.jpg
│ ├── 147.jpg
│ ├── 148.jpg
│ ├── 149.jpg
│ ├── 15.JPG
│ ├── 150.jpg
│ ├── 151.jpg
│ ├── 152.jpg
│ ├── 153.jpg
│ ├── 154.jpg
│ ├── 155.jpg
│ ├── 156.jpg
│ ├── 157.jpg
│ ├── 158.jpg
│ ├── 159.jpg
│ ├── 16.JPG
│ ├── 160.jpg
│ ├── 161.jpg
│ ├── 162.jpg
│ ├── 163.jpg
│ ├── 164.jpg
│ ├── 165.jpg
│ ├── 166.jpg
│ ├── 167.jpg
│ ├── 168.jpg
│ ├── 169.jpg
│ ├── 17.JPG
│ ├── 170.jpg
│ ├── 171.jpg
│ ├── 172.jpg
│ ├── 173.jpg
│ ├── 174.jpg
│ ├── 178.jpg
│ ├── 179.jpg
│ ├── 18.JPG
│ ├── 180.jpg
│ ├── 181.jpg
│ ├── 182.jpg
│ ├── 183.jpg
│ ├── 184.jpg
│ ├── 185.jpg
│ ├── 186.jpg
│ ├── 187.jpg
│ ├── 188.jpg
│ ├── 189.jpg
│ ├── 19.JPG
│ ├── 190.jpg
│ ├── 191.jpg
│ ├── 192.jpg
│ ├── 193.jpg
│ ├── 194.jpg
│ ├── 195.jpg
│ ├── 196.jpg
│ ├── 197.jpg
│ ├── 198.jpg
│ ├── 199.jpg
│ ├── 20.JPG
│ ├── 200.jpg
│ ├── 201.jpg
│ ├── 202.jpg
│ ├── 203.jpg
│ ├── 204.jpg
│ ├── 205.jpg
│ ├── 206.jpg
│ ├── 207.jpg
│ ├── 208.jpg
│ ├── 209.jpg
│ ├── 21.JPG
│ ├── 210.jpg
│ ├── 211.jpg
│ ├── 212.jpg
│ ├── 213.jpg
│ ├── 214.jpg
│ ├── 215.jpg
│ ├── 22.JPG
│ ├── 23.JPG
│ ├── 24.JPG
│ ├── 25.JPG
│ ├── 26.JPG
│ ├── 27.JPG
│ ├── 28.jpg
│ ├── 29.jpg
│ ├── 30.jpg
│ ├── 31.jpg
│ ├── 32.jpg
│ ├── 33.jpg
│ ├── 34.jpg
│ ├── 35.jpg
│ ├── 36.jpg
│ ├── 37.jpg
│ ├── 38.jpg
│ ├── 39.jpg
│ ├── 40.jpg
│ ├── 41.jpg
│ ├── 42.jpg
│ ├── 43.jpg
│ ├── 44.jpg
│ ├── 45.jpg
│ ├── 46.jpg
│ ├── 47.jpg
│ ├── 48.jpg
│ ├── 49.jpg
│ ├── 50.jpg
│ ├── 51.jpg
│ ├── 52.jpg
│ ├── 53.jpg
│ ├── 54.jpg
│ ├── 55.jpg
│ ├── 56.jpg
│ ├── 57.jpg
│ ├── 58.jpg
│ ├── 59.jpg
│ ├── 60.jpg
│ ├── 61.jpg
│ ├── 62.jpg
│ ├── 63.jpg
│ ├── 64.jpg
│ ├── 65.jpg
│ ├── 66.jpg
│ ├── 67.jpg
│ ├── 68.jpg
│ ├── 69.jpg
│ ├── 70.jpg
│ ├── 71.jpg
│ ├── 72.jpg
│ ├── 73.jpg
│ ├── 74.jpg
│ ├── 75.jpg
│ ├── 76.jpg
│ ├── 77.jpg
│ ├── 78.jpg
│ ├── 79.jpg
│ ├── 80.jpg
│ ├── 81.jpg
│ ├── 82.jpg
│ ├── 83.jpg
│ ├── 84.jpg
│ ├── 85.jpg
│ ├── 86.jpg
│ ├── 87.jpg
│ ├── 88.jpg
│ ├── 89.jpg
│ ├── 90.jpg
│ ├── 91.jpg
│ ├── 92.jpg
│ ├── 93.jpg
│ ├── 94.jpg
│ ├── 95.jpg
│ ├── 96.jpg
│ ├── 97.jpg
│ ├── 98.jpg
│ └── 99.jpg
└── 3. 算法训练营 --- 谭超专栏
├── .DS_Store
├── Part I 介绍
├── 1 - 1 高效使用算法训练营.pdf
├── 1 - 2 数据结构与算法总览.pdf
└── 1 - 3 编程注意事项.pdf
├── Part II 理论讲解
├── .DS_Store
├── 1 时间复杂度和空间复杂度分析.pdf
├── 10. 深度优先搜索 (DFS)& 广度优先搜索 (BFS).pdf
├── 11. 贪心算法 Greedy.pdf
├── 12. 二分查找.pdf
├── 13. 动态规划 Dynamic Programming.pdf
├── 14. 高级搜索 1 :剪枝 ( Cut - Off ) 的实现和特性.pdf
├── 15. 高级搜索 2 双向 BFS 的实现、特性和题解.pdf
├── 16. 高级搜索 3 :启发式搜索 Heuristic Search.pdf
├── 17. 字典树 Trie 树.pdf
├── 18. 并查集 Disjoint Set.pdf
├── 19. 高级树、AVL 树 和 红黑树.pdf
├── 2 数组、链表、跳表的基本实现和特性.pdf
├── 20. 位运算.pdf
├── 21. 布隆过滤器 Bloom Filter.pdf
├── 22. LRU Cache.pdf
├── 23. 排序算法.pdf
├── 3 栈( Stack)、队列 (Queue)的基本实现和特性.pdf
├── 4. 哈希表 (Hash Table)、映射 ( Map )、集合 (Set ) 的实现与特性.pdf
├── 5. 树( Tree )、二叉树 ( Binary Tree)、二叉搜索树 ( Binary Search Tree )的实现和特性.pdf
├── 6. 递归的实现、特性以及思维要点.pdf
├── 8. 图 ( Graph ) 的实现和特性.pdf
└── 9. 分治 (Divide & Conquer)、回溯.pdf
└── Part III Leetcode 练习
├── .DS_Store
├── 1. 数组 Array
├── 11. 盛水最多的容器.pdf
├── 15. 三数之和 (高频老题).pdf
├── 189. 旋转数组.pdf
├── 26. 删除有序数组中的重复项.pdf
├── 283. 移动零.pdf
├── 4. 两数之和.pdf
├── 66. 加一.pdf
├── 70. 爬楼梯.pdf
└── 88.合并两个有序数组.pdf
├── 2.链表 LinkedList
├── 141. 环形链表 I.pdf
├── 142. 环形链表 II.pdf
├── 206. 反转链表.pdf
├── 21. 合并两个有序链表.pdf
├── 24. 两两交换链表中的节点.pdf
└── 25. K 个一组翻转链表.pdf
├── 3. 栈 Stack
├── .DS_Store
├── 155.最小栈.pdf
├── 20. 有效的括号.pdf
├── 42.接雨水 (重要).pdf
└── 84.柱状图中最大的矩形 (重要).pdf
└── 4. 队列 Queue
├── .DS_Store
├── 239.滑动窗口.pdf
└── 641.设计循环双端队列.pdf
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/.DS_Store
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/1. DSA --- Summary from ' Geeks for Geeks'/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/1. DSA --- Summary from ' Geeks for Geeks'/.DS_Store
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/.DS_Store
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/64.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/64.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part1 概要及复杂度分析/01.概要说明.md:
--------------------------------------------------------------------------------
1 | # 概要说明
2 |
3 |
4 |
5 | ### 数据结构 和 算法 的区别
6 |
7 | ** 数据结构是为算法服务的,算法作用在特定的数据结构之上。**
8 |
9 |
10 |
11 | ### 学习重点
12 |
13 | - 首先要掌握一个数据结构与算法中最重要的概念 --- **复杂度分析**。
14 | - 数据结构与算法的正文内容 --- 见下图:
15 |
16 | 
17 |
18 |
19 |
20 | ### 最常用的、最基础的数据结构与算法
21 |
22 | 20 个 最常用的、最基础的数据结构与算法,不管是应付面试还 是工作需要,只要集中精力逐一攻克,就足够了。
23 |
24 | - 10 数据结构 :数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树。
25 | - 10 个算法 :递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法。
26 |
27 |
28 |
29 | ### Tip
30 |
31 | - 在数据结构和算法的过程中,不要只是死记硬背,不要为了学习而学习,而是要 **学习它的 "来历" "自身特点" "适合解决的问题" 以及 "实际应用场景" .**
32 |
33 |
34 |
35 | - 学习数据结构和算法的过程,是非常好的 **思维训练过程**。所以,千万不要被动地记忆,要多辩证地思考,多问为什么。
36 |
37 |
38 |
39 | - 要注意的点:
40 |
41 | 1. 边学边练,适度刷题。
42 | 2. 多问、多思考、多互动。
43 | 3. 要给自己设立一个切实可行的目标。
44 | 4. 知识需要沉淀,不要试图一下子掌握所有。
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | *`@ 笔记时间 :2020-8-11 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
53 |
54 |
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part2 数据结构(Data Structure)/05. 跳表(Skip List).md:
--------------------------------------------------------------------------------
1 | # 跳表 (Skip List)
2 |
3 | [toc]
4 |
5 | ## 定义
6 |
7 | 跳表(Skip List)是一种随机化的数据, 由 William Pugh 在论文《Skip lists: a probabilistic alternative to balanced trees》中提出, 跳表以有序的方式在层次化的链表中保存元素。
8 |
9 | 它是各方面性能都比较优秀的 **动态数据结构**,可以支持快速地插入、删除、查找操作,写起来也不复杂,甚至可以替代 **红黑树 (Red-Black Tree)(平衡树)**.
10 |
11 |
12 |
13 | 成分说明:
14 |
15 | - 表头(head):负责维护跳跃表的节点指针。
16 |
17 | - 跳跃表节点:保存着元素值,以及多个层。
18 |
19 | - 层:保存着指向其他元素的指针。高层的指针越过的元素数量大于等于低层的指针,为了提高查找的效率,程序总是从高层先开始访问,然后随着元素值范围的缩小,慢慢降低层次。
20 |
21 | - 表尾:全部由 NULL 组成,表示跳跃表的末尾。
22 |
23 |
24 | ## 理解 跳表
25 |
26 | 对于 **单链表**,即便链表中存储的数据是有序的,如果要在其中查找某个苏剧,也只能从头到尾遍历链表。这样查询效率会很低,时间复杂度会很高,是 $O(n)$.
27 |
28 |
29 |
30 | ### 为提高效率,对链表建立一级"索引"
31 |
32 | 每两个节点提取一个节点到上一级,把抽出来的那一级叫做 **索引** 或 **索引层**。
33 |
34 |
35 |
36 | 其中,down 表示 down 指针,指向下一级结点。
37 |
38 | 如果现在要查找 16 节点,可以先在索引层遍历,当遍历到索引层中值为 13 的结点时,发现下一个节点是 17,那要查找的节点 16 肯定就在这两个节点之间。
39 |
40 | 然后,通过索引层节点的 down 指针,下降到原始链表这一层,继续遍历。
41 |
42 | 这时,只需要再遍历 2 个节点,就可以找到值等于 16 的这个节点。
43 |
44 | 这样,原来要查找 16,需要遍历 10 个节点,现在只需要遍历 7 个节点。
45 |
46 | 由此可以看出,**加来一层索引之后,查找一个节点需要遍历的节点个数减少了,也就是查找效率提高了。**
47 |
48 | ### 建立二级索引
49 |
50 | 在第一级索引的基础之上,每两个节点就抽出一个节点到第二级索引。
51 |
52 |
53 |
54 | 现在,查找 16 节点,只需要遍历 6 个节点(1,7,13, down 13, down 13, 16), 需要遍历的节点数量又减少了。
55 |
56 | ### 感受多级索引提升的查询效率
57 |
58 | 包含 64 个节点的链表,并按照在之前索引的基础上,每两个节点就抽出一个节点到上级节点方法,建立了五级索引.
59 |
60 |
61 |
62 | 现在,查找 62 节点:
63 |
64 | - 没有索引 : 遍历 62 个节点
65 | - 有索引 : 遍历 11 个节点。
66 |
67 | ## 分析 跳表
68 |
69 | ### 时间复杂度分析
70 |
71 | 如果链表中有 n 个节点,会有多少级索引?(每两个节点会抽出一个节点作为上一级索引的节点)
72 |
73 | - 一级索引节点个数: $n/2$
74 | - 二级索引节点个数: $n/4$
75 | - 三级索引节点个数: $n/8$
76 | - 依此类推....
77 |
78 | **第 k 级索引的结点个数是第 k - 1 级索引的节点个数的 $1/2$,那第 k 级索引节点的个数就是 $n/2^k$ **
79 |
80 | 假设索引有 h 级,最高的索引有 2 个节点,则 $n/2^h=2$,从而得到 $h = log_2{n-1}$.如果包含原始链表这一层,整个跳表的高度就是 $log_2{n}$ .所以,在跳表中查询某个数据的时候,如果每一层都要遍历 m 个节点,那在跳表中查询一个数据的时间复杂度就是 $O(m*log\ n)$.
81 |
82 | 按照前面这种索引结构,每一级索引都最多只需要遍历 3个节点,也就是说 m = 3,但为什么是 3 呢?
83 |
84 |
85 | 假设要查找的数据是 x,在第 k 级索引中,遍历到 y 节点后,发现 x 大于 y,但 小于 后面的节点 z,所以通过 y 的 down 指针,从第 k 级索引下降到第 k - 1 级索引。在 k - 1 级索引中,y 和 z 之间只有 3 个节点**(包含 y 和 z)**,所以,在 k - 1 级索引中最多只需要遍历 3 个节点,依次类推,每一级索引都最多只需要遍历 3 个节点。
86 |
87 |
88 |
89 | 通过上面的分析,m = 3, 所以跳表中查询任意数据的时间复杂度是 $O(logn)$.
90 |
91 | > 这个是查找时间复杂度跟二分查找是一样的,换句话说,这其实是基于单链表实现了二分查找。根据空间换时间的设计思路,这样做会使用掉很多空间。
92 |
93 | ### 空间复杂度分析
94 |
95 | 跳变需要存储多级索引,跟定要消耗更多的空间,那到底需要消耗多少额外空间呢?
96 |
97 | - 假设原始链表大小为 n, 第一级索引大约有 $n/2$ 个节点,第二级索引大约有 $n/4$ 个节点,依次类推,每上升一级就减少一半,直到剩下 2 个节点,这其实是个等比数列。
98 |
99 |
100 |
101 | - 节点总和
102 |
103 | $n/2+n/4+n/8...+8+4+2=n-2$
104 |
105 | - 空间复杂度
106 |
107 | $O(n)$
108 |
109 | 也就是说,如果将包含 n 个节点的单链表构造成跳表,需要额外在用接近 n 个节点的存储空间。
110 |
111 | 那能否降低索引占用的内存空间?
112 |
113 | 前面的索引结构是每两个节点抽一个节点到上级索引,如果每三个或五个节点抽一个节点到上级索引呢?
114 |
115 |
116 |
117 | - 一级索引:$n/3$
118 |
119 | - 二级索引:$n/9$
120 |
121 | - 依此类推,每往上一级,索引节点数都除以 3.
122 |
123 | 为了方便,假设最高一级的索引节点个数是 1,写下每级索引的结点个数:
124 |
125 |
126 |
127 | - 通过等比数列求和公式,总的索引节点个数
128 |
129 | $n/3+n/9+n/27+...+9+3+1=n/2$.
130 |
131 | - 空间复杂度
132 |
133 | $O(n)$
134 |
135 | 但是比上面每两个结点抽一个节点的索引构造方法,减少了一半的索引节点存储空间。
136 |
137 |
138 | > 实际上,在软件开发中,不必太在意索引占用的额外空间。
139 | >
140 | > 在讲数据结构和算法时,习惯性地把要处理的数据看成整数,但在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引节点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引节点大很多时,那索引占用的额外空间就可以忽略了。
141 |
142 | ## 高效的动态 插入 和 删除
143 |
144 | 跳表这个动态数据结构,不仅支持查找操作,还支持动态的插入、删除操作,而且插入、删除操作的时间复杂度也是 $O(log\ n)$.
145 |
146 | ### 插入 操作
147 |
148 | #### 单链表
149 |
150 | 一旦定位好要插入的位置,插入节点的时间复杂度是很低的,就是 $O(1).$
151 |
152 | 但是,为了保证原始链表中数据的有序性,需要先遍历每个节点,找到插入的位置,这个查找操作就会比较耗时。
153 |
154 | #### 跳表
155 |
156 | 查找某个节点的时间复杂度 $O(log\ n)$,所以这里查找某个数据该插入的位置,方法也是类似的,时间复杂度也是 $O(log\ n$.
157 |
158 |
159 |
160 |
161 |
162 | ### 删除 操作
163 |
164 | 如果要删除的节点在索引也有出现,则既要删除原链表中的节点,也要删除索引中的。
165 |
166 | 因为单链表中的删除操作需要拿到要删除结点的前驱结点,然后通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点。当然,如果是双向链表,就不需要考虑这个问题了。
167 |
168 | ## 索引状态更新
169 |
170 | 当不停地往跳表中插入数据时,如果不断地更新索引,就有可能出现某 2 个索引节点之间数据非常多的情况。
171 |
172 | 极端情况下,跳表会退化成单链表。
173 |
174 |
175 |
176 | 所以,需要某种手段来维护索引与原始链表大小之间的平衡,即,如果链表中节点多了,索引节点就相应地增加一些,避免复杂度退化,以及查找、插入、删除操作性能下降。
177 |
178 | 像红黑树、AVL树这样的平衡二叉树,通过左右旋的方式保持左右子树的大小平衡。
179 |
180 | 跳表通过随机函数来维护前面提到的`平衡性`。
181 |
182 | 当往跳表中插入数据时,可以选择同时将这个数据插入到部分索引层中,但如何选择索引层呢?
183 |
184 | 解决方法是,通过一个随机函数,来决定这个节点插入到哪几级索引中,比如随机函数生成了值 K,那就将这个节点添加到第一级到第 K 级这 K 级索引中。
185 |
186 |
187 |
188 | 随即函数的选择很有讲究,从概率上来讲,能够保证跳表的索引大小和数据大小平衡性,不至于性能过度退化。(随即函数参见之后代码或 Redis 中关于有序集合的跳表实现)
189 |
190 |
191 |
192 | ## 思考题
193 |
194 | 1. 如果每三个或者五个结点提取一个结点作为上级索引,对应的在跳表中查询数据的时间复杂度是多少呢?
195 |
196 | 如果每三个或者五个节点提取一个节点作为上级索引,那么对应的查询数据时间复杂度,应该也还是 O(logn)。
197 |
198 | 假设每 5 个节点提取,那么最高一层有 5 个节点,而跳表高度为 $log_5n$,每层最多需要查找 6 个节点(包括后面一个节点),即 O(mlogn) 中的 m = 6,最终,时间复杂度为 O(logn)。
199 |
200 | 空间复杂度也还是 O(logn),虽然省去了一部分索引节点,但是似乎意义不大。
201 |
202 | 2. Redis 为什么会选择用跳表来实现有序集合呢?
203 |
204 | redis有序集合是跳跃表实现的,直接这么说有失偏驳,他是复合数据结构,准确说应该是由一个双hashmap构成的字典和跳跃表实现的
205 |
206 | Redis 中的有序集合是通过跳表来实现的,严格点讲,其实还用到了散列表。不过散列表我们后面才会讲到,所以我们现在暂且忽略这部分。如果你去查看 Redis 的开发手册,就会发现,Redis 中的有序集合支持的核心操作主要有下面这几个:
207 |
208 | - 插入一个数据;
209 | - 删除一个数据;
210 | - 查找一个数据;
211 | - 按照区间查找数据(比如查找值在[100, 356]之间的数据);
212 | - 迭代输出有序序列。
213 |
214 | 其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。
215 |
216 | 对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。
217 |
218 | 当然,Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
219 |
220 | 不过,跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。
221 |
222 | *`@ 笔记时间 :2020-11-03 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part2 数据结构(Data Structure)/06. 散列表 I(Hash Table).md:
--------------------------------------------------------------------------------
1 | # 散列表 I (Hash Table)
2 |
3 | [toc]
4 |
5 | ## 散列思想
6 |
7 | - 散列表也叫 `哈希表` 或 `Hash表`
8 |
9 | - **散列表用的是数组支持按照下标随即访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表.**
10 |
11 | - 例子 Level 1:
12 |
13 | 假如我们有 89 名选手参加学校运动会,为了方便记录成绩,每个选手雄起那都会贴上自己的参赛号码,这89名选手的编号依次是 1 到 89,如何通过编程实现通过编号快速查找到对应的选手信息?
14 |
15 | 1. 把这 89 名选手的信息按照其编号对应的下标放在数组里,即编号为 1 的选手,放到数组中下标为 1 的位置;编号为 2 的选手,放到下标为 2 的位置;依次类推,编号为 k 的选手放到数组下标为 k 的位置。
16 | 2. 因为参赛编号跟数组下标一一对应,当需啊哟查询参赛编号为 x 的选手的时候,只需要将下标为 x 的数组元素取出来就可以了,时间复杂度为 $O(1)$。
17 |
18 | 在这个例子中,已经用到了散列思想,参赛编号是自然数,并且与数组的下标形成一一映射,所以利用数组支持根据下标随即访问的时候,时间复杂度是 $O(1)$ 这一特性,就可以实现快速查找编号对应的选手信息。
19 |
20 | - 例子 Level2:
21 |
22 | 假设参赛编号不能设置得这么简单,要加上年纪、班级这些更详细的信息,所以把编号的规则稍微修改了一下,用 6 位数字 来表示。 比如 051167,其中,前两位 05 表示年纪,中间两位 11 表示 班级,最后两位还是原来的编号 1 到 89,这时候该如何存储选手信息,才能支持通过编号来快速查找选手信息?
23 |
24 | 思路还是类似的,尽管不能直接把编号作为数组下标,但可以截取参赛编号的后两位作为数组下标,来存取选手信息数据。当通过参赛编号查询选手信息的时候,用同样的方法,取参赛编号的后两位,作为数组下标,来读取数组中的数据。
25 |
26 | 其中,参赛选手的编号叫做 *键(Key,关键字)*,用键来标识选手。
27 |
28 | 把参赛编号转化为数组下标的映射方法就叫做 *散列函数(Hash 函数、哈希哈数)*,而散列函数计算得到的值就叫做 *散列值(Hash 值、哈希值)*。
29 |
30 |
31 |
32 |
33 | - 总结:
34 |
35 | **散列表用的就是数组支持按照下标随机访问的时候,时间复杂度是 $O(1)$的特性。**
36 |
37 | 通过散列函数把元素的键值映射为下标,然后将数据存储在数组中对应下标的位置,当按照键值查询元素时,用同样的散列函数,将将键值转化为数组下标,从对应的数组下标的位置取数据。
38 |
39 | ## 散列函数
40 |
41 | 散列函数,顾名思义,它是一个函数。可以把它定义成 *hash(key)*,其中 key 表示元素的键值, *hash(key)* 的值表示经过散列函数计算得到的散列值。
42 |
43 | - 第一个例子中,编号就是数组下标,所以 hash(key) 就等于 key,伪代码如下:
44 |
45 | ```java
46 | int hash(String key){
47 |
48 | // 获取后两位字符
49 | string lastTwoChars = key.substr(length-2,length);
50 |
51 | // 将后两位字符转换为整数
52 | int hashValue = convert lastTwoChas to int-type;
53 |
54 | return hashValue;
55 | }
56 | ```
57 |
58 | ### *散列函数设计的基本要求
59 |
60 | 1. **散列函数计算得到的散列值是一个非负整数;**
61 |
62 | 2. **如果 key1 = key2, 那 hash(key1) == hash(key2);**
63 |
64 | 3. **如果 key1 $\neq$ key2 那 hash(key1) $\neq$ hash(key2).**
65 |
66 |
67 | 说明:
68 |
69 | 1. 数组下标是从 0 开始的,所以散列函数生成的散列值也要是非负整数;
70 |
71 | 2. 相同的 key,经过散列函数得到散列值也应该是相同的;
72 |
73 | 3. 不同的 key,经过散列函数得到的散列值一定是不相同的。
74 |
75 | 但是在真实的情况下,要想找到一个不同的 key 对应的散列值都不一样的散列函数,几乎是不可能的,即便像业界著名的 `MD5、SHA、CRC`等哈希算法,也无法完全避免这种**散列冲突**。而且,因为数组的存储空间有限,也会加大散列冲突的概率。
76 |
77 | 所以,几乎无法找到一个完美的无冲突的散列函数,即便能找到,付出的时间成本、计算成本也是很大的,所以针对散列冲突问题,需要通过其他途径来解决。
78 |
79 | ### 散列冲突
80 |
81 | 再好的散列函数也无法避免散列冲突,而常用的散列冲突解决方法有两类:
82 |
83 | - 开放寻址法 (Open Addressing)
84 |
85 | - 链表法(Chaining)
86 |
87 | #### 1. 开放寻址法 (Open Addressing)
88 |
89 | 开放寻址法的核心思想是,如果出现了散列冲突,就重新探测一个空闲位置,将其插入。
90 |
91 | 有三种方法可以进行空闲位置的探测:
92 |
93 | 1. 线形探测(Linear Probing)
94 |
95 | 2. 二次探测(Quadratic Probing)
96 |
97 | 3. 双重散列(Double Hashing)
98 |
99 | ##### 1. 线性探测 (Linear Probing)
100 |
101 | ###### 散列表插入元素
102 |
103 | 线形探测是比较简单的探测方法,类似于线形查找。
104 |
105 | 当往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用,就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。
106 |
107 | 举下图说明,黄色表示空闲位置,橙色表示存储数据:
108 |
109 |
110 |
111 | 从图中可以看出,散列表的大小为 10,在元素 x 插入散列表之前,已经有 6 个元素插入到散列表中。 x 经过 Hash 算法之后,被散列到位置下标为 7 的位置,但这个位置已经有数据了,所以就产生了冲突。
112 |
113 | 于是,就顺序地往后一个一个地找,看有没有空闲的位置,遍历到尾部都没有找到空闲的位置,于是再从表头开始找,直到找到空闲位置 2,于是将其插入到这个位置。
114 |
115 | ###### 散列表查找元素
116 |
117 | **在散列表中查找元素的过程有点儿类似插入过程,通过散列函数求出要查找元素的键值对应的散列值,然后比较数组中下标为散列值得元素和要查找的元素。如果相等,则说明就是要找的元素;否则就顺序往后依次查找。如果遍历到数组中的空闲位置,还没有找到,就说明要查找的元素并没有在散列表中。**
118 |
119 |
120 |
121 |
122 | ###### 散列表删除元素
123 |
124 | 对于使用线形探测法解决冲突的散列表,删除操作稍微有些特别,**不能单纯地把要删除的元素设置为空**。
125 |
126 | **因为在查找的时候,一旦通过线形探测方法,找到一个空闲位置,就可以认定散列表中不存在这个数据。但是,如果这个空闲位置是后来删除的,就会导致原来的查找算法失效。**
127 |
128 | 可以将删除的元素特殊标记为 `deleted`,当线性探测查找时,遇到标记为 `deleted` 的空间,并不是停下来,而是继续往下探测。
129 |
130 |
131 |
132 |
133 | ##### 线形探测存在的问题
134 |
135 | **当散列表中插入的数据越来越多时,散列冲突发生的可能性就会越来越大,空闲位置就会越来越少,线形探测的时间就会越来越久。极端情况下,可能需要探测整个散列表,所以最坏情况下时间复杂度为 $O(n)$. 同理,在删除和查找时,也有可能会线形探测整张散列表,才能找到要查找或者删除的数据。**
136 |
137 |
138 | #### 2. 二次探测 (Quadratic Probing)
139 |
140 | 二次探测是线性探测每次探测步长的`二次方`.
141 |
142 | 说明:
143 |
144 | 所谓二次探测,跟线性探测很像,线性探测每次探测的步长是 1,那它探测的下标序列就是 $hash(key)+0, hash(key)+1, hash(key)+2...$
145 |
146 | 而二次探测探测的步长就变成了原来的 `二次方`,也就是说,它的探测下标序列就是 $hash(key)+ 0, hash(key)+1^2,hash(key)+2^2,....$
147 |
148 | #### 3. 双重散列
149 |
150 | 所谓双重散列,意思就是不仅要使用一个散列函数,使用一组散列函数 $hash1(key),hash2(key),hash3(key)...$
151 |
152 | 先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列函数,以此类推,直到找到空闲的存储位置。
153 |
154 | #### 总结
155 |
156 | 不管采用哪种探测方法,当散列表中空闲的位置不多时,散列冲突的概率就会大大提高,为了尽可能保证散列表的操作效率,一般情况下,会尽可能保证散列表中有一定比例的空闲槽位。
157 |
158 | 用 `装载因子(Load Factor)来表示空位的多少,装载因子越大,说明空闲位置越少,冲突越多,散列表的性能就会下降`。
159 |
160 | 计算公式:
161 |
162 | $$
163 | 散列表的转载因子 = 填入表中的元素个数\div 散列表长度
164 | $$
165 |
166 | #### 2. 链表法
167 |
168 | 链表法是一种更加常用的散列冲突解决办法,相比开发寻址法,要简单的多。
169 |
170 | 看下图,在散列表中,每个 `桶(bucket)` 或者 `槽(slot)` 会对应一条链条,所有散列值相同的元素我们都放到相同槽位对应的链表中。
171 |
172 |
173 |
174 | ##### 插入
175 |
176 | 插入的时候,只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是 $O(1)$.
177 |
178 | #### 查找 和 删除
179 |
180 | 当查找、删除一个元素时,同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。
181 |
182 | 这两个操作的时间复杂度跟链表的长度 k 成正比,也就是 $O(k)$.
183 |
184 | 对于散列比较均匀的散列函数来说,理论上讲, $k = n/m$,其中 n 表示散列中数据的个数,m 表示散列表中 `槽` 的个数。
185 |
186 | ## 思考
187 |
188 | 1. Word 文档中单词拼写检查功能是如何实现的?
189 |
190 | 常用的英文单词有 20 万个左右,假设单词的平均长度是 10 个字母,平均一个单词占用 10 个字节的内存空间,那 20 万英文单词大约占 2MB 的存储空间,就算放大 10 倍也就是 20MB。对于现在的计算机来说,这个大小完全可以放在内存里面。所以我们可以用散列表来存储整个英文单词词典。
191 |
192 | 当用户输入某个英文单词时,拿用户输入的单词去散列表中查找。如果查到,则说明拼写正确;如果没有查到,则说明拼写可能有误,给予提示。借助散列表这种数据结构,就可以轻松实现快速判断是否存在拼写错误
193 |
194 | 2. 假设我们有 10 万条 URL 访问日志,如何按照访问次数给 URL 排序?
195 |
196 | 遍历 10 万条数据,以 URL 为 key,访问次数为 value,存入散列表,同时记录下访问次数的最大值 K,时间复杂度 O(N)。
197 |
198 | 如果 K 不是很大,可以使用桶排序,时间复杂度 O(N)。如果 K 非常大(比如大于 10 万),就使用快速排序,复杂度 O(NlogN)。
199 |
200 | 3. 有两个字符串数组,每个数组大约有 10 万条字符串,如何快速找出两个数组中相同的字符串?
201 |
202 | 以第一个字符串数组构建散列表,key 为字符串,value 为出现次数。再遍历第二个字符串数组,以字符串为 key 在散列表中查找,如果 value 大于零,说明存在相同字符串。时间复杂度 O(N)。
203 |
204 | *`@ 笔记时间 :2020-11-05 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part2 数据结构(Data Structure)/08. 散列表 III (Hash Table).md:
--------------------------------------------------------------------------------
1 | # 散列表 III (Hash Table)
2 |
3 | [toc]
4 |
5 | ## 散列表 和 链表
6 |
7 | 散列表和链表,经常会被放在一起使用:
8 |
9 | 1. 链表 :LRU 缓存淘汰算法
10 |
11 | 在链表那一节,说到如何用链表实现 LRU 缓存淘汰算法,但是链表实现的 LRU 缓存淘汰算法的时间复杂度是 $O(n)$.
12 |
13 | 通过散列表可以将这个时间复杂度降低到 $O(1)$.
14 |
15 | 2. 跳表 : Redis 有序集合
16 |
17 | 在跳表那一节,Redis 的有序集合是使用跳表来实现的,跳表可以看作是一种改进版的链表.
18 |
19 | Redis 有序集合不仅使用了跳表,还用到了散列表.
20 |
21 | 3. Java : LinkedHashMap 容器
22 |
23 | Java 语言中的 LinkedHashMap 这样一个常用的容器,也用到了散列表和链表两种数据结构.
24 |
25 | ## LRU 缓存淘汰算法
26 |
27 | 借助散列表可以把 基于链表的 LRU 缓存淘汰算法的时间复杂度降低为 $O(1)$.
28 |
29 | ### 回顾
30 |
31 | 维护一个按照访问时间从大到小有序排列的链表结构,因为缓存大小有限,当缓存空间不够,需要淘汰一个数据时,就直接将链表头部的节点删除.
32 |
33 | 当要缓存某个数据的时候,先在链表中查找这个数据:
34 |
35 | `如果没有找到,则直接将数据放到链表的尾部;如果找到了,就把它移动到链表的尾部. 因为查找数据需要遍历链表,所以单纯用链表实现的 LRU 缓存淘汰算法的时间复杂度很高,是 O(n).`
36 |
37 | ### 缓存 cache 系统操作
38 |
39 | 一个 缓存 cache 系统包含的操作如下:
40 |
41 | - 往缓存中添加一个数据;
42 |
43 | - 往缓存中删除一个数据;
44 |
45 | - 往缓存中查找一个数据.
46 |
47 | 这三个操作都要设计 `查找`操作,如果单纯地采用链表,时间复杂度只能是 $O(n)$,如果将散列表和链表两种数据结构结合使用,可以将这三个操作的时间复杂度都降低到 $O(1)$.
48 |
49 | 具体结构如下:
50 |
51 |
52 |
53 | `使用双向链表存储数据,链表中的每个节点处理存储数据 (data)、前驱指针(prev)、后继指针(next)之外,还新增了一个特殊的字段 hnext.`
54 |
55 | 因为散列表示通过链表法解决散列冲突的,所以每个节点会在两条链中,`一个链是刚刚提到的双向链表,另一个链是散列表中的拉链.前驱和后继指针是为了将节点串在双向链表中,hnext 指针是为了将节点串在散列表的拉链中.`
56 |
57 | ### 时间复杂度 $O(1)$ 分析
58 |
59 | 了解了这个散列表和双向链表的组合存储结构之后,前面讲到的缓存的三个操作是如何做到时间复杂度是 $O(1)$?
60 |
61 | #### 如何查找一个数据?
62 |
63 | 散列表中查找数据的时间复杂度接近 $O(1)$,所以通过散列表,可以很快地在缓存中找到一个数据.
64 |
65 | 当找到数据之后,还需要将它移动到双向链表的尾部.
66 |
67 | #### 如何删除一个数据?
68 |
69 | 如果要删除一个数据,则需要找到数据所在的节点,然后将节点删除,借助散列表,可以在 $O(1)$ 时间复杂度里找到要删除的节点.
70 |
71 | 因为链表是双向链表,双向链表可以通过前驱指针 $O(1)$ 时间复杂度获取前驱节点,`所以在双向链表中,删除节点只需要 $O(1)$ 的时间复杂度.`
72 |
73 | #### 如何添加一个数据?
74 |
75 | 添加数据到缓存稍微有点麻烦,需要先看这个数据是否已经在缓存中,`如果已经在缓存中,需要将其移动到双向链表的尾部;如果不在缓存中,还要看缓存有没有满.如果满了,则将双向链表头部的节点删除,然后再将数据放到链表的尾部;如果没有满,就直接将数据放到链表的尾部.`
76 |
77 | 这整个过程涉及的查找操作都可以通过散列表来完成,其他的操作,比如删除头节点、链表尾部插入数据等,都可以在 $O(1)$ 的时间复杂度内完成.所以,这三个操作的时间复杂度都是 $O(1)$.
78 |
79 | 至此,就通过散列表和双向链表的组合使用,实现了一个高效的、支持 LRU 缓存淘汰的算法的缓存系统原型.
80 |
81 | ## Redis 有序集合
82 |
83 | ### 回顾
84 |
85 | 在有序集合中,每个成员对象有两个重要的属性,`key(键值)` 和 `score(分值)`。
86 |
87 | 所以,不仅可以通过 score 来查找数据,还可以通过 key 来查找数据.
88 |
89 | 用户积分排行榜功能,举例说明:
90 |
91 | 可以通过用户的 ID 来查找积分信息,也可以通过积分区间来查找用户 ID 或者 姓名信息.
92 |
93 | 这里包含的 ID、姓名和积分的用户信息,就是成员对象,用户 ID 就是 key, 积分就是 score.
94 |
95 |
96 |
97 | ### Redis 有序集合操作
98 |
99 | - 添加一个成员对象;
100 |
101 | - 按照键值来删除一个成员对像;
102 |
103 | - 按照键值来查找一个成员对象;
104 |
105 | - 按照分值区间查找数据,比如查找积分在 [100,356] 之间的成员对象;
106 |
107 | - 按照分值从小到大排序成员遍历.
108 |
109 | 如果仅仅按照分值成员将对象组织成跳表的结构,那按照键值来删除、查询成员对象就会很慢。
110 |
111 | 解决方法与 LRU 缓存淘汰算法的解决方法类似,可以再按照键值构建一个散列表,这样按照 key 来删除、查找一个成员对象的时间复杂度就变成了 $O(1)$.同时,借助跳表结构,其他操作也非常高效.
112 |
113 |
114 | > 实际上,Redis 有序集合的操作还有另外一类,也就是查找成员对象的排名(Rank)或者根据排名区间查找成员对象。
115 | >
116 | > 这个功能单纯用刚刚讲的这种组合结构就无法高效实现了。这块内容后面的章节再讲。
117 |
118 | ## Java LinkedHashMap
119 |
120 | ### 回顾
121 |
122 | HashMap 底层是通过散列表这种数据结构实现的,而 LinkedHashMap 前面比 HashMap 多了一个 **Linked**.
123 |
124 | 这个 **Linked** 也并不仅仅代表它是通过 链表法 解决散列冲突的.
125 |
126 | ```Java
127 | HashMap m = new LinkedHashMap<>();
128 | m.put(3,11);
129 | m.put(1,12);
130 | m.put(5,23);
131 | m.put(2,22);
132 |
133 | for (Map.Entry e : e.entrySet()){
134 | System.out.println(e.getKey());
135 | }
136 | ```
137 | 上面代码会按照数据插入的顺序依次来打印,即 3,1,5,2.
138 |
139 | 但散列表中数据是经过散列函数打乱之后无规律存储的,为什么会实现按照数据的插入顺序来遍历打印呢?
140 |
141 | `LinkedHashMap 是通过散列表和链表组合在一起实现的. 实际上,它不仅支持按照插入顺序遍历数据,还支持按照访问顺序来遍历数据.`
142 |
143 | ```Java
144 | // 10是初始大小,0.75是装载因子,true是表示按照访问时间排序
145 | HashMap m = new LinkedHashMap<>(10, 0.75f, true);
146 | m.put(3, 11);
147 | m.put(1, 12);
148 | m.put(5, 23);
149 | m.put(2, 22);
150 |
151 | m.put(3, 26);
152 | m.get(5);
153 |
154 | for (Map.Entry e : m.entrySet()) {
155 | System.out.println(e.getKey());
156 | }
157 | ```
158 |
159 | 打印结果:1,2,3,5.
160 |
161 | 分析:
162 |
163 | - 每次调用 put() 函数,往 LinkedHashMap 中添加数据的时候,都会将数据添加到链表的尾部,所以,在前四个操作完成之后,链表中的数据是下面这样的:
164 |
165 |
166 |
167 |
168 | - 在第 8 行代码中,再次将键值为 3 的数据放入到 LinkedHashMap 的时候,会先查找这个键值是否已经有了,然后,再讲已经存在的 (3,11) 删除,并且将新的 (3,26) 放到链表的尾部.所以,这个时候链表中的数据就是下面这样:
169 |
170 |
171 |
172 | - 当第 9 行代码访问到 key 为 5 的数据的时候,将被访问到的数据移动到链表尾部.
173 | 所以,这个时候链表中的数据是下面这样:
174 |
175 |
176 |
177 | - 所以,最后打印出来的数据是 1,2,3,5.
178 |
179 | 从上面分析来看,按照访问时间排序的 LinkedHashMap 本身就是一个支持 LRU 缓存淘汰策略的缓存系统.
180 |
181 | ### 总结
182 |
183 | `LinkedHashMap 是通过双向链表和散列表这两种数据结构组合实现的. LinkedHashMap 中 Linked 实际上是指的是双向链表,并非指用链表法解决散列冲突.`
184 |
185 | ## 思考
186 |
187 | 1. 为什么散列表和链表经常一块使用?
188 |
189 | `散列表这种数据结构虽然支持非常高效的数据插入、删除、查找操作,但是散列表中的数据都是通过散列函数打乱之后无规律存储的。也就说,它无法支持按照某种顺序快速地遍历数据。如果希望按照顺序遍历散列表中的数据,那就需要将散列表中的数据拷贝到数组中,然后排序,再遍历。`
190 |
191 | `因为散列表是动态数据结构,不停地有数据的插入、删除,所以每当按顺序遍历散列表中的数据的时候,都需要先排序,那效率势必会很低。为了解决这个问题,将散列表和链表(或者跳表)结合在一起使用。`
192 |
193 | ```txt
194 | - 数组占据随机访问的优势,却有需要连续内存的缺点。
195 |
196 | - 链表具有可不连续存储的优势,但访问查找是线性的。
197 |
198 | - 散列表和链表、跳表的混合使用,是为了结合数组和链表的优势,规避它们的不足。
199 |
200 | - 可以得出数据结构和算法的重要性排行榜:连续空间 > 时间 > 碎片空间.
201 | ```
202 |
203 | 2. 如果把今天讲的几个散列表和链表结合使用的例子中的双向链表改成单链表,还能否正常工作呢?
204 |
205 | 在删除一个元素时,虽然能 O(1) 的找到目标结点,但是要删除该结点需要拿到前一个结点的指针,遍历到前一个结点复杂度会变为 O(N),所以用双链表实现比较合适。
206 |
207 |
208 | 3. 假设猎聘网有 10 万名猎头,每个猎头都可以通过做任务(比如发布职位)来积累积分,然后通过积分来下载简历。假设你是猎聘网的一名工程师,如何在内存中存储这 10 万个猎头 ID 和积分信息,让它能够支持这样几个操作:
209 |
210 | - 根据猎头的 ID 快速查找、删除、更新这个猎头的积分信息;
211 | - 查找积分在某个区间的猎头 ID 列表;
212 | - 查找按照积分从小到大排名在第 x 位到第 y 位之间的猎头 ID 列表。
213 |
214 |
215 | 以积分排序构建一个跳表,再以猎头 ID 构建一个散列表:
216 |
217 | 1. ID 在散列表中所以可以 O(1) 查找到这个猎头;
218 |
219 | 2. 积分以跳表存储,跳表支持区间查询;
220 |
221 | 3. 这点根据目前学习的知识暂时无法实现,老师文中也提到了。
222 |
223 | *`@ 笔记时间 :2020-11-09 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part2 数据结构(Data Structure)/09. 树(Tree).md:
--------------------------------------------------------------------------------
1 | # 树 (Tree)
2 |
3 | ### 介绍
4 |
5 | "树"这种数据结构很像现实生活中的"树", 结构图片如下:
6 |
7 |
8 |
9 | ### 术语
10 |
11 | - `节点`:每个元素;
12 | - `父子关系`:连接相邻节点之间的关系;
13 | - `父节点`: 一个节点含有子节点,则这个节点称为其子节点的父节点;
14 | - `子节点`: 一个节点含有的子树的根节点称为该节点的子节点;
15 | - `兄弟节点`: 具有相同父节点的节点互称为兄弟节点;
16 | - `根节点`: 没有父节点的节点,即 一棵树中,最大的节点度称为树的度;
17 | - `节点的度`: 一个节点含有的子树的个数称为该节点的度;
18 | - `叶节点(or 叶子节点、终端节点)`:没有子节点的节点,即度为零的节点;
19 | - `分支节点(or 非终端节点)`:度不为零的节点;
20 | - `层(Level)`:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
21 | - `深度(Depth)`: 对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
22 | - `高度(Height)`: 对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
23 | - `堂兄弟节点`:父节点在同一层的节点互为堂兄弟;
24 | - `节点的祖先`:从根到该节点所经分支上的所有节点;
25 | - `子孙`:以某节点为根的子树中任一节点都称为该节点的子孙;
26 | - `森林`:由 m (m >= 0) 棵互不相交的树的集合称为森林.
27 |
28 | ### 举例说明
29 |
30 |
31 |
32 | A 节点就是 B 节点的父节点,B 节点是 A 节点的子节点。
33 |
34 | B、C、D 这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点。
35 |
36 | 把没有父节点的节点叫做根节点,也就是图中的节点 E。
37 |
38 | 把没有子节点的节点叫做叶子节点或者叶节点,比如图中的 G、H、I、J、K、L 都是叶子节点。
39 |
40 | ### 高度(Height)、深度(Depth)、层(Level)区分
41 |
42 |
43 |
44 | >- 节点高度 = 节点到叶子节点的 **最长路径**(边数);
45 | >
46 | >- 节点深度 = **根节点** 到这个节点所经历的 **边的个数**;
47 | >
48 | >- 节点层数 = 节点深度 + 1;
49 | >- 树的高度 = **根节点** 的高度.
50 |
51 | 这三个概念定义比较容易混淆,描述起来也比较空洞,举例说明:
52 |
53 |
54 |
55 | - 高度:在生活中是从下往上度量,比如要度量第 10 层楼的高度、第 13 层楼的高度,起点都是地面;所以,数据结构中的高度也是一样,从最底层开始计数,并且计数的起点是 0。
56 | - 深度:在生活中是从上往下度量,比如水中鱼的深度,是从水平面开始度量的;所以,树这种数据结构的深度也是类似的,从根节点开始度量,并且计数起点也是0.
57 | - 层数:跟深度的计算类似,不过计数起点是 1,也就是说根节点位于第 1 层。
58 |
59 | *`@ 笔记时间 :2020-11-11 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part2 数据结构(Data Structure)/15. 图 (Graph).md:
--------------------------------------------------------------------------------
1 | # 图(Graph)
2 |
3 |
4 |
5 | ## 理解"图"(Graph)
6 |
7 | - 图(Graph)是一种非线性表数据结构,和树比起来,这是一种更加复杂的非线性表结构;
8 |
9 | - 概念:
10 |
11 | 1. **顶点(Vertex)**:图中的元素;
12 |
13 | 2. **边(Edge)**:图中的一个顶点与任意其他顶点建立的连接关系;
14 |
15 |
16 |
17 |
18 | 4. **方向(Direction)**:把边有方向的图叫做"有向图";把边没有方向的图叫做"无向图";
19 |
20 | 5. **度(Degree)**:在无向图中,表示一个顶点有多少条边;在有向图中,把度分为 **入度**(In-Degree) 和 **出度**(Out-Degree);
21 |
22 | 6. **入度(In-Degree)**:表示有多少条边指向这个顶点;
23 |
24 | 7. **出度(Out-Degree)**:表示有多少条边是以这个顶点为起点指向其他顶点;
25 |
26 |
27 |
28 | 8. **带权图(Weighted Graph)**:图中每条边都有一个权重(Weight),可以通过这个权重来表示 QQ 好友间的亲密度。
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | - 例子:
39 |
40 | - 微信的社交关系,可以把每个用户看作一个顶点,如果两个用户之间互加好友,那就在两者之间建立一条边。所以,整个微信的关系就可以用一张图来表示。其中,每个用户有多少个好友,对应到图中,就叫做顶点的度。
41 |
42 |
43 |
44 | - 微博的社交关系,跟微信还有点不一样,或者说更加复杂一点。微博允许单向关注,也就是说,用户 A 关注了用户 B,但用户 B 可以不关注用户 A。入度就表示有多少粉丝,出度就表示关注了多少人。
45 |
46 |
47 |
48 | - QQ的社交关系更要复杂一点,qq 密度这样一个功能,不仅记录了用户之间的好友关系,还记录了两个用户之间的亲密度,如果两个用户经常往来,那亲密度就比较高;如不经常往来,那亲密度就比较低。
49 |
50 |
51 |
52 | ## 邻接矩阵(Adjacency Matrix)
53 |
54 | - 图最直观的一种存储方法就是,邻接矩阵。
55 |
56 | - 邻接矩阵的底层依赖一个二维数组,对于无向图来说,如果顶点 i 与 顶点 j 之间有边,就将 A\[i][j] 和 A\[j][i] 标记为 1;对于有向图来说,如果顶点 i 与 顶点 j 之间,有一条箭头从顶点 i 指向顶点 j 的边,那就将 A\[i][j] 标记为 1。对于带权图,数组中就存储相应的权重。
57 |
58 |
59 |
60 | - 缺点:浪费存储空间
61 |
62 | 对于无向图来说,如果 A\[i][j] 等于 1,那 A\[j][i] 也肯定等于 1. 实际上,只需要存储一个就可以了,也就是说,无向图的二维数组中,如果将其用对角线划分为上下两部分,那只需要利用上面或者下面这样一半的空间就足够了,另外一半白白浪费掉了。
63 |
64 | 还有,如果存储的是 **稀疏图(Sparse Matrix)**,也就是说,顶点很多,但每个顶点的边并不多,那邻接矩阵的存储方法就更加浪费空间了。比如,微信有好几亿的用户,对应到图上就是好几亿的顶点。但是每个用户的好友并不会很多,一般也就三五百个而已,如果用邻接矩阵来存储,那绝大部分的存储空间都被浪费了。
65 |
66 | - 优点::
67 |
68 | 1. 简单、直接:因为基于数组,所以在获取两个顶点的关系时,就非常高效;
69 |
70 | 1. 方便计算:因为用邻接矩阵的方式存储图,可将很多图的运算转换成矩阵之间的运算。
71 |
72 | 例子:求解最短路径问题时的会提到一个,`Floyd-Warshall 算法`,就是利用矩阵循环相乘若干次得到的结果。
73 |
74 | ## 邻接表(Adjacency List)
75 |
76 | - 邻接表如下图,每个顶点对应一条链表,链表中存储的是与这个顶点相连接的其他顶点。另外,图中画的是一个有向图的邻接表存储方式,每个顶点对应的链表,存储的是指向的顶点。对于无向图来说,也是类似的,不过,每个顶点的链表中存储的,是跟这个顶点有边相连的顶点:
77 |
78 |
79 |
80 | - 时间、空间复杂度互换:
81 |
82 | 邻接矩阵比较浪费空间,但是使用起来比较节省时间;相反,邻接表存储起来比较节省空间,但是使用起来比浪费时间。
83 |
84 | 就像图中的例子,如果确定,是否存在一条从顶点2到顶点4的边,那就要遍历顶点2对应的那条链表,看链表中是否存在顶点4;而且,前面讲过,链表的存储方式对缓存不友好。所以,比起,邻接矩阵的存储方式,在邻接表中查询两个顶点之间的关系就没有那么高效了。
85 |
86 | 在散列表中讲过,在基于链表法解决冲突的散列表中,如果链过长,为了提高查找效率,可以将链表换成其他更加高效的数据结构,比如平衡二叉查找树等。刚刚也讲到,邻接表长的很像散列表,所以,可以将邻接表同散列表一样进行"改进升级"。
87 |
88 | 可将邻接表中的链表改成平衡二叉查找树,实际开发中,可以选择用红黑树。这样,就可以更加快速地查找两个顶点之间是否存在边。当然,这里的二叉查找树,可以换成其他动态数据结构,比如跳表、散列表等;除此之外,还可以将链表改成有序动态数组,可以通过二分查找的方法来快速定位两个顶点之间是否是存在边。
89 |
90 |
91 |
92 | ## 思考
93 |
94 | 1. 如何存储微博、微信等这些社交网络的好友关系?
95 |
96 | 前面分析了,微博、微信是两种 "图",前者是有向图,后者是无向图。在这个问题上,两者的解决思路差不多,所以只拿微博来讲解。
97 |
98 | *数据机构是为算法服务的,所以具体选择哪种存储方法,与期望支持的操作有关系。针对微博用户关系,假设需要支持下面这样几个操作:*
99 |
100 | 1. 判断用户 A 是否关注了用户 B;
101 | 1. 判断用户 A 是否是用户 B 的粉丝;
102 | 1. 用户 A 关注用户 B;
103 | 1. 用户 A 取消关注用户 B;
104 | 1. 根据用户名称的首字母排序,分页获取用户的粉丝列表;
105 | 1. 根据用户名称的首字母排序,分页获取用户的关注列表。
106 |
107 | 关于如何存储一个图,前面讲到两种主要的存储方法,邻接矩阵和邻接表。因为社交网络是一张稀疏图,使用邻接矩阵存储比较浪费就存储空间。所以,这里才用了邻接表来存储。
108 |
109 | 不过,用一个邻接表来存储这种有向图是不够的。去查找某个用户关注了哪些用户非常容易,但是如果要想知道某个用户都被哪些用户关注了,也就是用户的粉丝列表,是非常困难的。
110 |
111 | 基于此,需要一个逆邻接表。邻接表中存储了用户的关注关系,逆邻接表中存储的是用户的被关注关系。对应到图上,邻接表中,每个顶点的链表中,存储的就是这个顶点指向的顶点,逆邻接表中,每个顶点的链表中,存储的是指向这个顶点的顶点。
112 |
113 | 如果要查找某个用户关注了哪些用户,可以在邻接表中查找;如果要查找某个用户被哪些用户关注了,从逆邻接表中查找。
114 |
115 |
116 |
117 | 基础的邻接表不适合快速判断两个用户之间是否是关注与被关注的关系,所以选择改进版本,将邻接表中的链表改为支持快速查找的动态数据结构。但是选择哪种动态数据结构呢?红黑树、跳表、有序动态数组还是散列表?
118 |
119 | 因为需要按照用户名称的首字母排序,分页来获取用户的粉丝列表或者关注列表,用跳表这种结构再合适不过了。这是因为,跳表插入、删除、查找都非常高效,时间复杂度是 $O(logn)$,空间复杂度上稍高,是$O(n)$.最重要的一点,跳表中存储的数据本来就是有序的,分页获取粉丝列表或关注列表,就非常高效。
120 |
121 | 如果对于小规模的数据,比如社交网络中只有几万、几十万个用户,可以将整个社交关系存储在内存中,上面的解决思路是没有问题的。但是如果像微博那样有上亿的用户,数据规模太大,就无法全部存储在内存中了。这个时候,怎么办呢?
122 |
123 | 可以通过哈希算法等数据分片方式,将邻接表存储在不同的机器上。可以看下面这幅图,在机器1上存储顶点1、2、3的邻接表,在机器2上,存储顶点4、5的邻接表。逆邻接表的处理方式也一样,当要查询顶点与顶点的关系的时候,就利用同样的哈希算法,先定位顶点所在的机器,然会再相应的机器上查找。
124 |
125 |
126 |
127 | 除此之外,还有另外一种解决思路,就是利用外部存储(比如硬盘),因为外部存储的存储空间要比内存会宽裕很多。数据库是经常来持久化存储关系数据的,所以这里介绍一种数据库的存储方式。
128 |
129 | 下面这张表来存储这样一个图,为了高效地支持前面定义的操作,可以在表上建立多个索引,比如第一列、第二列,给这两列都建立索引:
130 |
131 |
132 |
133 | 2. 像微信这种无向图,因该怎么存储?
134 |
135 | 微信好友关系存储方式。无向图,也可以使用邻接表的方式存储每个人所对应的好友列表。为了支持快速查找,好友列表可以使用红黑树存储。
136 |
137 | 2. 除了今天局的社交网络可以用图来表示之外,符合图这种结构特点的例子还有很多,比如知识图谱(Knowledge Graph),关于图这种数据结构,还能想到其他生活或工作中的例子吗?
138 |
139 | 生活工作中应用图的例子。很多,互联网上网页之间通过超链接连接成一张有向图;城市乃至全国交通网络是一张加权图;人与人之间的人际关系够成一张图,著名的六度分割理论据说就是基于这个得到的。
140 |
141 | *`@ 笔记时间 :2021-02- 12 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part2 数据结构(Data Structure)/16. B+树.md:
--------------------------------------------------------------------------------
1 | # B+ 树
2 |
3 | [toc]
4 |
5 | ## 算法解析 :思考的过程比结论更重要
6 |
7 | ### 1. 解决问题的前提是定义清楚问题
8 |
9 | 如何定义清楚问题呢?除了对问题进行详细的调研,还有一个办法,就是,`通过对一些模糊的需求进行假设,来限定要解决的问题的范围。`
10 |
11 | 首先,假设要解决的问题,只包含这样两个常用的需求:
12 |
13 | - 根据某个值查找数据,比如 SELECT * FROM user WHERE id = 1234;
14 | - 根据区间值来查找某些数据,比如 SELECT * FROM user WHERE id > 1234 and id < 2345.
15 |
16 | 除了这些功能性需求之外,往往还涉及一些非功能性需求,比如安全、性能、用户体验等等。限于专栏要讨论的主要是数据结构和算法,对于非功能性需求,着重考虑 **性能方面**的需求。性能方面的需求,主要考察时间和空间两方面,也就是 **执行效率和存储空间**。
17 |
18 | 执行效率,希望通过索引,查询数据的效率尽可能地高;在存储空间方面,希望索引不要消耗太多的内存空间。
19 |
20 | ### 2. 尝试用学过的数据结构解决这个问题
21 |
22 | 问题的需求大致定义清楚了,现在回想一下,能否利用已经学过的数据结构解决这个问题?
23 |
24 | 支持快速查询、插入等操作的动态数据结构:散列表、平衡二叉查找树、跳表。
25 |
26 | - 散列表:散列表的查询性能很好,时间复杂度是 $O(1)$。但是,散列表不能支持按照区间快速查找数据,所以,散列表不能满足需求。
27 |
28 | - 平衡二叉查找树:尽管平衡二叉查找树查询的性能也很高,时间复杂度是 $O(logn)$。而且,对树进行中序遍历,还可以得到一个从小到大有序的数据序列,但这仍然不足以支持按照区间快速查找数据。
29 |
30 | - 跳表:跳表是在链表之上加上多层索引构成的。它支持快速地插入、查找、删除数据,对应的时间复杂度是 $O(logn)$.并且,跳表也支持按照区间快速地查找数据。只需要定位到区间起点值对应在链表中的节点,然后从这个节点开始,顺序遍历链表,直到区间终点对应的结点为止,这期间遍历得到的数据就是满足区间值得数据。
31 |
32 |
33 |
34 | 这样看来,跳表是可以解决这个问题。实际上,数据库索引所用到的数据结构跟跳表非常相似,叫作 **B+ 树**。不过,它是通过二叉树演化过来的,而非跳表。
35 |
36 | ### 3. 改造二叉查找树来解决这个问题
37 |
38 | 为了让二叉查找树按照区间来查找数据,可以对它进行这样的改造:树中的节点并不存储数据本身, 而是只是作为索引。除此之外,把每个叶子节点串在一条链表上,链表中的数据从小到大是有序的。经过改造之后的二叉树,如下图,看起来很像跳表:
39 |
40 |
41 |
42 | 改造之后,如果要求某个区间的数据,只需要拿区间的起始值,在树中进行查找,当查找到某个叶子节点之后,再顺着链表往后遍历,直到链表中的节点数据值大于区间的终止值为止。所有遍历到的数据,就是符合区间值得所有数据。
43 |
44 |
45 |
46 | 但是,要为几千万、上亿的数据构建索引,如果将索引存储在内存中,尽管内存访问的速度非常快,查询的效率非常高,但是,占用的内存会非常多。
47 |
48 | 比如,给一亿个数据构建二叉查找树索引,那索引中会包含大约1一亿个节点,每个节点假设占用16个字节,那就需要大约 1GB 的内存空间。给一张表建立索引,需要 1GB 的内存空间。如果要给 10 张表建立索引,那对内存的需求是无法满足的,如何解决这个索引占用太多内存的问题?
49 |
50 | 可以借助时间换空间的思路,把索引存储在硬盘中,而非内存中。但,硬盘是一个非常慢速的存储设备,通常内存的访问速度是纳秒级别的,而磁盘访问的速度是毫秒级别的。读取同样大小的数据,从磁盘中读取花费的时间,是从内存中读取所花费时间的上万倍,甚至是几十万倍。
51 |
52 | 这种将索引存储在硬盘的方案,尽管减少了内存消耗,但是在数据查找的过程中,需要读取磁盘中的索引,因此数据查询效率就相应降低很多。
53 |
54 | 二叉查找树,经过改造之后,支持区间查找的功能就实现了。不过了,为了节省内存,如果把树存储在硬盘中,那么每个节点的读取(或者访问),都对应依次磁盘 IO 操作。树的高度就等于每次查询数据时磁盘 IO 操作的次数。
55 |
56 | 前面讲到,比起内存读写操作,磁盘 IO 操作非常耗时,所以优化的重点就是尽量减少磁盘 IO 操作,也就是,尽量降低树的高度。那如何降低树的高度呢?
57 |
58 | 如果把索引构建成 m 叉树,高度是不是比 二叉树要小呢?如图所示,给 16 个数据构建二叉树索引,树的高度是4,查找一个数据,就需要 4 个磁盘 IO 操作(如果根节点存储在内存中,其他节点存储在磁盘中),如果对16个数据构建五叉树索引,那高度只有2,查找一个数据,对应只需要 2 次磁盘操作。如果 m 叉树中的 m 是 100,那对一亿个数据构建索引,树的高度也只是 3,最多只要 3 次磁盘 IO 就能获取到数据。磁盘 IO 变少了,查找数据的效率也就提高了。
59 |
60 |
61 |
62 |
63 |
64 | 如果将 m 叉树实现 B+ 树索引,用代码实现出来,就是下面这个样子(假设给 int 类型的数据库字段添加索引,所以代码中的 kewwords 是 int 类型的):
65 |
66 | ```java
67 | /**
68 | * 这是B+树非叶子节点的定义。
69 | *
70 | * 假设keywords=[3, 5, 8, 10]
71 | * 4个键值将数据分为5个区间:(-INF,3), [3,5), [5,8), [8,10), [10,INF)
72 | * 5个区间分别对应:children[0]...children[4]
73 | *
74 | * m值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:
75 | * PAGE_SIZE = (m-1)*4[keywordss大小]+m*8[children大小]
76 | */
77 | public class BPlusTreeNode {
78 | public static int m = 5; // 5叉树
79 | public int[] keywords = new int[m-1]; // 键值,用来划分数据区间
80 | public BPlusTreeNode[] children = new BPlusTreeNode[m];//保存子节点指针
81 | }
82 |
83 | /**
84 | * 这是B+树中叶子节点的定义。
85 | *
86 | * B+树中的叶子节点跟内部节点是不一样的,
87 | * 叶子节点存储的是值,而非区间。
88 | * 这个定义里,每个叶子节点存储3个数据行的键值及地址信息。
89 | *
90 | * k值是事先计算得到的,计算的依据是让所有信息的大小正好等于页的大小:
91 | * PAGE_SIZE = k*4[keyw..大小]+k*8[dataAd..大小]+8[prev大小]+8[next大小]
92 | */
93 | public class BPlusTreeLeafNode {
94 | public static int k = 3;
95 | public int[] keywords = new int[k]; // 数据的键值
96 | public long[] dataAddress = new long[k]; // 数据地址
97 |
98 | public BPlusTreeLeafNode prev; // 这个结点在链表中的前驱结点
99 | public BPlusTreeLeafNode next; // 这个结点在链表中的后继结点
100 | }
101 | ```
102 |
103 | 代码说明:对于相同个数的数据构建 m 叉树索引,m 叉树中的 m 越大,那树的高度就越小,那 m 叉树中的 m 是不是越大越好呢?到底多大才最合适?
104 |
105 | 不管是内存中的数据,还是磁盘中的数据,操作系统都是按页(一页大小通常是 4KB,这个值可以通过 getconfig PAGE_SIZE 命令查看)来读取的,一次会读取一页的数据。如果要读取的数据量超过一页的大小,就会触发多次 IO 操作。所以,在选择 m 大小的时候,要尽量让每个节点的大小等于一个页的大小。读取一个节点,只需要一次磁盘 IO 操作。
106 |
107 |
108 |
109 | 尽管索引可以提高数据库的查询效率,但是,作为一名开发工程师,索引有利也有弊,它会让写入数据的效率下降。
110 |
111 | 原因如下:
112 |
113 | 对于一个 B+ 树来说,m 值是根据页的大小事先计算好的,即,每个节点最多只能有 m 个子节点。在往数据库中写入数据的过程中,这样就可能使索引中某些节点的子节点个数超过 m,这个节点的大小超过了一个页的大小,读取这样一个节点,就会导致多次磁盘 IO 操作。如何解决呢?
114 |
115 | 实际上,处理思路并不复杂。只需要将这个节点分裂成两个节点。但是,节点分裂之后,其上层父节点的子节点个数就有可能超过 m 个。不过,这也没关系,可以用同样的方法,将父节点也分裂成两个节点。这种级联反应会从下往上,一直影响到根节点。这个分裂过程,可以结合下图一起看,会更容易理解(图中的 B+ 树一个三叉树,限定叶子节点中,数据的个数超过 2 个就分裂节点;非叶子节点中,子节点的个数超过 3 个就分裂节点)。
116 |
117 |
118 |
119 | 正是因为要时刻保证 B+ 树索引是一个 m 叉树,所以,索引的存在会导致数据库写入的速度降低。实际上,不光写入数据会变慢,删除数据也会变慢。
120 |
121 | 这是因为,在删除某个数据的时候,也要对应地更新索引节点,这个处理思路有点类似跳表中删除数据的处理思路。频繁的数据删除,就会导致某些节点中,子节点的个数变得非常少,长此以往,如果每个节点的子节点都比较少,势必会影响索引的效率。
122 |
123 | 所以,可以设置一个阙值。在B+树种,这个阙值等于 $m/2$。如果某个节点的子节点个数小于 $m/2$,就将它根相邻的兄弟节点合并。不过,合并之后节点的子节点个个数有可能会超过 m。针对这种情况,可以借助插入数据时候的处理方法,再分裂节点。
124 |
125 | 如下图所示:图中的B+树是一个五叉树,限定叶子节点中,数据的个数少于2个就合并节点;非叶子节点中,子节点的个数少于3个就合并节点。
126 |
127 |
128 |
129 | B+树的结构和操作,跟跳表非常类似,理论上讲,对跳表稍加改造,也可以替代B+树,作为数据库的索引实现。
130 |
131 | ## 总结引申
132 |
133 | 数据库索引的实现,依赖底层数据结构:B+ 树。它通过存储在磁盘的多叉树结构,做到了时间、空间的平衡,既保证了执行效率,又节省了内存。
134 |
135 | #### B+ 树
136 |
137 | B+ 树特点总结:
138 |
139 | - 每个节点中子节点的个数不能超过 $m$,也不能小于 $m/2$;
140 | - 根节点的子节点个数可以不超过 $m/2$,这是一个例外;
141 | - $m$ 叉树只存储索引,并不真正存储数据,这个有点儿类似跳表;
142 | - 通过链表将叶子节点串联在一起,这样可以方便按区间查找;
143 | - 一般情况,根节点会被存储在内存中,其他节点存储在磁盘中。
144 |
145 | #### B 树
146 |
147 | 除了 B+ 树,还有 B 树、B- 树 实际上,B- 树就是 B 树,英文翻译都是 B-Tree,这里的“-”并不是相对 B+ 树中的“+”,而只是一个连接符。
148 |
149 | 而 B 树实际上是低级版的 B+ 树,或者说 B+ 树是 B 树的改进版。B 树跟 B+ 树的不同点主要集中在这几个地方:
150 |
151 | - B+ 树中的节点不存储数据,只是索引,而 B 树中的节点存储数据;
152 | - B 树中的叶子节点并不需要链表来串联。
153 |
154 | 也就是说,B 树只是一个每个节点的子节点个数不能小于 m/2 的 m 叉树。
155 |
156 |
157 |
158 | *`@ 笔记时间 :2021-02- 14 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/01. 排序算法.md:
--------------------------------------------------------------------------------
1 | # 排序算法
2 |
3 | ## Outline
4 |
5 | [toc]
6 |
7 | ## 导读
8 |
9 | - 经典排序算法:冒泡排序、插入排序、选择排序、归并排序、快速排序、计数排序、基数排序、桶排序。
10 | - 按复杂度划分:
11 | - $O(n^2)$ : 冒泡排序、插入排序、选择排序.
12 | - $O(nlogn)$ : 快速排序、归并排序.
13 | - $O(n)$ : 桶排序、计数排序、基数排序.
14 |
15 | ## 如何分析一个排序算法
16 |
17 | 学习排序算法,除了学习它的算法原理、代码实现之外,更重要的是要学会如何评价、分析一个排序算法。
18 |
19 | ### **排序算法的执行效率**
20 |
21 | #### 1. 最好情况、最坏情况、平均情况时间复杂度
22 |
23 | 在分析排序算法的时间复杂度时,要分别给出最好情况、最坏情况、平均情况下的时间复杂度。除此之外,还要说出最好、最坏时间复杂度对应的要排序的原始数据是什么样的。
24 |
25 | 为什么要区分这三种时间复杂度呢?
26 |
27 | 第一,有些排序算法会区分,为了好对比,所以我们最好都做一下区分。
28 |
29 | 第二,对于要排序的数据,有的接近有序,有的完全无序。有序度不同的数据,对于排序的执行时间肯定是有影响的,所以要知道排序算法在不同数据下的性能表现。
30 |
31 | #### 2. 时间复杂度的系数、常数 、低阶
32 |
33 | 时间复杂度反映的是数据规模 n 很大的时候的一个增长趋势,所以它表示的时候会忽略系数、常数、低阶。
34 |
35 | 但是实际的软件开发中,排序的可能是 10 个、100 个、1000 个这样规模很小的数据,所以,在对同一阶时间复杂度的排序算法性能对比的时候,就要把系数、常数、低阶也考虑进来。
36 |
37 | #### 3. 比较次数和交换(或移动)次数
38 |
39 | 基于比较的排序算法的执行过程,会涉及两种操作,一种是元素比较大小,另一种是元素交换或移动。
40 |
41 | 所以,如果在分析排序算法的执行效率的时候,应该把比较次数和交换(或移动)次数也考虑进去。
42 |
43 | ### **排序算法的内存消耗**
44 |
45 | 算法的内存消耗可以通过空间复杂度来衡量,排序算法也不例外。
46 |
47 | 不过,针对排序算法的空间复杂度,还引入了一个新的概念,**原地排序(Sorted in place)**。
48 |
49 | **原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。**
50 |
51 | 冒泡、插入、选择排序,这三种排序算法,都是原地排序算法。
52 |
53 | ### **排序算法的稳定性**
54 |
55 | 仅仅用执行效率和内存消耗来衡量排序算法的好坏是不够的。针对排序算法,还有一个重要的度量指标,**稳定性**。
56 |
57 | **稳定性是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。**
58 |
59 | 例子:有一组数据 2、9、 3、 4、 8、 3, 按照大小排序之后就是 2、3、3、4、8、9。
60 |
61 | 这组数据里有两个 3。经过某种排序算法排序之后,如果两个 3 的前后顺序没有改变,那就把这种排序算法叫作**稳定的排序算法**;如果前后顺序发生变化,那对应的排序算法就叫作**不稳定的排序算法**。
62 |
63 | 你可能要问了,两个 3 哪个在前,哪个在后有什么关系啊,稳不稳定又有什么关系呢?为什么要考察排序算法的稳定性呢?
64 |
65 | 很多数据结构和算法课程,在讲排序的时候,都是用整数来举例,但在真正软件开发中,要排序的往往不是单纯的整数,而是一组对象,需要按照对象的某个 key 来排序。
66 |
67 | 比如说,现在要给电商交易系统中的“订单”排序。订单有两个属性,一个是下单时间,另一个是订单金额。如果现在有 10 万条订单数据,希望按照金额从小到大对订单数据排序。对于金额相同的订单,希望按照下单时间从早到晚有序。
68 |
69 | 对于这样一个排序需求,怎么来做呢?
70 |
71 | 最先想到的方法是:先按照金额对订单数据进行排序,然后,再遍历排序之后的订单数据,对于每个金额相同的小区间再按照下单时间排序。这种排序思路理解起来不难,但是实现起来会很复杂。
72 |
73 | 借助稳定排序算法,这个问题可以非常简洁地解决。解决思路是这样的:先按照下单时间给订单排序,注意是按照下单时间,不是金额。排序完成之后,用稳定排序算法,按照订单金额重新排序。两遍排序之后,得到的订单数据就是按照金额从小到大排序,金额相同的订单按照下单时间从早到晚排序的。为什么呢?
74 |
75 | **稳定排序算法可以保持金额相同的两个对象,在排序之后的前后顺序不变。**
76 |
77 | 第一次排序之后,所有的订单按照下单时间从早到晚有序了。在第二次排序中,我们用的是稳定的排序算法,所以经过第二次排序之后,相同金额的订单仍然保持下单时间从早到晚有序。
78 |
79 | 
80 |
81 |
82 |
83 | *`@ 笔记时间 :2020-8-19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/02. 冒泡排序 (Bubble Sort).md:
--------------------------------------------------------------------------------
1 | # 冒泡排序 (Bubble Sort)
2 |
3 | ## 概念
4 |
5 | > 冒泡排序只会操作相邻的两个数据。
6 | >
7 | > 每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。
8 | >
9 | > 如果不满足就让它俩互换。
10 | >
11 | > 一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。
12 |
13 | ## 举例说明
14 |
15 | 将一组数据 : 4, 5, 6, 3, 2, 1 从 小到大进行排序。
16 |
17 | - 第一次冒泡操作的详细过程:
18 |
19 | 
20 |
21 | 经过一次冒泡操作之后,6 这个元素已经存储在正确的位置上。
22 |
23 | - 要想完成所有数据的排序,只要进行 6 次这样的冒泡操作就行了:
24 |
25 | 
26 |
27 | ## 未优化代码 :全部遍历
28 |
29 | ```java
30 | // java code
31 | // 冒泡排序,a表示数组,n表示数组大小
32 | public void bubbleSort(int[] a, int n) {
33 | if (n <= 1) return;
34 |
35 | for (int i = 0; i < n; ++i) {
36 | for (int j = 0; j < n - i - 1; ++j) {
37 | if (a[j] > a[j+1]) { // 交换
38 | int tmp = a[j];
39 | a[j] = a[j+1];
40 | a[j+1] = tmp;
41 | }
42 | }
43 | }
44 | }
45 | ```
46 |
47 |
48 |
49 | ## 过程优化
50 |
51 | **当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。**
52 |
53 | 例子:下面给出要对 6 个元素进行冒泡排序,但是只需要 4 次冒泡操作就可以了。
54 |
55 | 
56 |
57 | ## 优化代码:没有交换(已排好序),提前结束
58 |
59 | ```java
60 | // java code
61 | // 冒泡排序,a表示数组,n表示数组大小
62 | public void bubbleSort(int[] a, int n) {
63 | if (n <= 1) return;
64 |
65 | for (int i = 0; i < n; ++i) {
66 | // 提前退出冒泡循环的标志位
67 | boolean flag = false;
68 | for (int j = 0; j < n - i - 1; ++j) {
69 | if (a[j] > a[j+1]) { // 交换
70 | int tmp = a[j];
71 | a[j] = a[j+1];
72 | a[j+1] = tmp;
73 | flag = true; // 表示有数据交换
74 | }
75 | }
76 | if (!flag) break; // 没有数据交换,提前退出
77 | }
78 | }
79 | ```
80 |
81 | ### 分析:
82 |
83 | **第一,冒泡排序是原地排序算法吗?**
84 |
85 | 冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为 $O(1)$,是一个原地排序算法。
86 |
87 | **第二,冒泡排序是稳定的排序算法吗?**
88 |
89 | 在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法。
90 |
91 | **第三,冒泡排序的时间复杂度是多少?**
92 |
93 | - 最好情况:要排序的数据已经是有序的了,只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是 $O(n)$。
94 | - 最坏情况:要排序的数据刚好是倒序排列的,需要进行 n 次冒泡操作,所以最坏情况时间复杂度为 $O(n^2)$。
95 |
96 | 
97 |
98 | - 平均情况 :如果用概率论方法定量分析平均时间复杂度,涉及的数学推理和计算就会很复杂。这里还有一种思路,通过**"有序度"**和**"逆序度"**这两个概念来进行分析。
99 |
100 |
101 |
102 | ### 用 "有序度" 和 "逆序度" 来代替平均复杂度
103 |
104 | 对于包含 n 个数据的数组,这 n 个数据就有 n! 种排列方式。不同的排列方式,冒泡排序执行的时间肯定是不同的。比如前面举的那两个例子,其中一个要进行 6 次冒泡,而另一个只需要 4 次。
105 |
106 | #### 有序度
107 |
108 | > 有序度是数组中具有有序关系的元素对的个数。
109 |
110 | 有序元素对用数学表达式表示:
111 | $$
112 | 有序元素对 : 如果 i < j, a[i] <= a[j].
113 | $$
114 |
115 |
116 | 
117 |
118 | 同理,对于一个倒序排列的数组,比如 6,5,4,3,2,1,有序度是 0;对于一个完全有序的数组,比如 1,2,3,4,5,6,有序度就是 $n*(n-1)/2$,也就是 15。把完全有序的数组的有序度叫作**满有序度**。
119 |
120 | >满有序度推导:
121 | >$$
122 | >1+2 +...+n-1 = (1+2+..+n-1+n)-n=\frac{n(1+n)}{2}-n=\frac{n(n-1)}{2}
123 | >$$
124 |
125 | #### 逆序度
126 |
127 | 逆序度的定义正好跟有序度相反(默认从小到大为有序),关于逆序度,就不举例子讲了。可以对照有序度的例子自己看下。
128 |
129 | 逆序元素对用数学表达式表示:
130 | $$
131 | 逆序元素对:a[i] > a[j], 如果i < j。
132 | $$
133 | 关于这三个概念,还可以得到一个公式:
134 | $$
135 | 逆序度 = 满有序度 - 有序度。
136 | $$
137 |
138 | ### 分析前面举例的冒泡排序
139 |
140 | 要排序的数组的初始状态是 4,5,6,3,2,1 ,其中,有序元素对有 (4,5) (4,6)(5,6),所以有序度是 3。
141 |
142 | n=6,所以排序完成之后终态的满有序度为 n*(n-1)/2=15。
143 |
144 | 
145 |
146 | 冒泡排序包含两个操作原子,**比较**和**交换**。每交换一次,有序度就加 1。不管算法怎么改进,交换次数总是确定的,即为**逆序度,也就是n*(n-1)/2–初始有序度**。
147 |
148 | 此例中就是 15–3=12,要进行 12 次交换操作。
149 |
150 | 对于包含 n 个数据的数组进行冒泡排序,平均交换次数是多少呢?
151 |
152 | - 最坏情况:初始状态的有序度是 $0$,所以要进行$ n*(n-1)/2$ 次交换。
153 | - 最好情况:初始状态的有序度是 $n*(n-1)/2$,就不需要进行交换。
154 | - 平均情况:可以取个中间值 $n*(n-1)/4$,来表示初始有序度既不是很高也不是很低的平均情况。
155 |
156 | 换句话说,平均情况下,需要 $n*(n-1)/4$ 次交换操作,比较操作肯定要比交换操作多,而复杂度的上限是 $O(n^2)$,所以平均情况下的时间复杂度就是 $O(n^2)$。
157 |
158 | 这个平均时间复杂度推导过程其实并不严格,但是很多时候很实用,毕竟概率论的定量分析太复杂,不太好用。等讲到快排的时候,还会再次用这种“不严格”的方法来分析平均时间复杂度。
159 |
160 | *`@ 笔记时间 :2020-8-19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/03. 插入排序 (Insertion Sort).md:
--------------------------------------------------------------------------------
1 | # 插入排序 (Insertion Sort)
2 |
3 | - 问题:一个有序的数组,往里面添加一个新的数据后,如何继续保持数据有序呢?很简单,只要遍历数组,找到数据应该插入的位置将其插入即可。
4 |
5 | 
6 |
7 | 这是一个动态排序的过程,即动态地往有序集合中添加数据,可以通过这种方法保持集合中的数据一直有序。
8 |
9 | 对于一组静态数据,也可以借鉴上面讲的插入方法,来进行排序,于是就有了插入排序算法。
10 |
11 | ## 概念
12 |
13 | >将数组中的数据分为两个区间,**已排序区间**和**未排序区间**。
14 | >
15 | >初始已排序区间只有一个元素,就是数组的第一个元素。
16 | >
17 | >插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。
18 | >
19 | >重复这个过程,直到未排序区间中元素为空,算法结束。
20 |
21 |
22 |
23 | ## 插入排序的实现
24 |
25 | 如图所示,要排序的数据是 4,5,6,1,3,2,其中左侧为已排序区间,右侧是未排序区间。
26 |
27 | 
28 |
29 | 插入排序也包含两种操作,一种是**元素的比较**,一种是**元素的移动**。
30 |
31 | 当需要将一个数据 a 插入到已排序区间时,需要拿 a 与已排序区间的元素依次比较大小,找到合适的插入位置。找到插入点之后,还需要将插入点之后的元素顺序往后移动一位,这样才能腾出位置给元素 a 插入。
32 |
33 | 对于不同的查找插入点方法(从头到尾、从尾到头),元素的比较次数是有区别的。但对于一个给定的初始序列,**移动操作的次数总是固定的,就等于逆序度。**
34 |
35 | 为什么说移动次数就等于逆序度呢?拿刚才的例子画了一个图表,一看就明白了。满有序度是 n*(n-1)/2=15,初始序列的有序度是 5,所以逆序度是 10。插入排序中,数据移动的个数总和也等于 10=3+3+4。
36 |
37 | ## 代码
38 |
39 | ```java
40 | // java code
41 |
42 | // 插入排序,a表示数组,n表示数组大小
43 | public void insertionSort(int[] a, int n) {
44 | if (n <= 1) return;
45 |
46 | for (int i = 1; i < n; ++i) {
47 | int value = a[i];
48 | int j = i - 1;
49 | // 查找插入的位置
50 | for (; j >= 0; --j) {
51 | if (a[j] > value) {
52 | a[j+1] = a[j]; // 数据移动
53 | } else {
54 | break;
55 | }
56 | }
57 | a[j+1] = value; // 插入数据
58 | }
59 | }
60 | ```
61 |
62 | ### 分析
63 |
64 | **第一,插入排序是原地排序算法吗?**
65 |
66 | 从实现过程可以很明显地看出,插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个原地排序算法。
67 |
68 | **第二,插入排序是稳定的排序算法吗?**
69 |
70 | 在插入排序中,对于值相同的元素,可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变,所以插入排序是稳定的排序算法。
71 |
72 | **第三,插入排序的时间复杂度是多少?**
73 |
74 | 如果要排序的数据已经是有序的,并不需要搬移任何数据。
75 |
76 | 如果从尾到头在有序数据组里面查找插入位置,每次只需要比较一个数据就能确定插入的位置。所以这种情况下,最好是时间复杂度为 $O(n)$。
77 |
78 | 注意,这里是从尾到**头遍历已经有序的数据**。
79 |
80 | 如果数组是倒序的,每次插入都相当于在数组的第一个位置插入新的数据,所以需要移动大量的数据,所以最坏情况时间复杂度为$ O(n^2)$。
81 |
82 | 还记得在数组中插入一个数据的平均时间复杂度是多少吗?没错,是 $O(n)$。所以,对于插入排序来说,每次插入操作都相当于在数组中插入一个数据,循环执行 n 次插入操作,所以平均时间复杂度为 $O(n^2)$。
83 |
84 | *`@ 笔记时间 :2020-8-19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/04. 冒泡 VS 插入.md:
--------------------------------------------------------------------------------
1 | # 为什么插入排序比冒泡排序更受欢迎?
2 |
3 | 冒泡排序和插入排序的时间复杂度都是 O(n2),都是原地排序算法,为什么插入排序要比冒泡排序更受欢迎呢?
4 |
5 | 前面分析冒泡排序和插入排序的时候讲到,冒泡排序不管怎么优化,元素交换的次数是一个固定值,是原始数据的逆序度。插入排序是同样的,不管怎么优化,元素移动的次数也等于原始数据的逆序度。
6 |
7 | 但是,从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。我们来看这段操作:
8 |
9 | ```java
10 | // java code
11 |
12 |
13 | // 冒泡排序中数据的交换操作:
14 | if (a[j] > a[j+1]) { // 交换
15 | int tmp = a[j];
16 | a[j] = a[j+1];
17 | a[j+1] = tmp;
18 | flag = true;
19 | }
20 |
21 | // 插入排序中数据的移动操作:
22 | if (a[j] > value) {
23 | a[j+1] = a[j]; // 数据移动
24 | } else {
25 | break;
26 | }
27 | ```
28 |
29 | 把执行一个赋值语句的时间粗略地计为单位时间(unit_time),然后分别用冒泡排序和插入排序对同一个逆序度是 K 的数组进行排序。用冒泡排序,需要 K 次交换操作,每次需要 3 个赋值语句,所以交换操作总耗时就是 3*K 单位时间。而插入排序中数据移动操作只需要 K 个单位时间。
30 |
31 | 这个只是非常理论的分析,为了实验,针对上面的冒泡排序和插入排序的 Java 代码,可以用一个性能对比测试程序,随机生成 10000 个数组,每个数组中包含 200 个数据,然后在机器上分别用冒泡和插入排序算法来排序,冒泡排序算法大约 700ms 才能执行完成,而插入排序只需要 100ms 左右就能搞定!
32 |
33 | 所以,虽然冒泡排序和插入排序在时间复杂度上是一样的,都是 $O(n^2)$,但是如果希望把性能优化做到极致,那肯定首选插入排序。插入排序的算法思路也有很大的优化空间,现在只是讲了最基础的一种。如果对插入排序的优化感兴趣,可以自行学习一下**希尔排序**。
34 |
35 |
36 |
37 | *`@ 笔记时间 :2020-8-19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/05. 选择排序 (Selection Sort).md:
--------------------------------------------------------------------------------
1 | # 选择排序 (Selection Sort)
2 |
3 | 选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
4 |
5 | 
6 |
7 | ## 代码
8 |
9 | ```java
10 | // java code
11 |
12 | // 选择排序,a表示数组,n表示数组大小
13 | public void selectionSort(int[] a, int n) {
14 | if (n <= 1) return;
15 |
16 | // 有序区的末尾位置
17 | for(int i=0; i 如果要排序一个数组,先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。
6 |
7 |
8 |
9 | ### 分治思想
10 |
11 | 归并排序使用的就是**分治思想**。**分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。**
12 |
13 | 分治算法一般都是用递归来实现的。**(分治是一种解决问题的处理思想,递归是一种编程技巧)。**
14 |
15 | ## 实现
16 |
17 | **写递归代码的技巧就是,分析得出递推公式,然后找到终止条件,最后将递推公式翻译成递归代码。**
18 |
19 | 所以,要想写出归并排序的代码,先写出归并排序的递推公式。
20 |
21 | ```java
22 | 递推公式:
23 | merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
24 |
25 | 终止条件:
26 | p >= r 不用再继续分解
27 | ```
28 |
29 | - 解释:
30 |
31 | 1. merge_sort(p…r) 表示,给下标从 p 到 r 之间的数组排序。
32 |
33 | 2. 将这个排序问题转化为了两个子问题,merge_sort(p…q) 和 merge_sort(q+1…r),其中下标 q 等于 p 和 r 的中间位置,也就是 (p+r)/2。
34 |
35 | 3. 当下标从 p 到 q 和从 q+1 到 r 这两个子数组都排好序之后,再将两个有序的子数组合并在一起,这样下标从 p 到 r 之间的数据就也排好序了。
36 |
37 | ### 分解
38 |
39 | 有了递推公式,转化成代码就简单多了。为了阅读方便,这里只给出伪代码,可以翻译成熟悉的编程语言。
40 |
41 | ```java
42 | // 归并排序算法, A是数组,n表示数组大小
43 | merge_sort(A, n) {
44 | merge_sort_c(A, 0, n-1)
45 | }
46 |
47 | // 递归调用函数
48 | merge_sort_c(A, p, r) {
49 |
50 | // 递归终止条件
51 | if p >= r then return
52 |
53 | // 取p到r之间的中间位置q
54 | q = (p+r) / 2
55 |
56 | // 分治递归
57 | merge_sort_c(A, p, q)
58 | merge_sort_c(A, q+1, r)
59 |
60 | // 将A[p...q]和A[q+1...r]合并为A[p...r]
61 | merge(A[p...r], A[p...q], A[q+1...r])
62 | }
63 | ```
64 |
65 | ### 合并
66 |
67 | merge(A[p…r], A[p…q], A[q+1…r]) 这个函数的作用就是,将已经有序的 A[p…q]和 A[q+1…r]合并成一个有序的数组,并且放入 A[p…r]。
68 |
69 | 那这个过程具体该如何做呢?
70 |
71 | 如图所示,申请一个临时数组 tmp,大小与 A[p…r]相同。用两个游标 i 和 j,分别指向 A[p…q]和 A[q+1…r]的第一个元素。比较这两个元素 A[i]和 A[j],如果 A[i]<=A[j],就把 A[i]放入到临时数组 tmp,并且 i 后移一位,否则将 A[j]放入到数组 tmp,j 后移一位。
72 |
73 |
74 |
75 | merge() 函数写成伪代码,就是下面这样:
76 |
77 | ```java
78 | merge(A[p...r], A[p...q], A[q+1...r]) {
79 | var i := p,j := q+1,k := 0 // 初始化变量i, j, k
80 | var tmp := new array[0...r-p] // 申请一个大小跟A[p...r]一样的临时数组
81 | while i<=q AND j<=r do {
82 | if A[i] <= A[j] {
83 | tmp[k++] = A[i++] // i++等于i:=i+1
84 | } else {
85 | tmp[k++] = A[j++]
86 | }
87 | }
88 |
89 | // 判断哪个子数组中有剩余的数据
90 | var start := i,end := q
91 | if j<=r then start := j, end:=r
92 |
93 | // 将剩余的数据拷贝到临时数组tmp
94 | while start <= end do {
95 | tmp[k++] = A[start++]
96 | }
97 |
98 | // 将tmp中的数组拷贝回A[p...r]
99 | for i:=0 to r-p do {
100 | A[p+i] = tmp[i]
101 | }
102 | }
103 | ```
104 |
105 | 另外,merge() 合并函数如果借助**哨兵**,代码就会简洁很多。
106 |
107 | ## 性能分析
108 |
109 | ### 第一,归并排序是稳定的排序算法吗?
110 |
111 | 结合前面画的那张图和归并排序的伪代码,能发现,归并排序稳不稳定关键要看 merge() 函数,也就是两个有序子数组合并成一个有序数组的那部分代码。
112 |
113 | **在合并的过程中,如果 A[p…q]和 A[q+1…r]之间有值相同的元素,那可以像伪代码中那样,先把 A[p…q]中的元素放入 tmp 数组。**这样就保证了值相同的元素,在合并前后的先后顺序不变。
114 |
115 | 所以,归并排序是一个稳定的排序算法。
116 |
117 | ### 第二,归并排序的时间复杂度是多少?
118 |
119 | 归并排序涉及递归,时间复杂度的分析稍微有点复杂。
120 |
121 | 递归的适用场景是,一个问题 a 可以分解为多个子问题 b、c,那求解问题 a 就可以分解为求解问题 b、c。问题 b、c 解决之后,再把 b、c 的结果合并成 a 的结果。
122 |
123 | 如果定义求解问题 a 的时间是 T(a),求解问题 b、c 的时间分别是 T(b) 和 T( c),那就可以得到这样的递推关系式:
124 | $$
125 | T(a) = T(b) + T(c) + K
126 | $$
127 | 其中 K 等于将两个子问题 b、c 的结果合并成问题 a 的结果所消耗的时间。
128 |
129 | 从刚刚的分析,可以得到一个重要的结论:**不仅递归求解的问题可以写成递推公式,递归代码的时间复杂度也可以写成递推公式。**
130 |
131 | 套用这个公式,来分析一下归并排序的时间复杂度。
132 |
133 | 假设对 n 个元素进行归并排序需要的时间是 T(n),那分解成两个子数组排序的时间都是 T(n/2)。
134 |
135 | merge() 函数合并两个有序子数组的时间复杂度是 O(n)。所以,套用前面的公式,归并排序的时间复杂度的计算公式就是:
136 |
137 | ```java
138 | T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。
139 | T(n) = 2*T(n/2) + n; n>1
140 | ```
141 |
142 | 计算过程:
143 |
144 | ```java
145 | T(n) = 2*T(n/2) + n
146 | = 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
147 | = 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
148 | = 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
149 | ......
150 | = 2^k * T(n/2^k) + k * n
151 | ......
152 | ```
153 |
154 | 通过这样一步一步分解推导,可以得到 $T(n) = 2^k*T(n/2^k)+k*n$。
155 |
156 | 当 $T(n/2^k)=T(1)$ 时,也就是 $n/2^k=1$,得到$k=log_2n$ 。
157 |
158 | 将 k 值代入上面的公式,得到 $T(n)=C*n+n*log_2n$ 。
159 |
160 | 如果用大 O 标记法来表示的话,$T(n)$ 就等于 $O(nlogn)$。
161 |
162 | 所以归并排序的时间复杂度是 $O(nlogn)$。
163 |
164 |
165 |
166 | 从原理分析和伪代码可以看出,归并排序的执行效率与要排序的原始数组的有序程度无关,所以其时间复杂度是非常稳定的,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 $O(nlogn)$。
167 |
168 | ### 第三,归并排序的空间复杂度是多少?
169 |
170 | 归并排序的时间复杂度任何情况下都是 $O(nlogn)$,看起来非常优秀。(待会儿你会发现,即便是快速排序,最坏情况下,时间复杂度也是 $O(n^2)$。)
171 |
172 | 但是,归并排序并没有像快排那样,应用广泛,这是为什么呢?
173 |
174 | 因为它有一个致命的“弱点”,那就是**归并排序不是原地排序算法**。
175 |
176 | 这是因为归并排序的合并函数,在合并两个有序数组为一个有序数组时,需要借助额外的存储空间。
177 |
178 | **递归代码的空间复杂度并不能像时间复杂度那样累加。尽管每次合并操作都需要申请额外的内存空间,但在合并完成之后,临时开辟的内存空间就被释放掉了。在任意时刻,CPU 只会有一个函数在执行,也就只会有一个临时的内存空间在使用。临时内存空间最大也不会超过 n 个数据的大小,所以空间复杂度是 O(n)。**
179 |
180 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/08. 快速排序(Quick Sort).md:
--------------------------------------------------------------------------------
1 | # 快速排序(Quick Sort)
2 |
3 | 快速排序(Quick Sort)简称为“快排”。快排利用的也是分治思想。乍看起来,它有点像归并排序,但是思路其实完全不一样。
4 |
5 | ## 原理
6 |
7 | >如果要排序数组中下标从 p 到 r 之间的一组数据,选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。
8 | >
9 | >遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。
10 | >
11 | >经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。
12 | >
13 | >根据分治、递归的处理思想,可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,直到区间缩小为 1,就说明所有的数据都有序了。
14 |
15 |
16 |
17 | - 公式:
18 |
19 | ```java
20 | 递推公式:
21 | quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r)
22 |
23 | 终止条件:
24 | p >= r
25 | ```
26 |
27 | - 伪代码:
28 |
29 | ```java
30 | // 快速排序,A是数组,n表示数组的大小
31 | quick_sort(A, n) {
32 | quick_sort_c(A, 0, n-1)
33 | }
34 |
35 | // 快速排序递归函数,p,r为下标
36 | quick_sort_c(A, p, r) {
37 | if p >= r then return
38 |
39 | q = partition(A, p, r) // 获取分区点
40 | quick_sort_c(A, p, q-1)
41 | quick_sort_c(A, q+1, r)
42 | }
43 | ```
44 |
45 | 这里有一个 partition() 分区函数。
46 |
47 | **partition() 分区函数实际上就是随机选择一个元素作为 pivot(一般情况下,可以选择 p 到 r 区间的最后一个元素)**,然后对 A[p…r]分区,函数返回 pivot 的下标。
48 |
49 | 如果不考虑空间消耗的话,partition() 分区函数可以写得非常简单。21申请两个临时数组 X 和 Y,遍历 A[p…r],将小于 pivot 的元素都拷贝到临时数组 X,将大于 pivot 的元素都拷贝到临时数组 Y,最后再将数组 X 和数组 Y 中数据顺序拷贝到 A[p…r]。
50 |
51 |
52 |
53 | 但是,如果按照这种思路实现的话,partition() 函数就需要很多额外的内存空间,所以快排就不是原地排序算法了。
54 |
55 | 如果希望快排是原地排序算法,那它的空间复杂度得是 O(1),那 partition() 分区函数就不能占用太多额外的内存空间,就需要在 A[p…r]的原地完成分区操作。
56 |
57 | - 原地分区函数的实现思路非常巧妙,见下面伪代码:
58 |
59 | ```java
60 | partition(A, p, r) {
61 | pivot := A[r]
62 | i := p
63 | for j := p to r-1 do {
64 | if A[j] < pivot {
65 | swap A[i] with A[j]
66 | i := i+1
67 | }
68 | }
69 | swap A[i] with A[r]
70 | return i
71 |
72 | ```
73 |
74 | 这里的处理有点类似选择排序。通过游标 i 把 A[p…r-1]分成两部分。A[p…i-1]的元素都是小于 pivot 的,暂且叫它“已处理区间”,A[i…r-1]是“未处理区间”。每次都从未处理的区间 A[i…r-1]中取一个元素 A[j],与 pivot 对比,如果小于 pivot,则将其加入到已处理区间的尾部,也就是 A[i]的位置。
75 |
76 | 在数组某个位置插入元素,需要搬移数据,非常耗时。当时讲了一种处理技巧,**就是交换,在 O(1) 的时间复杂度内完成插入操作。**这里也借助这个思想,只需要将 A[i]与 A[j]交换,就可以在 O(1) 时间复杂度内将 A[j]放到下标为 i 的位置。
77 |
78 |
79 |
80 | ## 性能分析
81 |
82 | ### 第一,归并排序是稳定的排序算法吗?
83 |
84 | 因为分区的过程涉及交换操作,如果数组中有两个相同的元素,比如序列 6,8,7,6,3,5,9,4,在经过第一次分区操作之后,两个 6 的相对先后顺序就会改变。所以,**快速排序并不是一个稳定的排序算法.**
85 |
86 | ### 第二,归并排序的时间复杂度是多少?
87 |
88 | 快排也是用递归来实现的。对于递归代码的时间复杂度,前面总结的公式,这里也还是适用的。
89 |
90 | ##### 最好情况 :$O(nlogn)$
91 |
92 | 如果每次分区操作,都能正好把数组分成大小接近相等的两个小区间,那快排的时间复杂度递推求解公式跟归并是相同的。
93 |
94 | 那么,快排的时间复杂度也是 $O(nlogn)$。
95 |
96 | ```java
97 | T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。
98 | T(n) = 2*T(n/2) + n; n>1
99 | ```
100 |
101 | ##### 最坏情况 : $O(n^2)$
102 |
103 | 但是,公式成立的前提是每次分区操作,选择的 pivot 都很合适,正好能将大区间对等地一分为二。但实际上这种情况是很难实现的。
104 |
105 | 举一个比较极端的例子。如果数组中的数据原来已经是有序的了,比如 1,3,5,6,8。如果每次选择最后一个元素作为 pivot,那每次分区得到的两个区间都是不均等的。需要进行大约 n 次分区操作,才能完成快排的整个过程。每次分区平均要扫描大约 $n/2$ 个元素,这种情况下,快排的时间复杂度就从 $O(nlogn)$ 退化成了 $O(n^2)$。
106 |
107 | 刚刚讲了两个极端情况下的时间复杂度,一个是分区极其均衡,一个是分区极其不均衡。它们分别对应快排的最好情况时间复杂度和最坏情况时间复杂度。
108 |
109 | ##### 平均情况:
110 |
111 | 假设每次分区操作都将区间分成大小为 9:1 的两个小区间。继续套用递归时间复杂度的递推公式,就会变成这样:
112 |
113 | ```java
114 | T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。
115 |
116 | T(n) = T(n/10) + T(9*n/10) + n; n>1
117 | ```
118 |
119 | 这个公式的递推求解的过程非常复杂,虽然可以求解,但不推荐用这种方法。实际上,递归的时间复杂度的求解方法除了递推公式之外,还有递归树,在树那一节再讲,这里暂时不说。
120 |
121 | 这里直接给你结论:T(n) 在大部分情况下的时间复杂度都可以做到 $O(nlogn)$,只有在极端情况下,才会退化到 $O(n^2)$。而且,也有很多方法将这个概率降到很低,如何来做?后面章节再讲。
122 |
123 | ### 第三,归并排序的空间复杂度是多少?
124 |
125 | 按照原地分区的方法进行,归并排序的空间复杂度为 $O(1)$, **是原地排序算法.**
126 |
127 |
128 |
129 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/09. 归并 VS 快排.md:
--------------------------------------------------------------------------------
1 | # 归并排序和快速排序的区别
2 |
3 |
4 |
5 | **归并排序的处理过程是由下到上的,先处理子问题,然后再合并。而快排正好相反,它的处理过程是由上到下的,先分区,然后再处理子问题。**
6 |
7 | 归并排序虽然是稳定的、时间复杂度为 $O(nlogn)$ 的排序算法,但是它是**非原地排序算法**。归并之所以是非原地排序算法,主要原因是**合并函数无法在原地执行**。
8 |
9 | 快速排序通过**设计巧妙的原地分区函数**,可以实现原地排序,解决了归并排序占用太多内存的问题。
10 |
11 |
12 |
13 | # 思考
14 |
15 | > 如何在 O(n) 的时间复杂度内查找一个无序数组中的第 K 大元素?
16 |
17 | 快排核心思想就是分治和分区,可以利用分区的思想,来解答。
18 |
19 | 比如,4, 2, 5, 12, 3 这样一组数据,第 3 大元素就是 4。
20 |
21 | 选择数组区间 A[0…n-1]的最后一个元素 A[n-1]作为 pivot,对数组 A[0…n-1]原地分区,这样数组就分成了三部分,A[0…p-1]、A[p]、A[p+1…n-1]。如果 p+1=K,那 A[p]就是要求解的元素;如果 K>p+1, 说明第 K 大元素出现在 A[p+1…n-1]区间,再按照上面的思路递归地在 A[p+1…n-1]这个区间内查找。同理,如果 K
24 |
25 | ## 分析
26 |
27 | 第一次分区查找,需要对大小为 n 的数组执行分区操作,需要遍历 n 个元素。第二次分区查找,我们只需要对大小为 n/2 的数组执行分区操作,需要遍历 n/2 个元素。依次类推,分区遍历元素的个数分别为、n/2、n/4、n/8、n/16.……直到区间缩小为 1。
28 |
29 | 如果把每次分区遍历的元素个数加起来,就是:n+n/2+n/4+n/8+…+1。这是一个等比数列求和,最后的和等于 2n-1。所以,上述解决思路的时间复杂度就为 O(n)。
30 |
31 | 有个很笨的办法,每次取数组中的最小值,将其移动到数组的最前面,然后在剩下的数组中继续找最小值,以此类推,执行 K 次,找到的数据不就是第 K 大元素了吗?
32 |
33 | 不过,时间复杂度就并不是 O(n) 了,而是 O(K * n)。
34 |
35 | 时间复杂度前面的系数不是可以忽略吗?
36 |
37 | O(K * n) 不就等于 O(n) 吗?这个可不能这么简单地划等号。
38 |
39 | 当 K 是比较小的常量时,比如 1、2,那最好时间复杂度确实是 O(n);但当 K 等于 n/2 或者 n 时,这种最坏情况下的时间复杂度就是 O(n2) 了。
40 |
41 |
42 |
43 |
44 |
45 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
46 |
47 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/10. 线形排序(Linear Sort).md:
--------------------------------------------------------------------------------
1 | # 线形排序(Linear Sort)
2 |
3 | 三种时间复杂度是 $O(n)$ 的排序算法:**桶排序、计数排序、基数排序**。
4 |
5 | 因为这些排序算法的时间复杂度是线性的,所以把这类排序算法叫作**线性排序(Linear sort)**。
6 |
7 | 之所以能做到线性的时间复杂度,主要原因是,这三个算法是**非基于比较的排序算法,都不涉及元素之间的比较操作**。
8 |
9 |
10 |
11 |
12 |
13 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/11. 桶排序(Bucket Sort).md:
--------------------------------------------------------------------------------
1 | # 桶排序(Bucket Sort)
2 |
3 | > 桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
4 |
5 | - 例子:对这组金额在 0 - 50 之间的订单进行排序
6 |
7 |
8 |
9 | ## 时间复杂度分析
10 |
11 | 如果要排序的数据有 n 个,把它们均匀地划分到 m 个桶内,每个桶里就有 $k=n/m$ 个元素。
12 |
13 | 每个桶**内部使用快速排序**,时间复杂度为 $O(k * logk)$。
14 |
15 | m 个桶排序的时间复杂度就是 $O(m * k * logk)$,因为 $k=n/m$,所以整个桶排序的时间复杂度就是 $O(n*log(n/m))$。
16 |
17 | 当桶的个数 $m$ 接近数据个数 $n$ 时,$log(n/m)$ 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 $O(n)$。
18 |
19 | ## 桶排序是不是可以替代之前讲的排序算法呢?
20 |
21 | 答案是否定的。**桶排序对要排序数据的要求是非常苛刻的。**
22 |
23 | 首先,**要排序的数据需要很容易就能划分成 m 个桶**,并且,**桶与桶之间有着天然的大小顺序**。这样每个桶内的数据都排序完之后,桶与桶之间的数据不需要再进行排序。
24 |
25 | 其次,**数据在各个桶之间的分布是比较均匀的**。如果数据经过桶的划分之后,有些桶里的数据非常多,有些非常少,很不平均,那桶内数据排序的时间复杂度就不是常量级了。在极端情况下,如果数据都被划分到一个桶里,那就退化为 $O(nlogn)$ 的排序算法了。
26 |
27 | ## 桶排序比较适合用在**外部排序**中。
28 |
29 | 所谓的**外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。**
30 |
31 | 比如说有 10GB 的订单数据,希望按订单金额(假设金额都是正整数)进行排序,但是内存有限,只有几百 MB,没办法一次性把 10GB 的数据都加载到内存中。这个时候该怎么办呢?
32 |
33 | 可以先扫描一遍文件,看订单金额所处的数据范围。假设经过扫描之后得到,订单金额最小是 1 元,最大是 10 万元。将所有订单根据金额划分到 100 个桶里,第一个桶我们存储金额在 1 元到 1000 元之内的订单,第二桶存储金额在 1001 元到 2000 元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00,01,02…99)。
34 |
35 | 理想的情况下,如果订单金额在 1 到 10 万之间均匀分布,那订单会被均匀划分到 100 个文件中,每个小文件中存储大约 100MB 的订单数据,就可以将这 100 个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。
36 |
37 | 不过,订单按照金额在 1 元到 10 万元之间并不一定是均匀分布的 ,所以 10GB 订单数据是无法均匀地被划分到 100 个文件中的。有可能某个金额区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存。这又该怎么办呢?
38 |
39 | 针对这些划分之后还是比较大的文件,可以继续划分,比如,订单金额在 1 元到 1000 元之间的比较多,就将这个区间继续划分为 10 个小区间,1 元到 100 元,101 元到 200 元,201 元到 300 元…901 元到 1000 元。如果划分之后,101 元到 200 元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。
40 |
41 |
42 |
43 |
44 |
45 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/12. 计数排序(Counting Sort).md:
--------------------------------------------------------------------------------
1 | # 计数排序(Counting Sort)
2 |
3 | > 计数排序其实是 **桶排序** 的一种特殊情况。当要排序的 n 个数据,所处的范围并不大的时候,比如最大值是 k,就可以把数据划分成 k 个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。
4 |
5 | 高考查分数系统会显示成绩以及所在省的排名。如果所在的省有 50 万考生,如何通过成绩快速排序得出名次呢?
6 |
7 | 考生的满分是 900 分,最小是 0 分,这个数据的范围很小,所以可以分成 901 个桶,对应分数从 0 分到 900 分。根据考生的成绩,将这 50 万考生划分到这 901 个桶里。桶内的数据都是分数相同的考生,所以并不需要再进行排序。只需要依次扫描每个桶,将桶内的考生依次输出到一个数组中,就实现了 50 万考生的排序。因为只涉及扫描遍历操作,所以时间复杂度是 $O(n)$。
8 |
9 | ## “计数”的含义的由来
10 |
11 | 计数排序的算法思想跟桶排序非常类似,只是桶的大小粒度不一样。不过,为什么这个排序算法叫“计数”排序呢?
12 |
13 | 想弄明白这个问题,就要来看计数排序算法的实现方法。
14 |
15 | 还拿考生那个例子来解释。为了方便说明,对数据规模做了简化。假设只有 8 个考生,分数在 0 到 5 分之间。这 8 个考生的成绩放在一个数组 A[8]中,它们分别是:2,5,3,0,2,3,0,3。
16 |
17 | 考生的成绩从 0 到 5 分,使用大小为 6 的数组 C[6]表示桶,其中下标对应分数。不过,C[6]内存储的并不是考生,而是对应的考生个数。像刚刚举的那个例子,只需要遍历一遍考生分数,就可以得到 C[6]的值。
18 |
19 |
20 |
21 | 图中可以看出,分数为 3 分的考生有 3 个,小于 3 分的考生有 4 个,所以,成绩为 3 分的考生在排序之后的有序数组 R[8]中,会保存下标 4,5,6 的位置。
22 |
23 |
24 |
25 | 如何快速计算出,每个分数的考生在有序数组中对应的存储位置呢?这个处理方法非常巧妙,很不容易想到。
26 |
27 | **思路是这样的:对 C[6]数组顺序求和,C[6]存储的数据就变成了下面这样子。C[k]里存储小于等于分数 k 的考生个数。**
28 |
29 |
30 |
31 | **从后到前依次扫描数组 A。比如,当扫描到 3 时,可以从数组 C 中取出下标为 3 的值 7,也就是说,到目前为止,包括自己在内,分数小于等于 3 的考生有 7 个,也就是说 3 是数组 R 中的第 7 个元素(也就是数组 R 中下标为 6 的位置)。当 3 放入到数组 R 中后,小于等于 3 的元素就只剩下了 6 个了,所以相应的 C[3]要减 1,变成 6。**
32 |
33 | **以此类推,当扫描到第 2 个分数为 3 的考生的时候,就会把它放入数组 R 中的第 6 个元素的位置(也就是下标为 5 的位置)。当扫描完整个数组 A 后,数组 R 内的数据就是按照分数从小到大有序排列的了。**
34 |
35 |
36 |
37 | - code
38 |
39 | ```java
40 | // java code
41 |
42 | public class CountingSort {
43 | /**
44 | * 计数排序
45 | *
46 | * @param arr 要排序的数组大小
47 | * @param n 数组元素个数
48 | */
49 | public static void sort(int[] arr, int n) {
50 | if (n <= 1) {
51 | return;
52 | }
53 |
54 | //默认数组最大的元素为数组第一个元素
55 | int max = arr[0];
56 | //遍历数组的所有的元素,找到最大的元素
57 | for (int i = 1; i < n; i++) {
58 | //若后面的元素大于指定的数组元素,则把元素进行交换
59 | if (arr[i] > max) {
60 | max = arr[i];
61 | }
62 | }
63 |
64 | //申请一个计数数组,下标从0~max。
65 | int[] c = new int[max + 1];
66 |
67 | //遍历数组,将每个元素的个数放入到计数数组中,比如分数为0的元素,在c[0]就累加1,依次类推
68 | for (int i = 0; i < n; i++) {
69 | c[arr[i]]++;
70 | }
71 |
72 | //开始重新整理c[]数组,将c[]数组顺序求和,比如分数0的个数1,分数为1的个数为3。那么重新整理后,分数<=0的为1,分数<=1人数诶1+3=4个,因为包含了<=0的个数,依次类推
73 | //所以终止条件为i<=max
74 | for (int i = 1; i <= max; i++) {
75 | c[i] = c[i] + c[i - 1];
76 | }
77 |
78 | //这时候开始进行排序,创建一个跟要排序的数组一样大小的数据空间
79 | int[] temp = new int[n];
80 |
81 | //开始循环需要排序的数据
82 | for (int i = 0; i < n; i++) {
83 | //计算出需要往temp临时数组哪个索引位置存放arr[i]的值。
84 | //根据原始数组的值找到计数数组的对应值的计数个数,得到c[arr[i]]的值,也就是temp数组从0开始,所以需要减一
85 | int index = c[arr[i]] - 1;
86 | temp[index] = arr[i];
87 | //每次循环,计数数组的元素值减一,因为数组放到了temp数组中
88 | c[arr[i]]--;
89 | }
90 |
91 | //重新赋值
92 | for (int i = 0; i < n; i++) {
93 | arr[i] = temp[i];
94 | }
95 | }
96 | }
97 | ```
98 |
99 |
100 |
101 | 这种利用另外一个数组来计数的实现方式是不是很巧妙呢?这也是为什么这种排序算法叫计数排序的原因。不过,千万不要死记硬背上面的排序过程,重要的是理解和会用。
102 |
103 | #### 尾部取数原因
104 |
105 | 数组保存的是次数累加结果(是从左开始累加的),也就是原数据的位置累加,现在要恢复到结果当然需要从尾部退格,这样才能位置对应,否则相等的数据时位置恰恰都是反着,比如原数组有两个2,累加结果是位置是6和7,若是从左边取数,这两个2最后放的位置就反了。
106 |
107 | ## 总结
108 |
109 | 1. **计数排序只能用在数据范围不大的场景中,如果数据范围 k 比要排序的数据 n 大很多,就不适合用计数排序了**
110 |
111 | 2. **计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。**
112 |
113 | 比如,还是拿考生这个例子。如果考生成绩精确到小数后一位,就需要将所有的分数都先乘以 10,转化成整数,然后再放到 9010 个桶内。
114 |
115 | 再比如,如果要排序的数据中有负数,数据的范围是[-1000, 1000],那我们就需要先对每个数据都加 1000,转化成非负整数。
116 |
117 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/13. 基数排序(Radix Sort).md:
--------------------------------------------------------------------------------
1 | # 基数排序(Radix Sort)
2 |
3 | 再来看这样一个排序问题。假设有 10 万个手机号码,希望将这 10 万个手机号码从小到大排序,有什么比较快速的排序方法呢?
4 |
5 | 之前讲的快排,时间复杂度可以做到 $O(nlogn)$,还有更高效的排序算法吗?桶排序、计数排序能派上用场吗?手机号码有 11 位,范围太大,显然不适合用这两种排序算法。针对这个排序问题,有没有时间复杂度是 $O(n)$ 的算法呢?现在就来介绍一种新的排序算法,基数排序。
6 |
7 | 刚刚这个问题里有这样的规律:**假设要比较两个手机号码 a,b 的大小,如果在前面几位中,a 手机号码已经比 b 手机号码大了,那后面的几位就不用看了。**
8 |
9 | 手机号码稍微有点长,画图比较不容易看清楚,用字符串排序的例子,画了一张基数排序的过程分解图:
10 |
11 |
12 |
13 | 注意,**这里按照每位来排序的排序算法要是稳定的**,否则这个实现思路就是不正确的。因为如果是非稳定排序算法,那最后一次排序只会考虑最高位的大小顺序,完全不管其他位的大小关系,那么低位的排序就完全没有意义了。
14 |
15 | 根据每一位来排序,可以用刚讲过的桶排序或者计数排序,它们的时间复杂度可以做到 $O(n$)。如果要排序的数据有 k 位,那就需要 k 次桶排序或者计数排序,总的时间复杂度是$ O(k*n$)。当 k 不大的时候,比如手机号码排序的例子,k 最大就是 11,所以基数排序的时间复杂度就近似于$ O(n)$。
16 |
17 | 实际上,有时候要排序的数据并不都是等长的,比如排序牛津字典中的 20 万个英文单词,最短的只有 1 个字母,最长的特意去查了下,有 45 个字母,中文翻译是尘肺病。对于这种不等长的数据,基数排序还适用吗?
18 |
19 | 实际上,可以**把所有的单词补齐到相同长度,位数不够的可以在后面补“0”**,因为根据[ASCII](https://zh.wiktionary.org/wiki/US-ASCII) 值,所有字母都大于“0”,所以补“0”不会影响到原有的大小顺序。这样就可以继续用基数排序了.
20 |
21 | ## 总结
22 |
23 | 基数排序对要排序的数据是有要求的
24 |
25 | 1. **需要可以分割出独立的“位”来比较,而且位之间有递进的关系**, 如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了。
26 | 2. **每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到 $O(n)$ 了**。
27 |
28 |
29 |
30 | # 思考
31 |
32 | > 1. 如何根据年龄给 100 万用户排序?
33 |
34 | 实际上,根据年龄给 100 万用户排序,就类似按照成绩给 50 万考生排序。假设年龄的范围最小 1 岁,最大不超过 120 岁。可以遍历这 100 万用户,根据年龄将其划分到这 120 个桶里,然后依次顺序遍历这 120 个桶中的元素。这样就得到了按照年龄排序的 100 万用户数据。
35 |
36 |
37 |
38 | > 2. 假设现在需要对 D,a,F,B,c,A,z 这个字符串进行排序,要求将其中所有小写字母都排在大写字母的前面,但小写字母内部和大写字母内部不要求有序。比如经过排序之后为 a,c,z,D,F,B,A,这个如何来实现呢?
39 | >
40 | > 如果字符串中存储的不仅有大小写字母,还有数字。要将小写字母的放到前面,大写字母放在最后,数字放在中间,不用排序算法,又该怎么解决呢?
41 |
42 | - 方法 1 :
43 |
44 | 用两个指针a、b:a指针从头开始往后遍历,遇到大写字母就停下,b从后往前遍历,遇到小写字母就停下,交换a、b指针对应的元素;重复如上过程,直到a、b指针相交。
45 |
46 | 对于小写字母放前面,数字放中间,大写字母放后面,可以先将数据分为小写字母和非小写字母两大类,进行如上交换后再在非小写字母区间内分为数字和大写字母做同样处理
47 |
48 | 其实第二个问题还可以优化一下,借助插入排序的思想,用两个指针分别从数组的最前面和最后面向中间遍历,这两个指针分别标记小写字母区间的最右侧和大写字母的最左侧;当小写字母和小写字母分居两侧后,中间部分自然就是数字了。时间复杂度为O(n)。
49 |
50 | - 方法2:不用排序算法,就直接遍历一遍,比较ASCII码
51 |
52 | - 方法3:利用桶排序思想,弄小写,大写,数字三个桶,遍历一遍,都放进去,然后再从桶中取出来就行了。相当于遍历了两遍,复杂度O(n)
53 |
54 |
55 |
56 | *`@ 笔记时间 :2020-8-23 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/14. 排序优化.md:
--------------------------------------------------------------------------------
1 | # 排序优化
2 |
3 | [toc]
4 |
5 | ## 如何选择合适的排序算法
6 |
7 | 如果要实现一个通用的、高效率的排序函数,应该选择哪种排序算法?先回顾一下前面讲过的几种排序算法。
8 |
9 |
10 |
11 | 前面讲过,线性排序算法的时间复杂度比较低,适用场景比较特殊。所以如果要写一个通用的排序函数,不能选择线性排序算法。
12 |
13 | - 如果对`小规模数据`进行排序,可以选择时间复杂度是 **$O(n^2)$** 的算法;
14 | - 如果对`大规模数据`进行排序,时间复杂度是 $O(nlogn)$ 的算法更加高效。
15 | - 所以,为了`兼顾任意规模数据`的排序,一般都会首选时间复杂度是 $O(nlogn)$的排序算法来实现排序函数。
16 |
17 | 时间复杂度是 $O(nlogn)$ 的排序算法不止一个,已经讲过的有`归并排序`、`快速排序`,后面讲堆的时候还会讲到`堆排序`。堆排序和快速排序都有比较多的应用,比如 Java 语言采用堆排序实现排序函数,C 语言使用快速排序实现排序函数。
18 |
19 | 使用归并排序的情况其实并不多。
20 |
21 | 快在最坏情况下的时间复杂度是 $O(n^2)$,而归并排序可以做到平均情况、最坏情况下的时间复杂度都是 $O(nlogn)$,从这点上看起来很诱人,那为什么它还是没能得到“宠信”呢?
22 |
23 | 还记得上一节讲的归并排序的空间复杂度吗?归并排序并不是原地排序算法,空间复杂度是 $O(n)$。所以,粗略点、夸张点讲,如果要排序 100MB 的数据,除了数据本身占用的内存之外,排序算法还要额外再占用 100MB 的内存空间,空间耗费就翻倍了.
24 |
25 | 前面讲到,快速排序比较适合来实现排序函数,但是,快速排序在最坏情况下的时间复杂度是 $O(n^2)$,下面将说明如何来解决这个“复杂度恶化”的问题。
26 |
27 | ## 优化快速排序?
28 |
29 | ### 为什么快排最坏情况下时间复杂度是 $O(n^2)$?
30 |
31 | 前面讲过,如果数据原来就是有序的或者接近有序的,每次分区点都选择最后一个数据,那快速排序算法就会变得非常糟糕,时间复杂度就会退化为 $O(n^2)$。
32 |
33 | 实际上,** $O(n^2)$ 时间复杂度出现的主要原因还是`因为分区点选得不够合理**。
34 |
35 | ### 最理想的区分点
36 |
37 | `被分区点分开的两个分区中,数据的数量差不多`
38 |
39 | 如果很粗暴地直接选择第一个或者最后一个数据作为分区点,不考虑数据的特点,肯定会出现之前讲的那样,在某些情况下,排序的最坏情况时间复杂度是 $O(n^2)$。为了提高排序算法的性能,也要尽可能地让每次分区都比较平均.
40 |
41 |
42 |
43 | ### 两个常用的、简单的分区算法
44 |
45 | ##### 1. 三数取中法
46 |
47 | `从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点。`
48 |
49 | 这样每间隔某个固定的长度,取数据出来比较,将中间值作为分区点的分区算法,肯定要比单纯取某一个数据更好。但是,如果要排序的数组比较大,那“三数取中”可能就不够了,可能要“五数取中”或者“十数取中”。
50 |
51 | ##### 2. 随机法
52 |
53 | `随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。`
54 |
55 | **这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现每次分区点都选得很差的情况,所以平均情况下,这样选的分区点是比较好的。**
56 |
57 | 时间复杂度退化为最糟糕的 $O(n^2)$ 的情况,出现的可能性不大。
58 |
59 |
60 |
61 | 快速排序是用递归来实现的。在递归那一节讲过,递归要警惕堆栈溢出。为了避免快速排序里,递归过深而堆栈过小,导致堆栈溢出,有两种解决办法:
62 |
63 | **第一种是限制递归深度**, **一旦递归过深,超过了事先设定的阈值,就停止递归。**
64 |
65 | **第二种是通过在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈的过程,这样就没有了系统栈大小的限制**
66 |
67 |
68 |
69 | ## 举例分析排序函数
70 |
71 | 为了对如何实现一个排序函数有一个更直观的感受,拿 Glibc 中的 qsort() 函数举例说明一下。虽说 qsort() 从名字上看,很像是基于快速排序算法实现的,实际上它并不仅仅用了快排这一种算法。
72 |
73 | 如果去看源码,就会发现,qsort() 会**优先使用归并排序来排序输入数据**,**因为归并排序的空间复杂度是 $O(n)$,所以对于小数据量的排序**,比如 1KB、2KB 等,**归并排序额外需要** 1KB、2KB 的**内存空间**,**这个问题不大**
74 |
75 | 现在计算机的内存都挺大的,很多时候追求的是速度。还记得前面讲过的**用空间换时间**的技巧吗?这就是一个典型的应用。
76 |
77 | 但如果**数据量太大**,就跟前面提到的,排序 100MB 的数据,这个时候**再用归并排序就不合适了**。所以,要排序的数据量比较大的时候,qsort() **会改为用快速排序算法来排序**。
78 |
79 | 那 qsort() 是如何选择快速排序算法的分区点的呢?如果去看源码,就会发现,qsort() 选择分区点的方法就是“三数取中法”。是不是也并不复杂?
80 |
81 | 还有前面提到的递归太深会导致堆栈溢出的问题,qsort() 是通过自己实现一个堆上的栈,手动模拟递归来解决的。
82 |
83 | 实际上,qsort() 并不仅仅用到了**归并排序**和**快速排序**,它还用到了**插入排序**。
84 |
85 | 在快速排序的过程中,当要排序的区间中,元素的个数小于等于 4 时,qsort() 就退化为插入排序,不再继续用递归来做快速排序,因为前面也讲过,`在小规模数据面前,`$O(n^2)$`时间复杂度的算法并不一定比` $O(nlogn)$`的算法执行时间长`。现在就来分析下这个说法。
86 |
87 | ### 分析 :`在小规模数据面前,`$O(n^2)$`时间复杂度的算法并不一定比` $O(nlogn)$`的算法执行时间长`
88 |
89 | 在讲复杂度分析的时候讲过,算法的性能可以通过时间复杂度来分析,但是,这种复杂度分析是比较偏理论的,如果深究的话,实际上时间复杂度并不等于代码实际的运行时间。
90 |
91 | **时间复杂度代表的是一个增长趋势**,如果画成增长曲线图,会发现 $O(n^2)$ 比 $O(nlogn)$ 要陡峭,也就是说增长趋势要更猛一些。
92 |
93 | 但是,前面讲过,在大 O 复杂度表示法中,会省略低阶、系数和常数,也就是说,$O(nlogn)$ 在没有省略低阶、系数、常数之前可能是 $O(k*n*logn + c)$,而且 k 和 c 有可能还是一个比较大的数。
94 |
95 | 假设 k=1000,c=200,当对小规模数据(比如 n=100)排序时,n2的值实际上比 knlogn+c 还要小。
96 |
97 | ```java
98 | k * n * logn + c = 1000 * 100 * log100 + 200 远大于10000
99 |
100 | n^2 = 100*100 = 10000
101 | ```
102 |
103 | 所以,对于小规模数据的排序,$O(n^2)$ 的排序算法并不一定比 $O(nlogn)$ 排序算法执行的时间长。
104 |
105 | `对于小数据量的排序,选择比较简单、不需要递归的插入排序算法`。
106 |
107 | 还记得之前讲到的`哨兵来简化代码`,提高执行效率吗?
108 |
109 | 在 qsort() 插入排序的算法实现中,也利用了这种编程技巧。
110 |
111 | 虽然哨兵可能只是少做一次判断,但是毕竟排序函数是非常常用、非常基础的函数,性能的优化要做到极致。
112 |
113 |
114 |
115 | *`@ 笔记时间 :2020-8-29 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/17. 堆排序(Heap Sort).md:
--------------------------------------------------------------------------------
1 | # 堆排序(Heap Sort)
2 |
3 | [toc]
4 |
5 | ## 介绍
6 |
7 | 借助于堆这种数据结构实现的排序算法,就叫做堆排序。
8 |
9 | 时间复杂度非常稳定,是 $O(nlogn)$,并且它还是原地排序算法.
10 |
11 |
12 |
13 | ## 实现堆排序
14 |
15 | 两大步骤:`建堆` 和 `排序`.
16 |
17 | ### 1. 建堆
18 |
19 | 首先,**将数组原地建成一个堆**。
20 |
21 | 所谓,**原地**就是,不借助另一个数组,就在原数组上操作。
22 |
23 | 建堆的过程,有两种思路:
24 |
25 |
26 |
27 | 第一种是借助前面讲的,在堆中插入一个元素的思路。尽管数组中包含 n 个数据,但是可以假设,起初堆中只包含一个数据,就是下标为 1 的数据。然后,调用前面讲的插入操作,将下标从 2 到 n 的数据依次插入到堆中。这样就将包含 n 个数据的数组,组织成了堆。
28 |
29 |
30 |
31 | 第二种实现思路,跟第一种截然相反。第一种建堆思路的处理过程是从前往后处理数组数据,并且每个数据插入堆中时,都是从下往上堆化。而第二种实现思路,是从后往前处理数组,并且每个数据都是从上往下堆化。
32 |
33 |
34 |
35 | 举例:
36 |
37 | 因为叶子节点往下堆化只能自己跟自己比较,所以直接从最后一个非叶子节点开始,依次堆化就行。
38 |
39 |
40 |
41 |
42 |
43 | 代码如下:
44 |
45 | ```java
46 |
47 | private static void buildHeap(int[] a, int n) {
48 | for (int i = n/2; i >= 1; --i) {
49 | heapify(a, n, i);
50 | }
51 | }
52 |
53 | private static void heapify(int[] a, int n, int i) {
54 | while (true) {
55 | int maxPos = i;
56 | if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;
57 | if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;
58 | if (maxPos == i) break;
59 | swap(a, i, maxPos);
60 | i = maxPos;
61 | }
62 | }
63 | ```
64 |
65 |
66 |
67 | 在这段代码中,对下标从 $\frac{n}{2}$ 开始到 1 的数据进行堆化,下标是 $\frac{n}{2}+1$ 到 n 的节点是叶子节点,不需要堆化。实际上,对于完全二叉树来说,下标从 $\frac{n}{2} + 1$ 到 n 的节点都是叶子节点。
68 |
69 | #### 建堆操作的时间复杂度分析
70 |
71 | 每个节点堆化的时间复杂度是 $O(log n)$,那 $\frac{n}{2} + 1$ 个节点堆化的总时间复杂度是不是就是 $O(nlogn)$?
72 |
73 | 这个答案虽然没错,但是这个值不够精确,实际上,堆排序的建堆过程的时间复杂度是 $O(n)$.
74 |
75 | ##### 推导:
76 |
77 | 因为叶子节点不需要堆化,所以需要堆化的节点从倒数第二层开始。每个节点堆化的过程中,需要比较和交换的节点个数,跟这个节点的高度 k 成正比.
78 |
79 | 把每一层的节点个数和对应的高度画出来,可以看到,只需要将每个节点的高度求和,得出的就是建堆的时间复杂度.
80 |
81 |
82 |
83 |
84 |
85 | 将每个非叶子节点的高度求和:
86 |
87 |
88 |
89 | 这个公式的求解稍微有点技巧,不过高中都学过:*把公式左右都乘以 2,就得到另一个公式 $S_2$,将$S_2$ 错位拉齐,并且用 $S_2$ 减去 $S_1$,可以得到 S.*
90 |
91 |
92 |
93 | 因为 $h=log_2n$,带入公式 S, 就能得到 S = O(n),所以,建堆的时间复杂度就是 O(n).
94 |
95 |
96 |
97 | ### 2. 排序
98 |
99 | 建堆结束之后,数组中的数据已经是按照大顶堆的特性来组织的.数组中的第一个元素就是堆顶,也就是最大的元素.把它跟最后一个元素交换,那最大元素就放到了下标为 n 的位置.
100 |
101 | 这个过程有点类似上面讲的 "删除堆顶元素"的操作,当堆顶元素移除之后,把下标为 n 的元素放到堆顶,然后再通过堆化的方法,将剩下的 $n-1$ 个元素重新构建成堆。堆化完成之后,再取堆顶的元素,放到下标是 $n-1$ 的位置,一直重复这个过程,直到最后堆中只剩下标为 1 的一个元素,排序工作就完成了.
102 |
103 |
104 |
105 | 代码:
106 |
107 | ```java
108 | // n表示数据的个数,数组a中的数据从下标1到n的位置。
109 | public static void sort(int[] a, int n) {
110 | buildHeap(a, n);
111 | int k = n;
112 | while (k > 1) {
113 | swap(a, 1, k);
114 | --k;
115 | heapify(a, k, 1);
116 | }
117 | }
118 | ```
119 |
120 | #### 时间复杂度和稳定性分析
121 |
122 | 整个堆排序的过程,都只需要极个别临时存储空间,所以堆排序是原地排序算法。堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是 O(n),排序过程的时间复杂度是 O(nlogn),所以,堆排序整体的时间复杂度是 O(nlogn)。
123 |
124 | 堆排序不是稳定的排序算法,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。
125 |
126 |
127 |
128 | ### 说明:为什么下标从1开始
129 |
130 | 之前假设,堆中的数据是从数组下标为 1 的位置开始存储。
131 |
132 | 那如果从 0 开始存储,实际上处理思路是没有任何变化的,唯一变化的,可能就是,代码实现的时候,计算子节点和父节点的下标的公式改变了。
133 |
134 | **如果节点的下标是 i,那左子节点的下标就是 2∗i+1,右子节点的下标就是 2∗i+2,父节点的下标就是 2i−1。**
135 |
136 |
137 |
138 | ## 思考
139 |
140 | 为什么快速排序要比堆排序性能好?
141 |
142 |
143 |
144 | ***第一点,堆排序数据访问的方式没有快速排序友好。***
145 |
146 |
147 |
148 | 对于快速排序来说,数据是顺序访问的。而对于堆排序来说,数据是跳着访问的。
149 |
150 |
151 |
152 | 比如,堆排序中,最重要的一个操作就是数据的堆化。比如下面这个例子,对堆顶节点进行堆化,会依次访问数组下标是 1,2,4,8 的元素,而不是像快速排序那样,局部顺序访问,所以,这样对 CPU 缓存是不友好的。
153 |
154 |
155 |
156 |
157 |
158 | ***第二点,对于同样的数据,在排序过程中,堆排序算法的数据交换次数要多于快速排序。***
159 |
160 |
161 |
162 | 在讲排序的时候,提过两个概念,有序度和逆序度。对于基于比较的排序算法来说,整个排序过程就是由两个基本的操作组成的,比较和交换(或移动)。快速排序数据交换的次数不会比逆序度多。
163 |
164 |
165 |
166 | 但是堆排序的第一步是建堆,建堆的过程会打乱数据原有的相对先后顺序,导致原数据的有序度降低。比如,对于一组已经有序的数据来说,经过建堆之后,数据反而变得更无序了。
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | 对于第二点,可以自己做个试验看下。用一个记录交换次数的变量,在代码中,每次交换的时候,就对这个变量加一,排
175 |
176 | 序完成之后,这个变量的值就是总的数据交换次数。这样就能很直观地理解,**堆排序比快速排序交换次数多。**
177 |
178 |
179 |
180 | *`@ 笔记时间 :2021-02-08 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
181 |
182 |
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/18. 深度和广度优先搜索.md:
--------------------------------------------------------------------------------
1 | # 深度和广度优先搜索
2 |
3 |
4 |
5 | ## 什么是 **搜索** 算法?
6 |
7 | - 算法是作用于具体数据结构之上的,深度优先搜索算法和广度优先搜索算法都是基于 "图" 这种数据结构的。这是因为,图这种数据结构的表达能力很强,大部分涉及搜索的场景都可以抽象成 "图"。
8 |
9 | - 图上的上搜索算法,最直接的理解就是,在图中找出从一个顶点出发,到另一个顶点的路径。具体方法有很多,比如两种最简单的、最 "暴力" 的深度优先、广度优先搜索,还有 A\*、IDA\* 等启发式搜索算法。
10 |
11 | - 图,有两种主要存储方法,邻接表和邻接矩阵,本节将会用邻接表来存储图。
12 |
13 | - **深度优先搜索算法和广度优先搜索算法**既可以用在无向图,也可以用在有向图上,本节都针对无向图来讲解。
14 |
15 | ```java
16 | public class Graph { // 无向图
17 | private int v; // 顶点的个数
18 | private LinkedList adj[]; // 邻接表
19 |
20 | public Graph(int v) {
21 | this.v = v;
22 | adj = new LinkedList[v];
23 | for (int i=0; i();
25 | }
26 | }
27 |
28 | public void addEdge(int s, int t) { // 无向图一条边存两次
29 | adj[s].add(t);
30 | adj[t].add(s);
31 | }
32 | }
33 | ```
34 |
35 |
36 |
37 | ## 广度优先搜索(BFS)
38 |
39 | 广度优先搜索(Breadth-First-Search),简称 BFS。 直观地讲,它其实就是一种 **地毯式** 层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索,如下图:
40 |
41 |
42 |
43 | ### BFS 实现
44 |
45 | 尽管广度优先搜索的原理挺简单,但代码实现还是稍微有点复杂的,所以,重点在于它的代码实现:
46 |
47 | ```java
48 | public void bfs(int s, int t) {
49 | if (s == t) return;
50 | boolean[] visited = new boolean[v];
51 | visited[s]=true;
52 | Queue queue = new LinkedList<>();
53 | queue.add(s);
54 | int[] prev = new int[v];
55 | for (int i = 0; i < v; ++i) {
56 | prev[i] = -1;
57 | }
58 | while (queue.size() != 0) {
59 | int w = queue.poll();
60 | for (int i = 0; i < adj[w].size(); ++i) {
61 | int q = adj[w].get(i);
62 | if (!visited[q]) {
63 | prev[q] = w;
64 | if (q == t) {
65 | print(prev, s, t);
66 | return;
67 | }
68 | visited[q] = true;
69 | queue.add(q);
70 | }
71 | }
72 | }
73 | }
74 |
75 | private void print(int[] prev, int s, int t) { // 递归打印s->t的路径
76 | if (prev[t] != -1 && t != s) {
77 | print(prev, s, prev[t]);
78 | }
79 | System.out.print(t + " ");
80 | }
81 | ```
82 |
83 | 代码说明:
84 |
85 | - bfs() 函数就是基于之前定义的,图的广度优先搜索的代码实现;
86 | - s 表示起始顶点,t 表示终止顶点;
87 | - 搜索一条从 s 到 t 的路径,实际上,就是求从 s 到 t 的最短路径。
88 | - 三个重要的辅助变量:
89 | - **visited**:用来记录已经被访问的顶点,用来避免顶点被重复访问;如果某个顶点q被访问,那相应的 visited[q] 会被设置为 true.
90 | - **queue**:是一个队列,用来存储已经被访问、但相连的顶点还没有被访问的顶点。因为广度优先搜索是逐层访问的,即,只有把第 k 层的顶点都访问完成之后,才能访问第 k + 1 层的顶点。当访问到第 k 层的顶点时候,需要把第 k 层的顶点记录下来,稍后才能通过第 k 层的顶点来找第 k + 1 层的顶点。所以,用这个队列来实现记录的功能。
91 | - **prev**:用来记录搜索路径。当从顶点 s 开始,广度优先搜索到顶点 t 后,prev 数组中存储的就是搜索的路径。不过,这个路径是反向存储的。 prev[w] 存储的是,顶点 w 是从哪个前驱顶点遍历过来的。比如,通过顶点 2 的邻接表访问到顶点 3,那 prev[3] 就等于 2。为了正向打印出路径,需要递归地来打印.
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | ### BFS 复杂度分析
102 |
103 | - 时间复杂度:
104 |
105 | 最坏情况下,终止顶点 t 离起始顶点 s 很远,需要遍历完整个图才能找到。这个时候,每个顶点都要进出一遍队列,每个边也都会被访问依次,所以,广度优先搜索的时间复杂度是 $O(V+E)$,其中,V - 表示顶点的个数,E - 表示边的个数。当然,对于一个连通图来说,也就是说一个图中的所有顶点都是连通的,E 肯定要大于等于 $V-1$,所以,广度优先搜索的时间复杂度可以简写为 $O(E)$.
106 |
107 | - 空间复杂度分析:
108 |
109 | 广度优先搜索的空间消耗主要在几个辅助变量 visited 数组、queue 队列、prev数组上。这三个存储空间的大小都不会超过顶点的个数,所以空间复杂度是 $O(V)$.
110 |
111 | ## 深度优先搜索(DFS)
112 |
113 | 深度优先搜索(Depth-First-Search),简称 DFS。
114 |
115 | 最直观的例子就是 "走迷宫",假设你站在迷宫的某个岔路口,然后想找出口。随意选择一个岔路口来走,走着走着发现走不通的时候,就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口。这种走法就是一种深度优先搜索策略。
116 |
117 | 如何在图中应用深度优先搜索,来找某个顶点到另一个顶点的路径呢?
118 |
119 | 如下图,搜索的起始顶点是 s,终止顶点是 t,希望在图中寻找一条从顶点 s 到 顶点 t 的路径,如果映射到迷宫那个例子,s 就是起始所在的位置,t 就是出口。
120 |
121 | 用深度递归算法,把整个搜索的路径标记出来。这里面实线箭头表示遍历,虚线箭头表示会退。从图中,可以看出,深度优先搜索找出来的路径,并不是顶点 s 到顶点 t 的最短路径。
122 |
123 |
124 |
125 | 实际上,深度优先搜索用的是一种比较著名的算法思想,即,**回溯思想**(后面会讲)。这种思想解决问题的过程,非常适合用递归来实现。
126 |
127 | ### DFS 实现
128 |
129 | 代码如下,可以发现,深度优先搜索代码的实现,也用到了 prev、visited 变量 以及 print() 函数,它们跟广度优先搜索代码实现里的作用是一样的。
130 |
131 | 不过,深度优先搜索代码实现里,有个比较特殊的变量 found, 作用是:当已经找到终止顶点 t 之后,就不再递归地继续查找了。
132 |
133 | ```java
134 | boolean found = false; // 全局变量或者类成员变量
135 |
136 | public void dfs(int s, int t) {
137 | found = false;
138 | boolean[] visited = new boolean[v];
139 | int[] prev = new int[v];
140 | for (int i = 0; i < v; ++i) {
141 | prev[i] = -1;
142 | }
143 | recurDfs(s, t, visited, prev);
144 | print(prev, s, t);
145 | }
146 |
147 | private void recurDfs(int w, int t, boolean[] visited, int[] prev) {
148 | if (found == true) return;
149 | visited[w] = true;
150 | if (w == t) {
151 | found = true;
152 | return;
153 | }
154 | for (int i = 0; i < adj[w].size(); ++i) {
155 | int q = adj[w].get(i);
156 | if (!visited[q]) {
157 | prev[q] = w;
158 | recurDfs(q, t, visited, prev);
159 | }
160 | }
161 | }
162 | ```
163 |
164 |
165 |
166 | ### DFS 复杂度分析
167 |
168 | - 时间复杂度:
169 |
170 | 每条边最多会被访问两次,一次是遍历、一次是回退。所以,图上的深度优先搜索算法的时间复杂度是 $O(E)$, E 表示边的个数.
171 |
172 | - 空间复杂度:
173 |
174 | 深度优先搜索算法的消耗内存主要是 visited、prev 数组和递归调用栈。
175 |
176 | visited、prev 数组的大小跟顶点的个数 V 成正比,递归调用栈的最大深度不会超过顶点的个数,所以总的空间复杂度就是 $O(V)$.
177 |
178 |
179 |
180 | ## 思考问题
181 |
182 | Question:如何找出社交网络中某个用户的三度好友关系?
183 |
184 | 上一节讲过,社交网络可以用图来表示。这个问题就非常适合用图的广度优先搜索算法来解决,因为广度优先搜索是层层往外推进的。首先,遍历与起始顶点最近的一层顶点,也就是用户的一度好友,然后再遍历与用户距离的边数为 2 的顶点,也就是二度好友关系,以及与用户距离的边数为 3 的顶点,也就是三度好友关系。
185 |
186 | 只需要稍加改造一下广度优先搜索代码,用一个数组来记录每个顶点与起始顶点的距离,非常容易就可以找出三度好友关系。
187 |
188 |
189 |
190 | *`@ 笔记时间 :2021-02-14 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/20. 字符串匹配算法 I :BF、RK 算法.md:
--------------------------------------------------------------------------------
1 | # 字符串匹配基础 I : BF、RK 算法
2 |
3 | [toc]
4 |
5 | ## 字符串匹配算法分类
6 |
7 | ### 单模式串匹配:
8 |
9 | - `一个模式串跟一个主串进行匹配 ,即,在一个主串中查找一个模式串。`
10 |
11 | - 分类:
12 | - 简单、好理解:BF 算法、RK 算法;
13 | - 难理解、但更加高效:BM 算法 和 KMP 算法.
14 |
15 | ### 多模式串匹配:
16 |
17 | - `在多个模式串和一个主串之间做匹配,即,在一个主串中查找多个模式串。`
18 |
19 |
20 |
21 | - 分类:
22 | - Trie 树 ;
23 | - AC 自动机.
24 |
25 | ## BF 算法
26 |
27 | - BF 算法,即,**Brute Force 暴力匹配算法**,也叫,**朴素匹配算法**。从名字可以看出,这种算法的字符串匹配方式很 **暴力**,当然也就会比较简单、好懂,但相应的性能也不高。
28 |
29 | - **主串** 与 **模式串**: 在字符串 A 中查找字符串 B,那字符串 A 就是主串,字符串 B 就是模式串。把主串的长度记作 n,模式串的长度记作 m.因为,是在主串中查找模式串,所以 n > m。
30 |
31 | - `算法思想:在主串中,检查起始位置分别是 0、1、2 .... n - m 且长度为 m 的 n - m + 1 个子串,看有没有跟模式串匹配的。`
32 |
33 | - 例子:
34 |
35 |
36 |
37 | 可以看出,在极端情况下,比如主串是 "aaaaa....aaaaaa"(省略号,代表有很多重复的字符 a),模式串 "aaaaab".每次都比对 m 个字符,要比对 n - m + 1 次,所以,这种算法的最坏情况时间复杂度是 $O(n*m)$.
38 |
39 |
40 |
41 | 尽管理论上,BF 算法的时间复杂度很高,是 $O(n*m)$,但在实际开发中,它却是一个比较常用的字符串匹配算法,原因如下:
42 |
43 |
44 |
45 | 1. 实际的软件开发中,大部分情况下,模式串和主串的长度都不会太长,而且每次模式串与主串中的子串匹配的时候,当中途遇到不能匹配的字符的时候,就可以停止了,不需要把 m 个字符都比对一下。所以,尽管理论上的最坏情况时间复杂度是 $O(n*m)$,但是,统计意义上,大部分情况下,算法执行效率要比这个高很多。
46 |
47 |
48 |
49 | 1. 朴素字符串匹配算法思想简单,代码实现也非常简单。简单意味着不容易出错,如果有 bug 也容易暴露和修复。在工程中,在满足性要求的前提下,简单是首选。这也是常说的 [KISS(Keep it Simple and Stupid) 设计原则]([KISS原则 - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/KISS原则)).
50 |
51 |
52 |
53 | 所以,在实际的软件开发,绝大部分情况下,朴素的字符串匹配算法就够用了。
54 |
55 | ## RK 算法
56 |
57 | - RK 算法,即,**Rabin-Karp 算法**.由它的两位发明只 Rabin 和 Karp 的名字来命名的。
58 |
59 | - BF算法的升级版:
60 |
61 | 在讲 BF 算法时,如果模式串长度为 m,主串长度为 n, 那在主串中,就会有 $n-m+1$ 个长度为 m 的子串,只需要暴力地对比这 $n-m+1$个字串与模式串,就可以找出主串与模式串匹配的子串。
62 |
63 | 但是,每次检查主串与子串是否匹配,需要依次比对每个字符,所以 BF 算法的时间复杂度就比较高,$O(n*m)$.对朴素的字符串匹配算法稍加改造,引入哈希算法,时间复杂度立刻就会降低。
64 |
65 | - `RK 算法的思路:通过哈希算法对主串中的 $n-m+1$个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。如果某个子串的哈希值与模式串相等,那就说明对应的字串和模式串匹配了(先不考虑哈希冲突)。因为哈希值是一个数字,数字之间比较是否相等是非常快速的,所以模式串和子串比较效率就提高了。`
66 |
67 |
68 |
69 | 不过,通过哈希算法计算子串的哈希值的时候,需要遍历子串中的每个字符。尽管模式串与子串比较的效率提高了,但是算法整体的效率并没有提高。有没有方法可以提高哈希算法计算子串哈希值得效率呢?
70 |
71 | 这就需要哈希算法设计的非常有技巧:假设要匹配的字符串的字符集中包含 K 个字符,可以用一个 K 进制数来表示一个子串,这个 K 进制数转化成十进制数,作为子串的哈希值。
72 |
73 | 举例:比如要处理的字符串只包含 a ~ z 这 26 个小写字母,那就用二十六进制来表示一个字符串。把 a ~ z 这 26 个字符映射到 0 ~ 25 这 26 个数字,a 就表示 0, b 就表示 1,以此类推,z 表示 25.
74 |
75 | 在十进制的表示法中,一个数字的值是通过下面的方式计算出来的。对应到二十六进制,一个包含 a 到 z 这 26 个字符的字符串,计算哈希德时候,只需要把进位从 10 改成 26 就可以。
76 |
77 |
78 |
79 | 为了方便解释,假设字符串中只包含 a ~ z 这 26 个小写字符,用二十六进制来表示一个字符串,对应的哈希值就是二十六进制数转化成十进制的结果。
80 |
81 | 这种哈希算法有一个特点,在主串中,相邻两个子串的哈希值的计算公式有一定的规律:
82 |
83 | **相邻两个子串 s[i-1] 和 s[i] (i 表示子串在主串中的起始位置,子串的长度都为 m),对应的哈希值计算公式有交集,即,可以使用 s[i-1] 的哈希值很快的计算出 s[i] 的哈希值,公式如下:**
84 |
85 |
86 |
87 | 不过,这里有一个小细节需要注意,那就是 $26^{(m-1)}$ 这部分的计算,可以通过查表的方法来提高效率。事先计算好,$26^0、26^1、26^2......26^{(m-1)}$,并且存储在一个长度为 m 的数组中,公式中的 "次方" 就对应数组的下标。当需要计算 26 的 x 次方的时候,就可以从数组的下标为 x 的位置取值,直接使用,省去了计算的时间。
88 |
89 |
90 |
91 | - 时间复杂度分析:
92 |
93 |
94 |
95 | 整个 RK 算法包含两部分,计算子串哈希值和模式串哈希值与子串哈希值之间的比较。
96 |
97 |
98 |
99 | 第一部分,前面也分析了,可以通过设计特殊的哈希算法,只需要扫描一遍主串就能计算出所有子串的哈希值了,所以这部分的时间复杂度是$O(n)$.
100 |
101 |
102 |
103 | 模式串哈希值与每个子串哈希值之间的比较时间复杂度是$O(1)$,总共需要比较 $n-m+1$ 个子串的哈希值,所以,这部分的时间复杂度也是 $O(n)$. 所以,RK 算法整体的时间复杂度就是 $O(n)$.
104 |
105 |
106 |
107 | 但是,模式串很长,相应的主串中的子串也会很长,通过上面的哈希算法计算得到的哈希值就可能很大,如果超过了计算机中整型数据可以表示的范围,那该如何解决呢?
108 |
109 |
110 |
111 | 刚刚设计的哈希算法是没有散列冲突的,也就是说,一个字符串与一个二十六进制数一一对应,不同的字符串的哈希值肯定不一样。因为,基于进制来表示一个字符串的,可以类比成十进制、十六进制来思考一下。实际上,为了能将哈希值落在整型数据范围内,可以牺牲一下,允许哈希冲突。那这个时候哈希算法该怎么设计呢?
112 |
113 |
114 |
115 | 哈希算法的设计方法有很多,例如:假设字符串中只包含 a ~ z 这 26 个英文字母,那每个字母对应一个数字,比如 a对应1,b对应2,以此类推,z对应26。可以把字符串中每个字母对应的数字相加,最后得到的和作为哈希值。这种哈希算法产生的哈希值的数据范围就相对要小很多了。
116 |
117 |
118 |
119 | 不过,这种哈希算法的哈希冲突概率也是挺高的。这只举了一个最简单的设计方法,还有很多更加优化的方法,比如将每一个字母从小到大对应一个素数,而不是1、2、3...这样的自然数,这样冲突的概率就会降低一些。
120 |
121 |
122 |
123 | 那新问题来了。之前只需要比较一下模式串和子串的哈希值,如果两个值相等,那这个子串就一定可以匹配模式串。但是,当存在哈希冲突的时候,有可能存在这样的情况,子串和模式串的哈希值虽然是相同的,但是两者本身并不匹配。
124 |
125 |
126 |
127 | 解决方法很简单,当发现一个子串的哈希值跟模式串的哈希值相等的时候,只需要再对比以下子串和模式串本身就好。当然,如果子串的哈希值与模式串的哈希值不想等,那对应的子串和模式串肯定也是不匹配的,就不需要比对子串和模式串本身了。
128 |
129 |
130 |
131 | 所以,哈希算法的冲突概率要相对控制得低一些,如果存在大量冲突,就会导致 RK 算法的时间复杂度退化、效率下降。极端情况下,如果存在大量的冲突,每次都再要比对子串和模式串本身,那时间复杂度就会退化成$O(n*m)$.但是,一般情况下,冲突不会很多,RK算法 的效率还是比 BF算法高。
132 |
133 |
134 |
135 |
136 |
137 |
138 | ## 总结
139 |
140 | - BF 算法是最简单、粗暴的字符串匹配算法,它的实现思路是,拿模式串与主串中是所有子串匹配,看是否有能匹配的子串。所以,时间复杂度也比较高,是 $O(n*m)$,n、m 表示主串和模式串的长度。不过,在实际的软件开发中,因为这种算法实现简单,对于处理小规模的字符串匹配很好用。
141 |
142 | - RK 算法是借助哈希算法对 BF 算法进行改造,即对每个子串分别求哈希值,然后拿子串的哈希值与模式串的哈希值比较,减少了比较的时间。所以,理想情况下,RK 算法的时间复杂度是 $O(n)$,跟 BF 算法相比,效率提高了很多。不过这样的效率取决于哈希算法的设计方法,如果存在冲突的情况下,时间复杂度可能会退化。极端情况下,哈希算法大量冲突,时间复杂度就退化为 $O(n*m)$。
143 |
144 |
145 |
146 | *`@ 笔记时间 :2021-02-14 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/24. 贪心算法 (Greedy Algorithm).md:
--------------------------------------------------------------------------------
1 | # 贪心算法(Greedy Algorithm)
2 |
3 | [toc]
4 |
5 | ## 前言
6 |
7 | 基础的数据结构和算法基本上学完了,接下来几节,会讲几种更加基本的算法。分别是*贪心算法、分治算法、回溯算法、动态规划*。更加确切地说,它们应该是`算法思想`,并不是具体的算法,常用来指导设计具体的算法和编码等。
8 |
9 | ## 理解贪心算法
10 |
11 | 问题:
12 |
13 | 假设有一个可以容纳 100kg 物品的背包,可以装各种物品。有以下 5 种豆子,每种豆子的总量和总价值都各不相同。为了让背包中所装物品的总价值最大,该如何选择在背包中装哪些豆子?每种豆子又该装多少呢?
14 |
15 |
16 |
17 | 实际上,这个问题很简单,只要先算一算每个物品的单价,按照单价由高到低依次来装就好了。单价从高到低排列,依次是:黑豆、绿豆、红豆、青豆、黄豆,所以,可以往背包里装 20kg 黑豆、30kg 绿豆、50kg 红豆。
18 |
19 | 这个问题的解决思路显而易见,它本质上借助的就是贪心算法。结合这个例子,总结一下贪心算法解决问题的步骤:
20 |
21 | `第一步,当看到这类问题的时候,首先要联想到贪心算法:针对一组数据,定义了限制值和期望值,希望从中选出几个数据,在满足限制值的情况下,期望值最大。`类比到刚刚的例子,限制值就是重量不能超过 100kg,期望值就是物品的总价值。这组数据就是 5 种豆子。从中选出一部分,满足重量不超过 100kg,并且总价值最大。
22 |
23 | `第二步,尝试看下这个问题是否可以用贪心算法解决:每次选择当前情况下,在对限制值同等贡献量的情况下,对期望值贡献最大的数据。`类比到刚刚的例子,每次都从剩下的豆子里面,选择单价最高的,也就是重量相同的情况下,对价值贡献最大的豆子。
24 |
25 | `第三步,举几个例子看下贪心算法产生的结果是否是最优的。`大部分情况下,举几个例子验证一下就可以了。严格地证明贪心算法的正确性,是非常复杂的,需要涉及比较多的数学推理。而且,从实践的角度来说,大部分能用贪心算法解决的问题,贪心算法的正确性都是显而易见的,也不需要严格的数学推导证明。
26 |
27 | `实际上,用贪心算法解决问题的思路,并不总能给出最优解`.因为当前面的选择,会影响后面的选择时,每种选择并不能保证一定是最优解。
28 |
29 | 举一个例子,在一个有权图中,从顶点 S 开始,找一条到顶点 T 的最短路径(路径中边的权值和最小)。贪心算法的解决思路是,每次都选择一条跟当前顶点相连的权最小的边,直到找到顶点 T。按照这种思路,求出的最短路径是 S->A->E->T,路径长度是 1+4+4=9。
30 |
31 |
32 |
33 | 但是,这种贪心的选择方式,最终求的路径并不是最短路径,因为路径 S->B->D->T 才是最短路径,因为这条路径的长度是 2+2+2=6。为什么贪心算法在这个问题上不工作了呢?
34 |
35 | **在这个问题上,贪心算法不工作的主要原因是,前面的选择,会影响后面的选择。如果第一步从顶点 S 走到顶点 A,那接下来面对的顶点和边,跟第一步从顶点 S 走到顶点 B,是完全不同的。所以,即便第一步选择最优的走法(边最短),但有可能因为这一步选择,导致后面每一步的选择都很糟糕,最终也就无缘全局最优解了。**
36 |
37 | ## 实战分析
38 |
39 | ### 1. 分糖果
40 |
41 | 有 m 个糖果和 n 个孩子。现在要把糖果分给这些孩子吃,但是糖果少,孩子多(m
64 |
65 | 这个问题的处理思路稍微不是那么好懂,不过,最好能弄懂,因为这个处理思想在很多贪心算法问题中都有用到,比如任务调度、教师排课等等问题。
66 |
67 | **这个问题的解决思路是这样的:假设这 n 个区间中最左端点是 $l_{min}$,最右端点是 $r_{max}$。这个问题就相当于,选择几个不相交的区间,从左到右将 $[l_{min}, r_{max}]$ 覆盖上。按照起始端点从小到大的顺序对这 n 个区间排序。每次选择的时候,左端点跟前面的已经覆盖的区间不重合的,右端点又尽量小的,这样可以让剩下的未覆盖区间尽可能的大,就可以放置更多的区间。这实际上就是一种贪心的选择方法。**
68 |
69 |
70 |
71 | ## 如何用贪心算法实现霍夫曼编码
72 |
73 | 假设有一个包含 1000 个字符的文件,每个字符占 1 个 byte(1byte=8bits),存储这 1000 个字符就一共需要 8000bits,那有没有更加节省空间的存储方式呢?
74 |
75 | 假设通过统计分析发现,这 1000 个字符中只包含 6 种不同字符,假设它们分别是 a、b、c、d、e、f。而 3 个二进制位(bit)就可以表示 8 个不同的字符,所以,为了尽量减少存储空间,每个字符用3个二进制位来表示。那存储这 1000 个字符只需要 3000bits 就可以了,比原来的存储方式节省了很多空间。不过,还有没有更加节省空间的存储方式呢?
76 |
77 | ```java
78 | a(000)、b(001)、c(010)、d(011)、e(100)、f(101)
79 | ```
80 |
81 | 霍夫曼编码就要登场了。霍夫曼编码是一种十分有效的编码方法,广泛用于数据压缩中,其压缩率通常在 20%~90% 之间。
82 |
83 | 霍夫曼编码不仅会考察文本中有多少个不同字符,还会考察每个字符出现的频率,根据频率的不同,选择不同长度的编码。霍夫曼编码试图用这种不等长的编码方法,来进一步增加压缩的效率。**如何给不同频率的字符选择不同长度的编码呢?根据贪心的思想,可以把出现频率比较多的字符,用稍微短一些的编码;出现频率比较少的字符,用稍微长一些的编码。对于等长的编码来说,解压缩起来很简单。比如刚才那个例子中,用 3 个 bit 表示一个字符。在解压缩的时候,每次从文本中读取3位二进制码,然后翻译成对应的字符。但是,霍夫曼编码是不等长的,每次应该读取1位还是2位、3位等等来解压缩呢?这个问题就导致霍夫曼编码解压缩起来比较复杂。为了避免解压缩过程中的歧义,霍夫曼编码要求各个字符的编码之间,不会出现某个编码是另一个编码前缀的情况**
84 |
85 |
86 |
87 | 假设这 6 个字符出现的频率从高到低依次是 a、b、c、d、e、f。把它们编码下面这个样子,任何一个字符的编码都不是另一个的前缀,在解压缩的时候,每次会读取尽可能长的可解压的二进制串,所以在解压缩的时候也不会歧义。经过这种编码压缩之后,这 1000 个字符只需要 2100bits 就可以了。
88 |
89 |
90 |
91 | 尽管霍夫曼编码的思想并不难理解,但是如何根据字符出现频率的不同,给不同的字符进行不同长度的编码呢?这里的处理稍微有些技巧。
92 |
93 | **把每个字符看作一个节点,并且附带着把频率放到优先级队列中。从队列中取出频率最小的两个节点 A、B,然后新建一个节点 C,把频率设置为两个节点的频率之和,并把这个新节点 C 作为节点 A、B 的父节点。最后再把 C 节点放入到优先级队列中。重复这个过程,直到队列中没有数据。**
94 |
95 |
96 |
97 | *现在,给每一条边加上画一个权值,指向左子节点的边统统标记为 0,指向右子节点的边,统统标记为 1,那`从根节点到叶节点的路径就是叶节点对应字符的霍夫曼编码。`*
98 |
99 |
100 |
101 | ## 总结
102 |
103 | 实际上,贪心算法适用的场景比较有限。这种算法思想更多的是指导设计基础算法。比如最小生成树算法、单源最短路径算法,这些算法都用到了贪心算法。所以,`不要刻意去记忆贪心算法的原理,多练习才是最有效的学习方法。`
104 |
105 | 贪心算法的最难的一块是如何将要解决的问题抽象成贪心算法模型,只要这一步搞定之后,贪心算法的编码一般都很简单。贪心算法解决问题的正确性虽然很多时候都看起来是显而易见的,但是要严谨地证明算法能够得到最优解,并不是件容易的事。所以,很多时候,只需要多举几个例子,看一下贪心算法的解决方案是否真的能得到最优解就可以了。
106 |
107 | ## 思考
108 |
109 | 1. 在一个非负整数 a 中,希望从中移除 k 个数字,让剩下的数字值最小,如何选择移除哪 k 个数字呢?
110 | 1. 假设有 n 个人等待被服务,但是服务窗口只有一个,每个人需要被服务的时间长度是不同的,如何安排被服务的先后顺序,才能让这 n 个人总的等待时间最短?
111 |
112 |
113 |
114 | *`@ 笔记时间 :2021-02- 18 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
115 |
116 |
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/25. 分治算法 (Divide and Conquer).md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 分治算法(Divide and Conquer)
4 |
5 | [toc]
6 |
7 | ## 如何理解分治算法?
8 |
9 | **分治算法(Divide and Conquer)的核心思想就是四个字,分而治之,就是将原问题划分成 n 个 规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。**
10 |
11 | 这个定义看起来有点类似递归的定义,但分治区别于递归:**分治算法是一种处理问题的思想,递归是一种编程技巧。**
12 |
13 | ### 涉及的操作
14 |
15 | 实际上,分治算法一般都比较适合用递归来实现,分治算法的递归实现中,每一层递归都会涉及这样三个操作:
16 |
17 | - `分解:将原问题分解成一系列子问题;`
18 | - `解决:递归地求解各个子问题,若子问题足够小,则直接求解;`
19 |
20 | - `合并:将子问题的结果合并成原问题.`
21 |
22 | ### 满足的条件
23 |
24 | 分之算法能解决的问题,一般需要满足以下几个条件:
25 |
26 | - `原问题与分解成的小问题具有相同的模式;`
27 | - `原问题分解成的子问题可以独立求解,子问题之间没有相关性`。这一点是分治算法跟动态规划的明显区别。
28 | - `具有分解终止条件,也就是说,当问题足够小时,可以直接求解;`
29 | - `可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果。`
30 |
31 | ## 应用举例分析
32 |
33 | 在排序算法中讲果数据的有序度、逆序度的概念:
34 |
35 | - 有序度:表示一组数据的有序程度;
36 | - 逆序度:表示一组数据的无序程度。
37 |
38 |
39 |
40 | 假设有 n 个数据,期望数据从小到大排列,那完全有序的数据的有序度就是 $n(n-1)/2$,逆序度等于 0; 相反,倒序排列的数据的有序度就是0,逆序度是 $n(n-1)/2$.除了这两种极端情况外,通过计算有序对或者逆序对的个数,来表示数据的有序度或逆序度:
41 |
42 |
43 |
44 | 现在的问题是,**如何编程求出一组数据的有序对个数或者逆序对个数呢?** 因为,有序对个数和逆序对个数的求解方法是类似的,所以,可以只思考逆序对个数的求解方法。
45 |
46 | 最笨的方法是,拿每个数字跟它后面的数字比较,看有几个比它小的。把比它小的数字个数记作 k,通过这样的方式,把每个数字都考察一遍之后,然后对每个数字对应的 k 值求和,最后得到的总和就是逆序对个数。不过,这样操作的时间复杂度 $O(n^2)$.那有没有更加高效的处理方法呢?
47 |
48 | 套用分治的思想来求数组 A 的逆序对个数,将数组分成前后两半 A1 和 A2,分别计算 A1 和 A2 的逆序对个数 K1 和 K2,然后再计算 A1 与 A2 之间的逆序对个数 K3. 那数组 A 的逆序对个数就等于 : K1 + K2 + K3.
49 |
50 | 使用分治算法其中一个要求是,子问题合并的代价不能太大,否则就起不了降低时间复杂度的效果。那么,如何快速地计算出两个子问题 A1 与 A2 之间的逆序对个数呢?
51 |
52 | 这里就要借助归并排序算法了:
53 |
54 | 归并排序中有一个非常关键的操作,就是将两个有序的小数组,合并成一个有序的数组。实际上,在这个合并的过程中,就可以计算这两个小数组的逆序对数了。每次合并操作,都计算逆序对个数,把这些计算出来的逆序对个数求和,就是这个数组的逆序对个数了。
55 |
56 |
57 |
58 | 代码如下:
59 |
60 | ```java
61 | private int num = 0; // 全局变量或者成员变量
62 |
63 | public int count(int[] a, int n) {
64 | num = 0;
65 | mergeSortCounting(a, 0, n-1);
66 | return num;
67 | }
68 |
69 | private void mergeSortCounting(int[] a, int p, int r) {
70 | if (p >= r) return;
71 | int q = (p+r)/2;
72 | mergeSortCounting(a, p, q);
73 | mergeSortCounting(a, q+1, r);
74 | merge(a, p, q, r);
75 | }
76 |
77 | private void merge(int[] a, int p, int q, int r) {
78 | int i = p, j = q+1, k = 0;
79 | int[] tmp = new int[r-p+1];
80 | while (i<=q && j<=r) {
81 | if (a[i] <= a[j]) {
82 | tmp[k++] = a[i++];
83 | } else {
84 | num += (q-i+1); // 统计p-q之间,比a[j]大的元素个数
85 | tmp[k++] = a[j++];
86 | }
87 | }
88 | while (i <= q) { // 处理剩下的
89 | tmp[k++] = a[i++];
90 | }
91 | while (j <= r) { // 处理剩下的
92 | tmp[k++] = a[j++];
93 | }
94 | for (i = 0; i <= r-p; ++i) { // 从tmp拷贝回a
95 | a[p+i] = tmp[i];
96 | }
97 | }
98 | ```
99 |
100 | 有些算法确实非常巧妙,并不是每个人短时间都能想到的。比如这个问题,并不是每个人都能想到可以借助归并排序算法来解决,不夸张地说,如果之前没接触过,绝大部分人都想不到。但是,如果告诉你可以借助归并排序算法来解决,那就应该要想到如何改造归并排序,来求解这个问题了,只要能做到这一点,就很棒了。
101 |
102 | 于分治算法,这还有两道比较经典的问题,可以自己练习一下。
103 |
104 | - 二维平面上有 n 个点,如何快速计算出两个距离最近的点对?
105 | - 有两个 $n*n$ 的矩阵 A,B,如何快速求解两个矩阵的乘积 $C=A*B$?
106 |
107 | ## 分治思想在海量数据处理中的应用
108 |
109 | 分治算法思想的应用是非常广泛的,并不仅限于指导编程和算法设计。它还经常用在海量数据处理的场景中。前面讲的数据结构和算法,大部分都是基于内存存储和单机处理。但是,如果要处理的数据量非常大,没法一次性放到内存中,这个时候,这些数据结构和算法就无法工作了。
110 |
111 | 比如,给 10GB 的订单文件按照金额排序这样一个需求,看似是一个简单的排序问题,但是因为数据量大,有 10GB,而机器的内存可能只有 2、3GB 这样子,无法一次性加载到内存,也就无法通过单纯地使用快排、归并等基础算法来解决了。
112 |
113 | 要解决这种数据量大到内存装不下的问题,就可以利用分治的思想。可以将海量的数据集合根据某种方法,划分为几个小的数据集合,每个小的数据集合单独加载到内存来解决,然后再将小数据集合合并成大数据集合。实际上,利用这种分治的处理思路,不仅仅能克服内存的限制,还能利用多线程或者多机处理,加快处理的速度。
114 |
115 | 比如刚刚举的那个例子,给 10GB 的订单排序,就可以先扫描一遍订单,根据订单的金额,将 10GB 的文件划分为几个金额区间。比如订单金额为 1 到 100 元的放到一个小文件,101 到 200 之间的放到另一个文件,以此类推。这样每个小文件都可以单独加载到内存排序,最后将这些有序的小文件合并,就是最终有序的 10GB 订单数据了。
116 |
117 | 如果订单数据存储在类似 GFS 这样的分布式系统上,当 10GB 的订单被划分成多个小文件的时候,每个文件可以并行加载到多台机器上处理,最后再将结果合并在一起,这样并行处理的速度也加快了很多。不过,这里有一个点要注意,就是数据的存储与计算所在的机器是同一个或者在网络中靠的很近(比如一个局域网内,数据存取速度很快),否则就会因为数据访问的速度,导致整个处理过程不但不会变快,反而有可能变慢。可能还有印象,这个就是讲线性排序的时候举的例子。
118 |
119 | ## 总结
120 |
121 | 分治算法用四个字概括就是“分而治之”,将原问题划分成 n 个规模较小而结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。这个思想非常简单、好理解。
122 |
123 | 今天讲了两种分治算法的典型的应用场景,一个是用来指导编码,降低问题求解的时间复杂度,另一个是解决海量数据处理问题。比如 MapReduce 本质上就是利用了分治思想。
124 |
125 | 感叹 Google 的创新能力如此之强,总是在引领技术的发展。实际上,`创新并非离我们很远,创新的源泉来自对事物本质的认识。无数优秀架构设计的思想来源都是基础的数据结构和算法,这本身就是算法的一个魅力所在。`
126 |
127 | ## 思考
128 |
129 | 1. 为什么说 MapReduce 的本质就是分治思想?
130 |
131 | 刚刚举的订单的例子,数据有 10GB 大小,可能给你的感受还不强烈。那如果我们要处理的数据是 1T、10T、100T 这样子的,那一台机器处理的效率肯定是非常低的。而对于谷歌搜索引擎来说,网页爬取、清洗、分析、分词、计算权重、倒排索引等等各个环节中,都会面对如此海量的数据(比如网页)。所以,利用集群并行处理显然是大势所趋。
132 |
133 |
134 |
135 | 一台机器过于低效,那我们就把任务拆分到多台机器上来处理。如果拆分之后的小任务之间互不干扰,独立计算,最后再将结果合并。这不就是分治思想吗?
136 |
137 | **实际上,MapReduce 框架只是一个任务调度器,底层依赖 GFS 来存储数据,依赖 Borg 管理机器。它从 GFS 中拿数据,交给 Borg 中的机器执行,并且时刻监控机器执行的进度,一旦出现机器宕机、进度卡壳等,就重新从 Borg 中调度一台机器执行。**
138 |
139 |
140 |
141 | 尽管 MapReduce 的模型非常简单,但是在 Google 内部应用非常广泛。它除了可以用来处理这种数据与数据之间存在关系的任务,比如 MapReduce 的经典例子,统计文件中单词出现的频率。除此之外,它还可以用来处理数据与数据之间没有关系的任务,比如对网页分析、分词等,每个网页可以独立的分析、分词,而这两个网页之间并没有关系。网页几十亿、上百亿,如果单机处理,效率低下,我们就可以利用 MapReduce 提供的高可靠、高性能、高容错的并行计算框架,并行地处理这几十亿、上百亿的网页。
142 |
143 | *`@ 笔记时间 :2021-02- 18 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/26. 回溯算法.md:
--------------------------------------------------------------------------------
1 | # 回溯算法
2 |
3 | [toc]
4 |
5 | ## 如何理解 "回溯算法"
6 |
7 | 笼统地讲,回溯算法很多时候都应用在"搜索"这类问题上。不过这里说的搜索,并不是狭义的指前面讲过的图的搜索算法,而是在一组可能的解中,搜索满足期望的解。
8 |
9 | 回溯的处理思想,有点类似枚举搜索。枚举所有的解,找到满足期望的解。为了有规律地枚举所有可能的解,避免遗漏和重复,把问题求解的过程分为多个阶段。每个阶段,都会面对一个岔路口,先随意选一条路走,当发现这条路走不通的时候(不符合期望的解),就回退到上一个岔路口,另选一种走法继续走。
10 |
11 | ## 举例:八皇后问题
12 |
13 | 有一个 8 x 8 的棋盘,希望往里放 8 个 棋子(皇后),每个棋子所在的行、列、对角线都不能有另一个棋子。如下图,第一幅图是满足条件的一种方法,第二幅图是不满足条件的。八皇后问题就是期望找到所有满足这种要求的放棋子方式。
14 |
15 |
16 |
17 | 把这个问题划分成8个阶段,依次将8个棋子放到第一行、第二行、第三行......第八行。在放置过程中,不停地检查当前放法,是否满足要求。如果满足,则跳到下一行继续放置棋子;如果不满足,那就再换一种放法,继续尝试。
18 |
19 | 回溯算法非常适合用递归代码实现,代码如下:
20 |
21 | ```java
22 | int[] result = new int[8];//全局或成员变量,下标表示行,值表示queen存储在哪一列
23 | public void cal8queens(int row) { // 调用方式:cal8queens(0);
24 | if (row == 8) { // 8个棋子都放置好了,打印结果
25 | printQueens(result);
26 | return; // 8行棋子都放好了,已经没法再往下递归了,所以就return
27 | }
28 | for (int column = 0; column < 8; ++column) { // 每一行都有8中放法
29 | if (isOk(row, column)) { // 有些放法不满足要求
30 | result[row] = column; // 第row行的棋子放到了column列
31 | cal8queens(row+1); // 考察下一行
32 | }
33 | }
34 | }
35 |
36 | private boolean isOk(int row, int column) {//判断row行column列放置是否合适
37 | int leftup = column - 1, rightup = column + 1;
38 | for (int i = row-1; i >= 0; --i) { // 逐行往上考察每一行
39 | if (result[i] == column) return false; // 第i行的column列有棋子吗?
40 | if (leftup >= 0) { // 考察左上对角线:第i行leftup列有棋子吗?
41 | if (result[i] == leftup) return false;
42 | }
43 | if (rightup < 8) { // 考察右上对角线:第i行rightup列有棋子吗?
44 | if (result[i] == rightup) return false;
45 | }
46 | --leftup; ++rightup;
47 | }
48 | return true;
49 | }
50 |
51 | private void printQueens(int[] result) { // 打印出一个二维矩阵
52 | for (int row = 0; row < 8; ++row) {
53 | for (int column = 0; column < 8; ++column) {
54 | if (result[row] == column) System.out.print("Q ");
55 | else System.out.print("* ");
56 | }
57 | System.out.println();
58 | }
59 | System.out.println();
60 | }
61 | ```
62 |
63 | ## 两个经典应用
64 |
65 | ### 1. 0-1 背包
66 |
67 | 0-1 背包是非常经典的算法问题,很多场景都可以抽象成这个问题模型。这个问题的经典解法是动态规划,不过还有一种简单但没有那么高效的解法,那就是今天讲的回溯算法。
68 |
69 | 0-1 背包问题有很多变体,先介绍一种比较基础的。有一个背包,背包总的承载重量是 $Wkg$。现在有 n 个物品,每个物品的重量不等,并且不可分割。现在期望选择几件物品,装载到背包中。在不超过背包所能装载重量的前提下,如何让背包中物品的总重量最大?
70 |
71 | 实际上,背包问题在贪心算法那一节,已经讲过一个了,不过那里讲的物品是可以分割的,可以装某个物品的一部分到背包里面。今天讲的这个背包问题,物品是不可分割的,要么装要么不装,所以叫 0-1 背包问题。显然,这个问题已经无法通过贪心算法来解决了。现在来看看,用回溯算法如何来解决。
72 |
73 | 对于每个物品来说,都有两种选择,装进背包或者不装进背包。对于 n 个物品来说,总的装法就有 $2^n$ 种,去掉总重量超过 $Wkg$ 的,从剩下的装法中选择总重量最接近 $Wkg$ 的。不过,如何才能不重复地穷举出这 $2^n$ 种装法呢?
74 |
75 | 这里就可以用回溯的方法。可以把物品依次排列,整个问题就分解为了 n 个阶段,每个阶段对应一个物品怎么选择。先对第一个物品进行处理,选择装进去或者不装进去,然后再递归地处理剩下的物品。
76 |
77 | 代码如下:
78 |
79 | ```java
80 | public int maxW = Integer.MIN_VALUE; //存储背包中物品总重量的最大值
81 | // cw表示当前已经装进去的物品的重量和;i表示考察到哪个物品了;
82 | // w背包重量;items表示每个物品的重量;n表示物品个数
83 | // 假设背包可承受重量100,物品个数10,物品重量存储在数组a中,那可以这样调用函数:
84 | // f(0, 0, a, 10, 100)
85 | public void f(int i, int cw, int[] items, int n, int w) {
86 | if (cw == w || i == n) { // cw==w表示装满了;i==n表示已经考察完所有的物品
87 | if (cw > maxW) maxW = cw;
88 | return;
89 | }
90 | f(i+1, cw, items, n, w);
91 | if (cw + items[i] <= w) {// 已经超过可以背包承受的重量的时候,就不要再装了
92 | f(i+1,cw + items[i], items, n, w);
93 | }
94 | }
95 | ```
96 |
97 | ## 2. 正则表达式
98 |
99 | 正则表达式里最重要的一种算法思想就是回溯。
100 |
101 | 正则表达式中,最重要的就是通配符,通配符结合在一起,可以表达非常丰富的语义。为了方便讲解,假设正则表达式中只包含"\*"和"?"这两种通配符,并且对这两个通配符的语义稍微做些改变,其中,"\*" 匹配任意多个(大于等于 0 个)任意字符,“?”匹配零个或者一个任意字符。基于以上背景假设,我们看下,如何用回溯算法,判断一个给定的文本,能否跟给定的正则表达式匹配?
102 |
103 | 依次考察正则表达式中的每个字符,当是非通配符时,就直接跟文本的字符进行匹配,如果相同,则继续往下处理;如果不同,则回溯。
104 |
105 | 如果遇到特殊字符的时候,就有多种处理方式了,也就是所谓的岔路口,比如"\*"有多种匹配方案,可以匹配任意个文本串中的字符,就先随意的选择一种匹配方案,然后继续考察剩下的字符。如果中途发现无法继续匹配下去了,就回到这个岔路口,重新选择一种匹配方案,然后再继续匹配剩下的字符。
106 |
107 | 代码如下:
108 |
109 | ```java
110 | public class Pattern {
111 | private boolean matched = false;
112 | private char[] pattern; // 正则表达式
113 | private int plen; // 正则表达式长度
114 |
115 | public Pattern(char[] pattern, int plen) {
116 | this.pattern = pattern;
117 | this.plen = plen;
118 | }
119 |
120 | public boolean match(char[] text, int tlen) { // 文本串及长度
121 | matched = false;
122 | rmatch(0, 0, text, tlen);
123 | return matched;
124 | }
125 |
126 | private void rmatch(int ti, int pj, char[] text, int tlen) {
127 | if (matched) return; // 如果已经匹配了,就不要继续递归了
128 | if (pj == plen) { // 正则表达式到结尾了
129 | if (ti == tlen) matched = true; // 文本串也到结尾了
130 | return;
131 | }
132 | if (pattern[pj] == '*') { // *匹配任意个字符
133 | for (int k = 0; k <= tlen-ti; ++k) {
134 | rmatch(ti+k, pj+1, text, tlen);
135 | }
136 | } else if (pattern[pj] == '?') { // ?匹配0个或者1个字符
137 | rmatch(ti, pj+1, text, tlen);
138 | rmatch(ti+1, pj+1, text, tlen);
139 | } else if (ti < tlen && pattern[pj] == text[ti]) { // 纯字符匹配才行
140 | rmatch(ti+1, pj+1, text, tlen);
141 | }
142 | }
143 | }
144 | ```
145 |
146 | ## 总结
147 |
148 | 回溯算法的思想非常简单,大部分情况下,都是用来解决广义的搜索问题,也就是,从一组可能的解中,选择出一个满足要求的解。回溯算法非常适合用递归来实现,在实现的过程中,剪枝操作是提高回溯效率的一种技巧。利用剪枝,并不需要穷举搜索所有的情况,从而提高搜索效率。
149 |
150 | 尽管回溯算法的原理非常简单,但是却可以解决很多问题,比如开头提到的深度优先搜索、八皇后、0-1 背包问题、图的着色、旅行商问题、数独、全排列、正则表达式匹配等等。如果感兴趣的话,可以自己搜索研究一下,最好还能用代码实现一下。如果这几个问题都能实现的话,基本就掌握了回溯算法。
151 |
152 | ## 课后思考
153 |
154 | 现在对今天讲到的 0-1 背包问题稍加改造,如果每个物品不仅重量不同,价值也不同。如何在不超过背包重量的情况下,让背包中的总价值最大?
155 |
156 |
157 |
158 | *`@ 笔记时间 :2021-02- 18 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/30. 拓扑排序.md:
--------------------------------------------------------------------------------
1 | # 拓扑排序
2 |
3 | [toc]
4 |
5 | ## 前言
6 |
7 | 从今天开始,就进入了专栏的高级篇。相对基础篇,高级篇涉及的知识点,都比较零散,不是太系统。
8 |
9 | 所以,会围绕一个实际软件开发的问题,在阐述具体解决方法的过程中,将涉及的知识点详细讲解出来。
10 |
11 | 所以,相较于基础篇“**开篇问题 - 知识讲解 - 回答开篇 - 总结 - 课后思考**”这样的文章结构,高级篇稍作了些改变,大致分为这样几个部分:“**问题阐述 - 算法解析 - 总结引申 - 课后思考**”。
12 |
13 | ## 开篇问题
14 |
15 | 如何确定代码源文件的编译依赖关系?
16 |
17 | 一个完整的项目往往会包含很多代码源文件。编译器在编译整个项目的时候,需要按照依赖关系,依次编译每个源文件。比如,A.cpp 依赖 B.cpp,那在编译的时候,编译器需要先编译 B.cpp,才能编译 A.cpp。
18 |
19 | 编译器通过分析源文件或者程序员事先写好的编译配置文件(比如 Makefile 文件),来获取这种局部的依赖关系。**那编译器又该如何通过源文件两两之间的局部依赖关系,确定一个全局的编译顺序呢?**
20 |
21 |
22 |
23 | ## 算法解析
24 |
25 | 这个问题的解决思路与“图”这种数据结构的一个经典算法“拓扑排序算法”有关。那什么是拓扑排序呢?这个概念很好理解,先来看一个生活中的拓扑排序的例子。
26 |
27 | 在穿衣服的时候都有一定的顺序,可以把这种顺序想成,衣服与衣服之间有一定的依赖关系。比如说,必须先穿袜子才能穿鞋,先穿内裤才能穿秋裤。假设现在有八件衣服要穿,它们之间的两两依赖关系已经很清楚了,那如何安排一个穿衣序列,能够满足所有的两两之间的依赖关系?
28 |
29 | 这就是个拓扑排序问题。从这个例子中,应该能想到,在很多时候,拓扑排序的序列并不是唯一的。可以看画的这幅图,找到了好几种满足这些局部先后关系的穿衣序列。
30 |
31 |
32 |
33 | 弄懂了这个生活中的例子,开篇的关于编译顺序的问题,应该也有思路了。开篇问题跟这个问题的模型是一样的,也可以抽象成一个拓扑排序问题。
34 |
35 | 拓扑排序的原理非常简单,重点应该放到拓扑排序的实现上面。
36 |
37 | **算法是构建在具体的数据结构之上的**,所以,针对这个问题,先来看下,如何将问题背景抽象成具体的数据结构?
38 |
39 | 可以把源文件与源文件之间的依赖关系,抽象成一个有向图。每个源文件对应图中的一个顶点,源文件之间的依赖关系就是顶点之间的边。
40 |
41 | 如果 a 先于 b 执行,也就是说 b 依赖于 a,那么就在顶点 a 和顶点 b 之间,构建一条从 a 指向 b 的边。而且,这个图不仅要是有向图,还要是一个有向无环图,也就是不能存在像 $a->b->c->a$ 这样的循环依赖关系。因为图中一旦出现环,拓扑排序就无法工作了。实际上,拓扑排序本身就是基于有向无环图的一个算法。
42 |
43 | ```java
44 | public class Graph {
45 | private int v; // 顶点的个数
46 | private LinkedList adj[]; // 邻接表
47 |
48 | public Graph(int v) {
49 | this.v = v;
50 | adj = new LinkedList[v];
51 | for (int i=0; i();
53 | }
54 | }
55 |
56 | public void addEdge(int s, int t) { // s先于t,边s->t
57 | adj[s].add(t);
58 | }
59 | }
60 | ```
61 |
62 | 数据结构定义好了,现在来看,**如何在这个有向无环图上,实现拓扑排序?**
63 |
64 | 拓扑排序有两种实现方法,都不难理解。它们分别是 **Kahn 算法**和 **DFS 深度优先搜索算法**。
65 |
66 | 依次来看下它们都是怎么工作的。
67 |
68 | ## 1. Kahn 算法
69 |
70 | Kahn 算法实际上用的是贪心算法思想,思路非常简单、好懂。
71 |
72 | 定义数据结构的时候,如果 s 需要先于 t 执行,那就添加一条 s 指向 t 的边。所以,如果某个顶点入度为 0, 也就表示,没有任何顶点必须先于这个顶点执行,那么这个顶点就可以执行了。
73 |
74 | 先从图中,找出一个入度为 0 的顶点,将其输出到拓扑排序的结果序列中(对应代码中就是把它打印出来),并且把这个顶点从图中删除(也就是把这个顶点可达的顶点的入度都减 1)。循环执行上面的过程,直到所有的顶点都被输出。最后输出的序列,就是满足局部依赖关系的拓扑排序。
75 |
76 | 把 Kahn 算法用代码实现了一下,可以结合着文字描述一块看下。不过,应该能发现,这段代码实现更有技巧一些,并没有真正删除顶点的操作,代码中有详细的注释。
77 |
78 | ```java
79 | public void topoSortByKahn() {
80 | int[] inDegree = new int[v]; // 统计每个顶点的入度
81 | for (int i = 0; i < v; ++i) {
82 | for (int j = 0; j < adj[i].size(); ++j) {
83 | int w = adj[i].get(j); // i->w
84 | inDegree[w]++;
85 | }
86 | }
87 | LinkedList queue = new LinkedList<>();
88 | for (int i = 0; i < v; ++i) {
89 | if (inDegree[i] == 0) queue.add(i);
90 | }
91 | while (!queue.isEmpty()) {
92 | int i = queue.remove();
93 | System.out.print("->" + i);
94 | for (int j = 0; j < adj[i].size(); ++j) {
95 | int k = adj[i].get(j);
96 | inDegree[k]--;
97 | if (inDegree[k] == 0) queue.add(k);
98 | }
99 | }
100 | }
101 | ```
102 |
103 | ## 2. DFS 算法
104 |
105 | 图上的深度优先搜索前面已经讲过了,实际上,拓扑排序也可以用深度优先搜索来实现。不过这里的名字要稍微改下,更加确切的说法应该是深度优先遍历,遍历图中的所有顶点,而非只是搜索一个顶点到另一个顶点的路径。
106 |
107 | 代码如下:
108 |
109 | ```java
110 | public void topoSortByDFS() {
111 | // 先构建逆邻接表,边s->t表示,s依赖于t,t先于s
112 | LinkedList inverseAdj[] = new LinkedList[v];
113 | for (int i = 0; i < v; ++i) { // 申请空间
114 | inverseAdj[i] = new LinkedList<>();
115 | }
116 | for (int i = 0; i < v; ++i) { // 通过邻接表生成逆邻接表
117 | for (int j = 0; j < adj[i].size(); ++j) {
118 | int w = adj[i].get(j); // i->w
119 | inverseAdj[w].add(i); // w->i
120 | }
121 | }
122 | boolean[] visited = new boolean[v];
123 | for (int i = 0; i < v; ++i) { // 深度优先遍历图
124 | if (visited[i] == false) {
125 | visited[i] = true;
126 | dfs(i, inverseAdj, visited);
127 | }
128 | }
129 | }
130 |
131 | private void dfs(
132 | int vertex, LinkedList inverseAdj[], boolean[] visited) {
133 | for (int i = 0; i < inverseAdj[vertex].size(); ++i) {
134 | int w = inverseAdj[vertex].get(i);
135 | if (visited[w] == true) continue;
136 | visited[w] = true;
137 | dfs(w, inverseAdj, visited);
138 | } // 先把vertex这个顶点可达的所有顶点都打印出来之后,再打印它自己
139 | System.out.print("->" + vertex);
140 | }
141 | ```
142 |
143 | 这个算法包含两个关键部分。
144 |
145 | 第一部分是通过**邻接表构造逆邻接表**。邻接表中,边 s->t 表示 s 先于 t 执行,也就是 t 要依赖 s。在逆邻接表中,边 s->t 表示 s 依赖于 t,s 后于 t 执行。为什么这么转化呢?这个跟我们这个算法的实现思想有关。
146 |
147 | 第二部分是这个算法的核心,也就是递归处理每个顶点。对于顶点 vertex 来说,先输出它可达的所有顶点,也就是说,先把它依赖的所有的顶点输出了,然后再输出自己。
148 |
149 | 到这里,用 Kahn 算法和 DFS 算法求拓扑排序的原理和代码实现都讲完了。来看下,**这两个算法的时间复杂度分别是多少呢?**
150 |
151 | 从 Kahn 代码中可以看出来,每个顶点被访问了一次,每个边也都被访问了一次,所以,Kahn 算法的时间复杂度就是 O(V+E)(V 表示顶点个数,E 表示边的个数)。
152 |
153 | DFS 算法的时间复杂度我们之前分析过。每个顶点被访问两次,每条边都被访问一次,所以时间复杂度也是 O(V+E)。
154 |
155 | 注意,这里的图可能不是连通的,有可能是有好几个不连通的子图构成,所以,E 并不一定大于 V,两者的大小关系不确定。所以,在表示时间复杂度的时候,V、E 都要考虑在内。
156 |
157 | ## 总结引申
158 |
159 | 在基础篇中,关于“图”,我们讲了图的定义和存储、图的广度和深度优先搜索。今天,我们又讲了一个关于图的算法,拓扑排序。
160 |
161 | 拓扑排序应用非常广泛,解决的问题的模型也非常一致。**凡是需要通过局部顺序来推导全局顺序的,一般都能用拓扑排序来解决。**除此之外,拓扑排序还能检测图中环的存在。对于 Kahn 算法来说,如果最后输出出来的顶点个数,少于图中顶点个数,图中还有入度不是 0 的顶点,那就说明,图中存在环。
162 |
163 | 关于图中环的检测,在递归那一节讲过一个例子,在查找最终推荐人的时候,可能会因为脏数据,造成存在循环推荐,比如,用户 A 推荐了用户 B,用户 B 推荐了用户 C,用户 C 又推荐了用户 A。如何避免这种脏数据导致的无限递归?这个问题,现在是时候解答了。
164 |
165 | 实际上,这就是环的检测问题。因为每次都只是查找一个用户的最终推荐人,所以,并不需要动用复杂的拓扑排序算法,而只需要记录已经访问过的用户 ID,当用户 ID 第二次被访问的时候,就说明存在环,也就说明存在脏数据。
166 |
167 | ```java
168 | HashSet hashTable = new HashSet<>(); // 保存已经访问过的actorId
169 | long findRootReferrerId(long actorId) {
170 | if (hashTable.contains(actorId)) { // 存在环
171 | return;
172 | }
173 | hashTable.add(actorId);
174 | Long referrerId =
175 | select referrer_id from [table] where actor_id = actorId;
176 | if (referrerId == null) return actorId;
177 | return findRootReferrerId(referrerId);
178 | }
179 | ```
180 |
181 | 如果把这个问题改一下,想要知道,数据库中的所有用户之间的推荐关系了,有没有存在环的情况。这个问题,就需要用到拓扑排序算法了。把用户之间的推荐关系,从数据库中加载到内存中,然后构建成今天讲的这种有向图数据结构,再利用拓扑排序,就可以快速检测出是否存在环了。
182 |
183 | ## 思考题
184 |
185 | 1. 在今天的讲解中,我们用图表示依赖关系的时候,如果 a 先于 b 执行,我们就画一条从 a 到 b 的有向边;反过来,如果 a 先于 b,我们画一条从 b 到 a 的有向边,表示 b 依赖 a,那今天讲的 Kahn 算法和 DFS 算法还能否正确工作呢?如果不能,应该如何改造一下呢?
186 |
187 | 2. 今天讲了两种拓扑排序算法的实现思路,Kahn 算法和 DFS 深度优先搜索算法,如果换做 BFS 广度优先搜索算法,还可以实现吗?
188 |
189 |
190 |
191 | *`@ 笔记时间 :2021-02- 19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/33. 概率统计.md:
--------------------------------------------------------------------------------
1 | # 概率统计
2 |
3 | [toc]
4 |
5 | ## 前言
6 |
7 | 今天,再讲一个跟过滤相关的问题,如何过滤垃圾短信?
8 |
9 | 垃圾短信和骚扰电话,每个人都收到过吧?买房、贷款、投资理财、开发票,各种垃圾短信和骚扰电话,不胜其扰。**如果你是一名手机应用开发工程师,让你实现一个简单的垃圾短信过滤功能以及骚扰电话拦截功能,该用什么样的数据结构和算法实现呢?**
10 |
11 | ## 算法解析
12 |
13 | 实际上,解决这个问题并不会涉及很高深的算法。今天,就一块看下,如何利用简单的数据结构和算法,解决这种看似非常复杂的问题。
14 |
15 | ### 1. 基于黑名单的过滤器
16 |
17 | 可以维护一个骚扰电话号码和垃圾短信发送号码的黑名单。这个黑名单的收集,有很多途径,比如,可以从一些公开的网站上下载,也可以通过类似“360 骚扰电话拦截”的功能,通过用户自主标记骚扰电话来收集。对于被多个用户标记,并且标记个数超过一定阈值的号码,就可以定义为骚扰电话,并将它加入到我们的黑名单中。
18 |
19 | 如果黑名单中的电话号码不多的话,可以使用散列表、二叉树等动态数据结构来存储,对内存的消耗并不会很大。如果把每个号码看作一个字符串,并且假设平均长度是 16 个字节,那存储 50 万个电话号码,大约需要 10MB 的内存空间。即便是对于手机这样的内存有限的设备来说,这点内存的消耗也是可以接受的。
20 |
21 | 但是,如果黑名单中的电话号码很多呢?比如有 500 万个。这个时候,如果再用散列表存储,就需要大约 100MB 的存储空间。为了实现一个拦截功能,耗费用户如此多的手机内存,这显然有点儿不合理。
22 |
23 | 上一节讲了,布隆过滤器最大的特点就是比较省存储空间,所以,用它来解决这个问题再合适不过了。如果要存储 500 万个手机号码,把位图大小设置为 10 倍数据大小,也就是 5000 万,那也只需要使用 5000 万个二进制位(5000 万 bits),换算成字节,也就是不到 7MB 的存储空间。比起散列表的解决方案,内存的消耗减少了很多。
24 |
25 | 实际上,还有一种时间换空间的方法,可以将内存的消耗优化到极致。
26 |
27 | 可以把黑名单存储在服务器端上,把过滤和拦截的核心工作,交给服务器端来做。手机端只负责将要检查的号码发送给服务器端,服务器端通过查黑名单,判断这个号码是否应该被拦截,并将结果返回给手机端。
28 |
29 | 用这个解决思路完全不需要占用手机内存。不过,有利就有弊。网络通信是比较慢的,所以,网络延迟就会导致处理速度降低。而且,这个方案还有个硬性要求,那就是只有在联网的情况下,才能正常工作。
30 |
31 | 基于黑名单的过滤器就讲完了,不过,可能还会说,布隆过滤器会有判错的概率呀!如果它把一个重要的电话或者短信,当成垃圾短信或者骚扰电话拦截了,对于用户来说,这是无法接受的。说得没错,这是一个很大的问题。不过,们现在先放一放,等三种过滤器都讲完之后,再来解答。
32 |
33 | ### 2. 基于规则的过滤器
34 |
35 | 刚刚讲了一种基于黑名单的垃圾短信过滤方法,但是,如果某个垃圾短信发送者的号码并不在黑名单中,那这种方法就没办法拦截了。所以,基于黑名单的过滤方式,还不够完善,再继续看一种基于规则的过滤方式。
36 |
37 | 对于垃圾短信来说,还可以通过短信的内容,来判断某条短信是否是垃圾短信。预先设定一些规则,如果某条短信符合这些规则,就可以判定它是垃圾短信。实际上,规则可以有很多,比如下面这几个:
38 |
39 | - 短信中包含特殊单词(或词语),比如一些非法、淫秽、反动词语等;
40 | - 短信发送号码是群发号码,非我们正常的手机号码,比如 +60389585;短信中包含回拨的联系方式,比如手机号码、微信、QQ、网页链接等,因为群发短信的号码一般都是无法回拨的;
41 | - 短信格式花哨、内容很长,比如包含各种表情、图片、网页链接等;
42 | - 符合已知垃圾短信的模板。垃圾短信一般都是重复群发,对于已经判定为垃圾短信的短信,我们可以抽象成模板,将获取到的短信与模板匹配,一旦匹配,我们就可以判定为垃圾短信。
43 |
44 | 当然,如果短信只是满足其中一条规则,如果就判定为垃圾短信,那会存在比较大的误判的情况。可以综合多条规则进行判断。比如,满足 2 条以上才会被判定为垃圾短信;或者每条规则对应一个不同的得分,满足哪条规则,就累加对应的分数,某条短信的总得分超过某个阈值,才会被判定为垃圾短信。
45 |
46 | 不过,只是给出了一些制定规则的思路,具体落实到执行层面,其实还有很大的距离,还有很多细节需要处理。比如,第一条规则中,该如何定义特殊单词;第二条规则中,该如何定义什么样的号码是群发号码等等。限于篇幅,就不一一详细展开来讲了。这里只讲一下,如何定义特殊单词?
47 |
48 | 如果只是自己拍脑袋想,哪些单词属于特殊单词,那势必有比较大的主观性,也很容易漏掉某些单词。实际上,可以基于概率统计的方法,借助计算机强大的计算能力,找出哪些单词最常出现在垃圾短信中,将这些最常出现的单词,作为特殊单词,用来过滤短信。
49 |
50 | 不过这种方法的前提是,有大量的样本数据,也就是说,要有大量的短信(比如 1000 万条短信),并且还要求,每条短信都做好了标记,它是垃圾短信还是非垃圾短信。
51 |
52 | 对这 1000 万条短信,进行分词处理(借助中文或者英文分词算法),去掉“的、和、是”等没有意义的停用词(Stop words),得到 n 个不同的单词。针对每个单词,统计有多少个垃圾短信出现了这个单词,有多少个非垃圾短信会出现这个单词,进而求出每个单词出现在垃圾短信中的概率,以及出现在非垃圾短信中的概率。如果某个单词出现在垃圾短信中的概率,远大于出现在非垃圾短信中的概率,那就把这个单词作为特殊单词,用来过滤垃圾短信。
53 |
54 | 文字描述不好理解,举个例子来解释一下:
55 |
56 |
57 |
58 | ### 3. 基于概率统计的过滤器
59 |
60 | 基于规则的过滤器,看起来很直观,也很好理解,但是它也有一定的局限性。一方面,这些规则受人的思维方式局限,规则未免太过简单;另一方面,垃圾短信发送者可能会针对规则,精心设计短信,绕过这些规则的拦截。对此,再来看一种更加高级的过滤方式,基于概率统计的过滤方式。
61 |
62 | 这种基于概率统计的过滤方式,基础理论是基于朴素贝叶斯算法。为了更好地理解下面的内容,先通过一个非常简单的例子来看下,什么是朴素贝叶斯算法?
63 |
64 | 假设事件 A 是“小明不去上学”,事件 B 是“下雨了”。现在统计了一下过去 10 天的下雨情况和小明上学的情况,作为样本数据。
65 |
66 |
67 |
68 | 来分析一下,这组样本有什么规律。在这 10 天中,有 4 天下雨,所以下雨的概率 P(B)=4/10。10 天中有 3 天,小明没有去上学,所以小明不去上学的概率 P(A)=3/10。在 4 个下雨天中,小明有 2 天没去上学,所以下雨天不去上学的概率 P(A|B)=2/4。在小明没有去上学的 3 天中,有 2 天下雨了,所以小明因为下雨而不上学的概率是 P(B|A)=2/3。实际上,这 4 个概率值之间,有一定的关系,这个关系就是朴素贝叶斯算法,我们用公式表示出来,就是下面这个样子。
69 |
70 |
71 |
72 | 朴素贝叶斯算法是不是非常简单?我们用一个公式就可以将它概括。弄懂了朴素贝叶斯算法,我们再回到垃圾短信过滤这个问题上,看看如何利用朴素贝叶斯算法,来做垃圾短信的过滤。
73 |
74 | 基于概率统计的过滤器,是基于短信内容来判定是否是垃圾短信。而计算机没办法像人一样理解短信的含义。所以,需要把短信抽象成一组计算机可以理解并且方便计算的**特征项**,用这一组特征项代替短信本身,来做垃圾短信过滤。
75 |
76 | 可以通过分词算法,把一个短信分割成 n 个单词。这 n 个单词就是一组特征项,全权代表这个短信。因此,判定一个短信是否是垃圾短信这样一个问题,就变成了,判定同时包含这几个单词的短信是否是垃圾短信。
77 |
78 | 不过,这里并不像基于规则的过滤器那样,非黑即白,一个短信要么被判定为垃圾短信、要么被判定为非垃圾短息。使用概率,来表征一个短信是垃圾短信的可信程度。如果用公式将这个概率表示出来,就是下面这个样子:
79 |
80 |
81 |
82 | 尽管有大量的短信样本,但是没法通过样本数据统计得到这个概率。为什么不可以呢?可能会说,只需要统计同时包含 $W_1,W_2,W_3,…,W_n$ 这 n 个单词的短信有多少个(假设有 x 个),然后看这里面属于垃圾短信的有几个(假设有 y 个),那包含 $W_1,W_2,W_3,…,W_n$ 这 n 个单词的短信是垃圾短信的概率就是 $y/x$。
83 |
84 | 理想很丰满,但现实往往很骨感。忽视了非常重要的一点,那就是样本的数量再大,毕竟也是有限的,样本中不会有太多同时包含 $W_1,W_2,W_3,…,W_n$ 的短信的,甚至很多时候,样本中根本不存在这样的短信。没有样本,也就无法计算概率。所以这样的推理方式虽然正确,但是实践中并不好用。
85 |
86 | 这个时候,朴素贝叶斯公式就可以派上用场了。通过朴素贝叶斯公式,将这个概率的求解,分解为其他三个概率的求解。可以看画的图。那转化之后的三个概率是否可以通过样本统计得到呢?
87 |
88 |
89 |
90 | $P(W_1,W_2,W_3,…,W_n 同时出现在一条短信中 | 短信是垃圾短信)$这个概率照样无法通过样本来统计得到。但是可以基于下面这条著名的概率规则来计算 :
91 |
92 | > 独立事件发生的概率计算公式:P(A\*B) = P(A)\*P(B)
93 |
94 | >如果事件 A 和事件 B 是独立事件,两者的发生没有相关性,事件 A 发生的概率 P(A) 等于 p1,事件 B 发生的概率 P(B) 等于 p2,那两个同时发生的概率 P(A\*B) 就等于 P(A)\*P(B)。
95 |
96 | 基于这条独立事件发生概率的计算公式,我们可以把 P(W1,W2,W3,…,Wn 同时出现在一条短信中 | 短信是垃圾短信)分解为下面这个公式:
97 |
98 |
99 |
100 | 其中,$P(W_i 出现在短信中 | 短信是垃圾短信)表示垃圾短信中包含 W_i 这个单词的概率有多大$。这个概率值通过统计样本很容易就能获得。$假设垃圾短信有 y 个,其中包含 W_i 的有 x 个,那这个概率值就等于 x/y$。
101 |
102 | $P(W_1,W_2,W_3,…,W_n 同时出现在一条短信中 | 短信是垃圾短信)$这个概率值,就计算出来了,再来看下剩下两个。
103 |
104 | P(短信是垃圾短信)表示短信是垃圾短信的概率,这个很容易得到。把样本中垃圾短信的个数除以总样本短信个数,就是短信是垃圾短信的概率。
105 |
106 | 不过,$P(W_1,W_2,W_3,…,W_n 同时出现在一条短信中)$这个概率还是不好通过样本统计得到,原因前面说过了,样本空间有限。不过,没必要非得计算这一部分的概率值。为什么这么说呢?
107 |
108 | 实际上,可以分别计算同时包含 $W_1,W_2,W_3,…,W_n$ 这 n 个单词的短信,是垃圾短信和非垃圾短信的概率。假设它们分别是 $p_1$ 和 $p_2$。并不需要单纯地基于 $p_1$ 值的大小来判断是否是垃圾短信,而是通过对比 $p_1$ 和$p_2$ 值的大小,来判断一条短信是否是垃圾短信。更细化一点讲,那就是,如果 $p_1$ 是 $p_2$ 的很多倍(比如 10 倍),才确信这条短信是垃圾短信。
109 |
110 |
111 |
112 | 基于这两个概率的倍数来判断是否是垃圾短信的方法,就可以不用计算 $P(W_1,W_2,W_3,…,W_n 同时出现在一条短信中)$这一部分的值了,因为计算 $p_1$ 与 $p_2$ 的时候,都会包含这个概率值的计算,所以在求解 $p_1$ 和 $p_2$ 倍数 $(p_1/p_2)$的时候,也就不需要这个值。
113 |
114 | ## 总结引申
115 |
116 | 今天,讲了基于黑名单、规则、概率统计三种垃圾短信的过滤方法,实际上,今天讲的这三种方法,还可以应用到很多类似的过滤、拦截的领域,比如垃圾邮件的过滤等等。
117 |
118 | 在讲黑名单过滤的时候,讲到布隆过滤器可能会存在误判情况,可能会导致用户投诉。实际上,可以结合三种不同的过滤方式的结果,对同一个短信处理,如果三者都表明这个短信是垃圾短信,才把它当作垃圾短信拦截过滤,这样就会更精准。
119 |
120 | 当然,在实际的工程中,还需要结合具体的场景,以及大量的实验,不断去调整策略,权衡垃圾短信判定的准确率(是否会把不是垃圾的短信错判为垃圾短信)和召回率(是否能把所有的垃圾短信都找到),来实现需求。
121 |
122 | ## 思考题
123 |
124 | 关于垃圾短信过滤和骚扰电话的拦截,我们可以一块儿头脑风暴一下,看看你还有没有其他方法呢?
125 |
126 | *`@ 笔记时间 :2021-02- 19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
127 |
128 |
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/34. 向量空间.md:
--------------------------------------------------------------------------------
1 | # 向量空间
2 |
3 | ## 前言
4 |
5 | 很多人都喜爱听歌,以前我们用 MP3 听歌,现在直接通过音乐 App 在线就能听歌。而且,各种音乐 App 的功能越来越强大,不仅可以自己选歌听,还可以根据你听歌的口味偏好,给你推荐可能会喜爱的音乐,而且有时候,推荐的音乐还非常适合你的口味,甚至会惊艳到你!如此智能的一个功能,你知道它是怎么实现的吗?
6 |
7 | ## 算法解析
8 |
9 | 实际上,要解决这个问题,并不需要特别高深的理论。解决思路的核心思想非常简单、直白,用两句话就能总结出来。
10 |
11 | - 找到跟你口味偏好相似的用户,把他们爱听的歌曲推荐给你;
12 | - 找出跟你喜爱的歌曲特征相似的歌曲,把这些歌曲推荐给你。
13 |
14 | 接下来,就分别讲解一下这两种思路的具体实现方法。
15 |
16 | ### 1. 基于相似用户做推荐
17 |
18 | 如何找到跟你口味偏好相似的用户呢?或者说如何定义口味偏好相似呢?实际上,思路也很简单,我们把跟你听类似歌曲的人,看作口味相似的用户。你可以看我下面画的这个图。我用“1”表示“喜爱”,用“0”笼统地表示“不发表意见”。从图中我们可以看出,你跟小明共同喜爱的歌曲最多,有 5 首。于是,我们就可以说,小明跟你的口味非常相似。
19 |
20 |
21 |
22 | 只需要遍历所有的用户,对比每个用户跟你共同喜爱的歌曲个数,并且设置一个阈值,如果你和某个用户共同喜爱的歌曲个数超过这个阈值,我们就把这个用户看作跟你口味相似的用户,把这个用户喜爱但你还没听过的歌曲,推荐给你。
23 |
24 | 不过,刚刚的这个解决方案中有一个问题,如何知道用户喜爱哪首歌曲呢?也就是说,如何定义用户对某首歌曲的喜爱程度呢?
25 |
26 | 实际上,可以通过用户的行为,来定义这个喜爱程度。我们给每个行为定义一个得分,得分越高表示喜爱程度越高。
27 |
28 |
29 |
30 | 还是刚刚那个例子,如果把每个人对每首歌曲的喜爱程度表示出来,就是下面这个样子。图中,某个人对某首歌曲是否喜爱,不再用“1”或者“0”来表示,而是对应一个具体的分值。
31 |
32 |
33 |
34 | 有了这样一个用户对歌曲的喜爱程度的对应表之后,如何来判断两个用户是否口味相似呢?
35 |
36 | 显然,不能再像之前那样,采用简单的计数来统计两个用户之间的相似度。还记得之前讲字符串相似度度量时,提到的编辑距离吗?这里的相似度度量,可以使用另外一个距离,那就是欧几里得距离(Euclidean distance)。欧几里得距离是用来计算两个向量之间的距离的。
37 |
38 | 这个概念中有两个关键词,向量和距离:
39 |
40 | 一维空间是一条线,我们用 $1,2,3……$这样单个的数,来表示一维空间中的某个位置;二维空间是一个面,我们用 $ (1,3)(4,2)(2,2)……$ 这样的两个数,来表示二维空间中的某个位置;三维空间是一个立体空间,我们用 $(1,3,5)(3,1,7)(2,4,3)……$ 这样的三个数,来表示三维空间中的某个位置。一维、二维、三维应该都不难理解,那更高维中的某个位置该如何表示呢?
41 |
42 | 类比一维、二维、三维的表示方法,K 维空间中的某个位置,可以写作 $(X_1,X_2,X_3,…,X_K)$。这种表示方法就是向量(vector)。我们知道,二维、三维空间中,两个位置之间有距离的概念,类比到高纬空间,同样也有距离的概念,这就是我们说的两个向量之间的距离。
43 |
44 | 那如何计算两个向量之间的距离呢?还是可以类比到二维、三维空间中距离的计算方法。通过类比,就可以得到两个向量之间距离的计算公式。这个计算公式就是欧几里得距离的计算公式:
45 |
46 |
47 |
48 | 把每个用户对所有歌曲的喜爱程度,都用一个向量表示。我们计算出两个向量之间的欧几里得距离,作为两个用户的口味相似程度的度量。从图中的计算可以看出,小明与你的欧几里得距离距离最小,也就是说,你俩在高维空间中靠得最近,所以,我们就断定,小明跟你的口味最相似。
49 |
50 |
51 |
52 | ## 2. 基于相似歌曲做推荐
53 |
54 | 刚刚我们讲了基于相似用户的歌曲推荐方法,但是,如果用户是一个新用户,我们还没有收集到足够多的行为数据,这个时候该如何推荐呢?我们现在再来看另外一种推荐方法,基于相似歌曲的推荐方法,也就是说,如果某首歌曲跟你喜爱的歌曲相似,我们就把它推荐给你。
55 |
56 | 如何判断两首歌曲是否相似呢?对于人来说,这个事情可能会比较简单,但是对于计算机来说,判断两首歌曲是否相似,那就需要通过量化的数据来表示了。我们应该通过什么数据来量化两个歌曲之间的相似程度呢?
57 |
58 | 最容易想到的是,我们对歌曲定义一些特征项,比如是伤感的还是愉快的,是摇滚还是民谣,是柔和的还是高亢的等等。类似基于相似用户的推荐方法,我们给每个歌曲的每个特征项打一个分数,这样每个歌曲就都对应一个特征项向量。我们可以基于这个特征项向量,来计算两个歌曲之间的欧几里得距离。欧几里得距离越小,表示两个歌曲的相似程度越大。
59 |
60 | 但是,要实现这个方案,需要有一个前提,那就是我们能够找到足够多,并且能够全面代表歌曲特点的特征项,除此之外,我们还要人工给每首歌标注每个特征项的得分。对于收录了海量歌曲的音乐 App 来说,这显然是一个非常大的工程。此外,人工标注有很大的主观性,也会影响到推荐的准确性。
61 |
62 | 既然基于歌曲特征项计算相似度不可行,那我们就换一种思路。对于两首歌,如果喜欢听的人群都是差不多的,那侧面就可以反映出,这两首歌比较相似。如图所示,每个用户对歌曲有不同的喜爱程度,我们依旧通过上一个解决方案中定义得分的标准,来定义喜爱程度。
63 |
64 |
65 |
66 | 你有没有发现,这个图跟基于相似用户推荐中的图几乎一样。只不过这里把歌曲和用户主次颠倒了一下。基于相似用户的推荐方法中,针对每个用户,我们将对各个歌曲的喜爱程度作为向量。基于相似歌曲的推荐思路中,针对每个歌曲,我们将每个用户的打分作为向量。
67 |
68 | 有了每个歌曲的向量表示,我们通过计算向量之间的欧几里得距离,来表示歌曲之间的相似度。欧几里得距离越小,表示两个歌曲越相似。然后,我们就在用户已经听过的歌曲中,找出他喜爱程度较高的歌曲。然后,我们找出跟这些歌曲相似度很高的其他歌曲,推荐给他。
69 |
70 | ## 总结引申
71 |
72 | 实际上,这个问题是**推荐系统(Recommendation System**)里最典型的一类问题。之所以讲这部分内容,主要还是想给你展示,算法的强大之处,利用简单的向量空间的欧几里得距离,就能解决如此复杂的问题。不过,今天,我只给你讲解了基本的理论,实践中遇到的问题还有很多,比如冷启动问题,产品初期积累的数据不多,不足以做推荐等等。这些更加深奥的内容,你可以之后自己在实践中慢慢探索。
73 |
74 | *`@ 笔记时间 :2021-02- 19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
75 |
76 |
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/36.索引.md:
--------------------------------------------------------------------------------
1 | # 索引
2 |
3 | [toc]
4 |
5 | ## 前言
6 |
7 | 在第 48 节中,我们讲了 MySQL 数据库索引的实现原理。MySQL 底层依赖的是 B+ 树这种数据结构。留言里有同学问我,那**类似 Redis 这样的 Key-Value 数据库中的索引,又是怎么实现的呢?底层依赖的又是什么数据结构呢?**
8 |
9 | 今天,我就来讲一下索引这种常用的技术解决思路,底层往往会依赖哪些数据结构。同时,通过索引这个应用场景,我也带你回顾一下,之前我们学过的几种支持动态集合的数据结构。
10 |
11 | ## 为什需要索引?
12 |
13 | 在实际的软件开发中,业务纷繁复杂,功能千变万化,但是,万变不离其宗。如果抛开这些业务和功能的外壳,其实它们的本质都可以抽象为“对数据的存储和计算”。对应到数据结构和算法中,那“存储”需要的就是数据结构,“计算”需要的就是算法。
14 |
15 | 对于存储的需求,功能上无外乎增删改查。这其实并不复杂。但是,一旦存储的数据很多,那性能就成了这些系统要关注的重点,特别是在一些跟存储相关的基础系统(比如 MySQL 数据库、分布式文件系统等)、中间件(比如消息中间件 RocketMQ 等)中。
16 |
17 | “如何节省存储空间、如何提高数据增删改查的执行效率”,这样的问题就成了设计的重点。而这些系统的实现,都离不开一个东西,那就是索引。不夸张地说,索引设计得好坏,直接决定了这些系统是否优秀。
18 |
19 | 索引这个概念,非常好理解。你可以类比书籍的目录来理解。如果没有目录,我们想要查找某个知识点的时候,就要一页一页翻。通过目录,我们就可以快速定位相关知识点的页数,查找的速度也会有质的提高。
20 |
21 | ## 索引的需求定义
22 |
23 | 索引的概念不难理解,我想你应该已经搞明白。接下来,我们就分析一下,在设计索引的过程中,需要考虑到的一些因素,换句话说就是,我们该如何定义清楚需求呢?
24 |
25 | 对于系统设计需求,我们一般可以从**功能性需求**和**非功能性需求**两方面来分析,这个我们之前也说过。因此,这个问题也不例外。
26 |
27 | ### 1. 功能性需求
28 |
29 | 对于功能性需求需要考虑的点,我把它们大致概括成下面这几点。
30 |
31 | **数据是格式化数据还是非格式化数据?**要构建索引的原始数据,类型有很多。我把它分为两类,一类是结构化数据,比如,MySQL 中的数据;另一类是非结构化数据,比如搜索引擎中网页。对于非结构化数据,我们一般需要做预处理,提取出查询关键词,对关键词构建索引。
32 |
33 | **数据是静态数据还是动态数据?**如果原始数据是一组静态数据,也就是说,不会有数据的增加、删除、更新操作,所以,我们在构建索引的时候,只需要考虑查询效率就可以了。这样,索引的构建就相对简单些。不过,大部分情况下,我们都是对动态数据构建索引,也就是说,我们不仅要考虑到索引的查询效率,在原始数据更新的同时,我们还需要动态地更新索引。支持动态数据集合的索引,设计起来相对也要更加复杂些。
34 |
35 | **单值查找还是区间查找?**所谓单值查找,也就是根据查询关键词等于某个值的数据。这种查询需求最常见。所谓区间查找,就是查找关键词处于某个区间值的所有数据。你可以类比 MySQL 数据库的查询需求,自己想象一下。实际上,不同的应用场景,查询的需求会多种多样。
36 |
37 | **单关键词查找还是多关键词组合查找?**比如,搜索引擎中构建的索引,既要支持一个关键词的查找,比如“数据结构”,也要支持组合关键词查找,比如“数据结构 AND 算法”。对于单关键词的查找,索引构建起来相对简单些。对于多关键词查询来说,要分多种情况。像 MySQL 这种结构化数据的查询需求,我们可以实现针对多个关键词的组合,建立索引;对于像搜索引擎这样的非结构数据的查询需求,我们可以针对单个关键词构建索引,然后通过集合操作,比如求并集、求交集等,计算出多个关键词组合的查询结果。
38 |
39 | 实际上,不同的场景,不同的原始数据,对于索引的需求也会千差万别。我这里只列举了一些比较有共性的需求。
40 |
41 | ### 2. 非功能性需求
42 |
43 | 讲完了功能性需求,我们再来看,索引设计的非功能性需求。
44 |
45 | **不管是存储在内存中还是磁盘中,索引对存储空间的消耗不能过大。**如果存储在内存中,索引对占用存储空间的限制就会非常苛刻。毕竟内存空间非常有限,一个中间件启动后就占用几个 GB 的内存,开发者显然是无法接受的。如果存储在硬盘中,那索引对占用存储空间的限制,稍微会放宽一些。但是,我们也不能掉以轻心。因为,有时候,索引对存储空间的消耗会超过原始数据。
46 |
47 | **在考虑索引查询效率的同时,我们还要考虑索引的维护成本。**索引的目的是提高查询效率,但是,基于动态数据集合构建的索引,我们还要考虑到,索引的维护成本。因为在原始数据动态增删改的同时,我们也需要动态地更新索引。而索引的更新势必会影响到增删改操作的性能。
48 |
49 | ## 构建索引常用的数据结构有哪些?
50 |
51 | 刚刚从很宏观的角度,总结了在索引设计的过程中,需要考虑的一些共性因素。现在,我们就来看,对于不同需求的索引结构,底层一般使用哪种数据结构。
52 |
53 | 实际上,常用来构建索引的数据结构,就是我们之前讲过的几种支持动态数据集合的数据结构。比如,散列表、红黑树、跳表、B+ 树。除此之外,位图、布隆过滤器可以作为辅助索引,有序数组可以用来对静态数据构建索引。
54 |
55 | 散列表增删改查操作的性能非常好,时间复杂度是 O(1)。一些键值数据库,比如 Redis、Memcache,就是使用散列表来构建索引的。这类索引,一般都构建在内存中。
56 |
57 | **红黑树**作为一种常用的平衡二叉查找树,数据插入、删除、查找的时间复杂度是 O(logn),也非常适合用来构建内存索引。Ext 文件系统中,对磁盘块的索引,用的就是红黑树。
58 |
59 | **B+ 树**比起红黑树来说,更加适合构建存储在磁盘中的索引。B+ 树是一个多叉树,所以,对相同个数的数据构建索引,B+ 树的高度要低于红黑树。当借助索引查询数据的时候,读取 B+ 树索引,需要的磁盘 IO 次数会更少。所以,大部分关系型数据库的索引,比如 MySQL、Oracle,都是用 B+ 树来实现的。
60 |
61 | **跳表**也支持快速添加、删除、查找数据。而且,我们通过灵活调整索引结点个数和数据个数之间的比例,可以很好地平衡索引对内存的消耗及其查询效率。Redis 中的有序集合,就是用跳表来构建的。
62 |
63 | 除了散列表、红黑树、B+ 树、跳表之外,位图和布隆过滤器这两个数据结构,也可以用于索引中,辅助存储在磁盘中的索引,加速数据查找的效率。我们来看下,具体是怎么做的?
64 |
65 | 布隆过滤器有一定的判错率。但是,我们可以规避它的短处,发挥它的长处。尽管对于判定存在的数据,有可能并不存在,但是对于判定不存在的数据,那肯定就不存在。而且,布隆过滤器还有一个更大的特点,那就是内存占用非常少。我们可以针对数据,构建一个布隆过滤器,并且存储在内存中。当要查询数据的时候,我们可以先通过布隆过滤器,判定是否存在。如果通过布隆过滤器判定数据不存在,那我们就没有必要读取磁盘中的索引了。对于数据不存在的情况,数据查询就更加快速了。
66 |
67 | 实际上,有序数组也可以被作为索引。如果数据是静态的,也就是不会有插入、删除、更新操作,那我们可以把数据的关键词(查询用的)抽取出来,组织成有序数组,然后利用二分查找算法来快速查找数据。
68 |
69 | ## 总结引申
70 |
71 | 今天这节算是一节总结课。我从索引这个非常常用的技术方案,给你展示了散列表、红黑树、跳表、位图、布隆过滤器、有序数组这些数据结构的应用场景。学习完这节课之后,不知道你对这些数据结构以及索引,有没有更加清晰的认识呢?
72 |
73 | 从这一节内容中,你应该可以看出,架构设计离不开数据结构和算法。要想成长为一个优秀的业务架构师、基础架构师,数据结构和算法的根基一定要打稳。因为,那些看似很惊艳的架构设计思路,实际上,都是来自最常用的数据结构和算法。
74 |
75 | ## 课后思考
76 |
77 | 你知道基础系统、中间件、开源软件等系统中,有哪些用到了索引吗?这些系统的索引是如何实现的呢?
78 |
79 | > 待回看
80 |
81 | *`@ 笔记时间 :2021-02- 19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Part3 算法(Algorithm)/37.并行算法.md.md:
--------------------------------------------------------------------------------
1 | # 并行算法
2 |
3 | [toc]
4 |
5 | ## 前言
6 |
7 | 时间复杂度是衡量算法执行效率的一种标准。但是,时间复杂度并不能跟性能划等号。在真实的软件开发中,即便在不降低时间复杂度的情况下,也可以通过一些优化手段,提升代码的执行效率。毕竟,对于实际的软件开发来说,即便是像 10%、20% 这样微小的性能提升,也是非常可观的。
8 |
9 | 算法的目的就是为了提高代码执行的效率。那**当算法无法再继续优化的情况下,我们该如何来进一步提高执行效率呢?**我们今天就讲一种非常简单但又非常好用的优化方法,那就是并行计算。今天,我就通过几个例子,给你展示一下,**如何借助并行计算的处理思想对算法进行改造?**
10 |
11 | ## 并行排序
12 |
13 | 假设我们要给大小为 8GB 的数据进行排序,并且,我们机器的内存可以一次性容纳这么多数据。对于排序来说,最常用的就是时间复杂度为 O(nlogn) 的三种排序算法,归并排序、快速排序、堆排序。从理论上讲,这个排序问题,已经很难再从算法层面优化了。而利用并行的处理思想,我们可以很轻松地将这个给 8GB 数据排序问题的执行效率提高很多倍。具体的实现思路有下面两种。
14 |
15 | **第一种是对归并排序并行化处理。**我们可以将这 8GB 的数据划分成 16 个小的数据集合,每个集合包含 500MB 的数据。我们用 16 个线程,并行地对这 16 个 500MB 的数据集合进行排序。这 16 个小集合分别排序完成之后,我们再将这 16 个有序集合合并。
16 |
17 | **第二种是对快速排序并行化处理。**我们通过扫描一遍数据,找到数据所处的范围区间。我们把这个区间从小到大划分成 16 个小区间。我们将 8GB 的数据划分到对应的区间中。针对这 16 个小区间的数据,我们启动 16 个线程,并行地进行排序。等到 16 个线程都执行结束之后,得到的数据就是有序数据了。
18 |
19 | 对比这两种处理思路,它们利用的都是分治的思想,对数据进行分片,然后并行处理。它们的区别在于,第一种处理思路是,先随意地对数据分片,排序之后再合并。第二种处理思路是,先对数据按照大小划分区间,然后再排序,排完序就不需要再处理了。这个跟归并和快排的区别如出一辙。
20 |
21 | 这里我还要多说几句,**如果要排序的数据规模不是 8GB,而是 1TB,那问题的重点就不是算法的执行效率了,而是数据的读取效率。因为 1TB 的数据肯定是存在硬盘中,无法一次性读取到内存中,这样在排序的过程中,就会有频繁地磁盘数据的读取和写入。如何减少磁盘的 IO 操作,减少磁盘数据读取和写入的总量,就变成了优化的重点。**不过这个不是我们这节要讨论的重点,你可以自己思考下。
22 |
23 | ## 并行查找
24 |
25 | 散列表是一种非常适合快速查找的数据结构。
26 |
27 | 如果我们是给动态数据构建索引,在数据不断加入的时候,散列表的装载因子就会越来越大。为了保证散列表性能不下降,我们就需要对散列表进行动态扩容。对如此大的散列表进行动态扩容,一方面比较耗时,另一方面比较消耗内存。比如,我们给一个 2GB 大小的散列表进行扩容,扩展到原来的 1.5 倍,也就是 3GB 大小。这个时候,实际存储在散列表中的数据只有不到 2GB,所以内存的利用率只有 60%,有 1GB 的内存是空闲的。
28 |
29 | 实际上,我们可以将数据随机分割成 k 份(比如 16 份),每份中的数据只有原来的 1/k,然后我们针对这 k 个小数据集合分别构建散列表。这样,散列表的维护成本就变低了。当某个小散列表的装载因子过大的时候,我们可以单独对这个散列表进行扩容,而其他散列表不需要进行扩容。
30 |
31 | 还是刚才那个例子,假设现在有 2GB 的数据,我们放到 16 个散列表中,每个散列表中的数据大约是 150MB。当某个散列表需要扩容的时候,我们只需要额外增加 150*0.5=75MB 的内存(假设还是扩容到原来的 1.5 倍)。无论从扩容的执行效率还是内存的利用率上,这种多个小散列表的处理方法,都要比大散列表高效。
32 |
33 | 当我们要查找某个数据的时候,我们只需要通过 16 个线程,并行地在这 16 个散列表中查找数据。这样的查找性能,比起一个大散列表的做法,也并不会下降,反倒有可能提高。
34 |
35 | 当往散列表中添加数据的时候,我们可以选择将这个新数据放入装载因子最小的那个散列表中,这样也有助于减少散列冲突。
36 |
37 | ## 并行字符串匹配
38 |
39 | 前面学过,在文本中查找某个关键词这样一个功能,可以通过字符串匹配算法来实现。我们之前学过的字符串匹配算法有 KMP、BM、RK、BF 等。当在一个不是很长的文本中查找关键词的时候,这些字符串匹配算法中的任何一个,都可以表现得非常高效。但是,如果我们处理的是超级大的文本,那处理的时间可能就会变得很长,那有没有办法加快匹配速度呢?
40 |
41 | 可以把大的文本,分割成 k 个小文本。假设 k 是 16,我们就启动 16 个线程,并行地在这 16 个小文本中查找关键词,这样整个查找的性能就提高了 16 倍。16 倍效率的提升,从理论的角度来说并不多。但是,对于真实的软件开发来说,这显然是一个非常可观的优化。
42 |
43 | 不过,这里还有一个细节要处理,那就是原本包含在大文本中的关键词,被一分为二,分割到两个小文本中,这就会导致尽管大文本中包含这个关键词,但在这 16 个小文本中查找不到它。实际上,这个问题也不难解决,我们只需要针对这种特殊情况,做一些特殊处理就可以了。
44 |
45 | 假设关键词的长度是 m。我们在每个小文本的结尾和开始各取 m 个字符串。前一个小文本的末尾 m 个字符和后一个小文本的开头 m 个字符,组成一个长度是 2m 的字符串。我们再拿关键词,在这个长度为 2m 的字符串中再重新查找一遍,就可以补上刚才的漏洞了。
46 |
47 | ## 并行搜索
48 |
49 | 前面学习过好几种搜索算法,它们分别是广度优先搜索、深度优先搜索、Dijkstra 最短路径算法、A* 启发式搜索算法。对于广度优先搜索算法,我们也可以将其改造成并行算法。广度优先
50 |
51 | 广度优先搜索是一种逐层搜索的搜索策略。基于当前这一层顶点,我们可以启动多个线程,并行地搜索下一层的顶点。在代码实现方面,原来广度优先搜索的代码实现,是通过一个队列来记录已经遍历到但还没有扩展的顶点。现在,经过改造之后的并行广度优先搜索算法,我们需要利用两个队列来完成扩展顶点的工作。
52 |
53 | 假设这两个队列分别是队列 A 和队列 B。多线程并行处理队列 A 中的顶点,并将扩展得到的顶点存储在队列 B 中。等队列 A 中的顶点都扩展完成之后,队列 A 被清空,我们再并行地扩展队列 B 中的顶点,并将扩展出来的顶点存储在队列 A。这样两个队列循环使用,就可以实现并行广度优先搜索算法。
54 |
55 | ## 总结引申
56 |
57 | 上一节,通过实际软件开发中的“索引”这一技术点,回顾了之前学过的一些支持动态数据集合的数据结构。今天,我们又通过“并行算法”这个话题,回顾了之前学过的一些算法。
58 |
59 | 今天的内容比较简单,没有太复杂的知识点。我通过一些例子,比如并行排序、查找、搜索、字符串匹配,给你展示了并行处理的实现思路,也就是对数据进行分片,对没有依赖关系的任务,并行地执行。
60 |
61 | 并行计算是一个工程上的实现思路,尽管跟算法关系不大,但是,在实际的软件开发中,它确实可以非常巧妙地提高程序的运行效率,是一种非常好用的性能优化手段。
62 |
63 | 特别是,当要处理的数据规模达到一定程度之后,我们无法通过继续优化算法,来提高执行效率 的时候,我们就需要在实现的思路上做文章,利用更多的硬件资源,来加快执行的效率。所以,在很多超大规模数据处理中,并行处理的思想,应用非常广泛,比如 MapReduce 实际上就是一种并行计算框架。
64 |
65 | ## 课后思考
66 |
67 | 假设我们有 n 个任务,为了提高执行的效率,我们希望能并行执行任务,但是各个任务之间又有一定的依赖关系,如何根据依赖关系找出可以并行执行的任务?
68 |
69 | *`@ 笔记时间 :2021-02- 19 FROM 极客时间 《算法啊与数据结构之美》 王争 专栏`*
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/README.md:
--------------------------------------------------------------------------------
1 | # Algorithm (完结)
2 |
3 | 总结 极客时间 《算法啊与数据结构之美》 王争 专栏
4 |
5 | - 总结,笼统记录
6 | - 代码见专栏
7 | - 结合 Typora Markdown 编辑器 阅读效果更好.
8 |
9 | 
10 |
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/01.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/02.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/03.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/04.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/05.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/05.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/06.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/06.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/07.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/08.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/08.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/09.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/09.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/10.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/11.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/12.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/13.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/14.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/15.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/16.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/17.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/18.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/19.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/20.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/21.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/22.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/22.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/23.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/23.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/24.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/24.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/25.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/25.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/26.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/26.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/27.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/27.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/28.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/28.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/29.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/29.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/30.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/30.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/31.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/31.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/32.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/32.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/33.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/33.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/34.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/34.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/35.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/35.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/36.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/36.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/37.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/37.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/38.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/38.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/39.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/39.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/40.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/40.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/41.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/41.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/42.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/42.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/43.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/43.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/44.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/44.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/45.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/45.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/46.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/46.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/47.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/47.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/48.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/48.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/49.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/49.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/50.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/50.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/51.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/51.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/52.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/52.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/53.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/53.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/54.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/54.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/55.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/55.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/56.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/56.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/57.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/57.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/58.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/58.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/59.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/59.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/60.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/60.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources/61.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources/61.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/00.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/00.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/01.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/02.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/03.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/04.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/04.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/05.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/05.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/06.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/06.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/07.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/07.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/08.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/08.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/09.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/09.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/10.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/10.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/100.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/100.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/101.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/101.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/102.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/102.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/103.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/103.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/104.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/104.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/105.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/105.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/106.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/106.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/107.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/107.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/108.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/108.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/109.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/109.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/11.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/11.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/110.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/110.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/111.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/111.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/112.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/112.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/113.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/113.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/114.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/114.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/115.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/115.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/116.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/116.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/117.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/117.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/118.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/118.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/119.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/119.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/12.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/12.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/120.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/120.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/121.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/121.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/122.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/122.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/123.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/123.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/124.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/124.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/125.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/125.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/126.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/126.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/127.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/127.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/128.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/128.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/129.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/129.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/13.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/13.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/130.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/130.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/131.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/131.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/132.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/132.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/133.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/133.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/134.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/134.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/135.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/135.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/136.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/136.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/137.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/137.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/138.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/138.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/139.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/139.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/14.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/14.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/140.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/140.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/141.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/141.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/142.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/142.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/143.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/143.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/144.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/144.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/145.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/145.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/146.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/146.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/147.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/147.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/148.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/148.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/149.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/149.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/15.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/15.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/150.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/150.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/151.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/151.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/152.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/152.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/153.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/153.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/154.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/154.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/155.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/155.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/156.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/156.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/157.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/157.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/158.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/158.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/159.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/159.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/16.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/16.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/160.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/160.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/161.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/161.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/162.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/162.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/163.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/163.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/164.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/164.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/165.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/165.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/166.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/166.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/167.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/167.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/168.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/168.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/169.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/169.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/17.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/17.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/170.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/170.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/171.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/171.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/172.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/172.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/173.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/173.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/174.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/174.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/178.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/178.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/179.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/179.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/18.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/18.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/180.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/180.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/181.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/181.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/182.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/182.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/183.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/183.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/184.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/184.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/185.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/185.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/186.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/186.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/187.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/187.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/188.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/188.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/189.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/189.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/19.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/19.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/190.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/190.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/191.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/191.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/192.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/192.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/193.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/193.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/194.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/194.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/195.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/195.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/196.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/196.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/197.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/197.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/198.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/198.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/199.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/199.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/20.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/20.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/200.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/201.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/201.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/202.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/202.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/203.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/203.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/204.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/204.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/205.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/205.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/206.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/206.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/207.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/207.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/208.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/208.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/209.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/209.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/21.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/21.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/210.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/210.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/211.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/211.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/212.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/212.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/213.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/213.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/214.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/214.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/215.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/215.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/22.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/22.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/23.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/23.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/24.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/24.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/25.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/25.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/26.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/26.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/27.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/27.JPG
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/28.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/28.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/29.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/29.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/30.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/30.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/31.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/31.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/32.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/32.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/33.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/33.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/34.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/34.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/35.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/35.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/36.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/36.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/37.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/37.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/38.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/38.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/39.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/39.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/40.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/40.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/41.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/41.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/42.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/42.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/43.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/43.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/44.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/44.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/45.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/45.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/46.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/46.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/47.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/47.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/48.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/48.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/49.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/49.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/50.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/50.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/51.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/51.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/52.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/52.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/53.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/53.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/54.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/54.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/55.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/55.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/56.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/56.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/57.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/57.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/58.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/58.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/59.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/59.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/60.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/60.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/61.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/61.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/62.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/62.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/63.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/63.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/64.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/64.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/65.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/65.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/66.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/66.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/67.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/67.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/68.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/68.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/69.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/69.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/70.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/70.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/71.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/71.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/72.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/72.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/73.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/73.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/74.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/74.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/75.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/75.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/76.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/76.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/77.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/77.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/78.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/78.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/79.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/79.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/80.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/80.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/81.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/81.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/82.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/82.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/83.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/83.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/84.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/84.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/85.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/85.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/86.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/86.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/87.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/87.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/88.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/88.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/89.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/89.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/90.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/90.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/91.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/91.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/92.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/92.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/93.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/93.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/94.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/94.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/95.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/95.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/96.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/96.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/97.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/97.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/98.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/98.jpg
--------------------------------------------------------------------------------
/2. 数据结构与算法之美 --- 王争专栏/Resources1/99.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/2. 数据结构与算法之美 --- 王争专栏/Resources1/99.jpg
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/.DS_Store
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part I 介绍/1 - 1 高效使用算法训练营.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part I 介绍/1 - 1 高效使用算法训练营.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part I 介绍/1 - 2 数据结构与算法总览.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part I 介绍/1 - 2 数据结构与算法总览.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part I 介绍/1 - 3 编程注意事项.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part I 介绍/1 - 3 编程注意事项.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/.DS_Store
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/1 时间复杂度和空间复杂度分析.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/1 时间复杂度和空间复杂度分析.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/10. 深度优先搜索 (DFS)& 广度优先搜索 (BFS).pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/10. 深度优先搜索 (DFS)& 广度优先搜索 (BFS).pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/11. 贪心算法 Greedy.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/11. 贪心算法 Greedy.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/12. 二分查找.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/12. 二分查找.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/13. 动态规划 Dynamic Programming.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/13. 动态规划 Dynamic Programming.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/14. 高级搜索 1 :剪枝 ( Cut - Off ) 的实现和特性.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/14. 高级搜索 1 :剪枝 ( Cut - Off ) 的实现和特性.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/15. 高级搜索 2 双向 BFS 的实现、特性和题解.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/15. 高级搜索 2 双向 BFS 的实现、特性和题解.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/16. 高级搜索 3 :启发式搜索 Heuristic Search.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/16. 高级搜索 3 :启发式搜索 Heuristic Search.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/17. 字典树 Trie 树.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/17. 字典树 Trie 树.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/18. 并查集 Disjoint Set.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/18. 并查集 Disjoint Set.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/19. 高级树、AVL 树 和 红黑树.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/19. 高级树、AVL 树 和 红黑树.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/2 数组、链表、跳表的基本实现和特性.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/2 数组、链表、跳表的基本实现和特性.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/20. 位运算.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/20. 位运算.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/21. 布隆过滤器 Bloom Filter.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/21. 布隆过滤器 Bloom Filter.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/22. LRU Cache.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/22. LRU Cache.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/23. 排序算法.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/23. 排序算法.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/3 栈( Stack)、队列 (Queue)的基本实现和特性.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/3 栈( Stack)、队列 (Queue)的基本实现和特性.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/4. 哈希表 (Hash Table)、映射 ( Map )、集合 (Set ) 的实现与特性.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/4. 哈希表 (Hash Table)、映射 ( Map )、集合 (Set ) 的实现与特性.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/5. 树( Tree )、二叉树 ( Binary Tree)、二叉搜索树 ( Binary Search Tree )的实现和特性.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/5. 树( Tree )、二叉树 ( Binary Tree)、二叉搜索树 ( Binary Search Tree )的实现和特性.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/6. 递归的实现、特性以及思维要点.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/6. 递归的实现、特性以及思维要点.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/8. 图 ( Graph ) 的实现和特性.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/8. 图 ( Graph ) 的实现和特性.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/9. 分治 (Divide & Conquer)、回溯.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part II 理论讲解/9. 分治 (Divide & Conquer)、回溯.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/.DS_Store
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/11. 盛水最多的容器.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/11. 盛水最多的容器.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/15. 三数之和 (高频老题).pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/15. 三数之和 (高频老题).pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/189. 旋转数组.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/189. 旋转数组.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/26. 删除有序数组中的重复项.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/26. 删除有序数组中的重复项.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/283. 移动零.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/283. 移动零.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/4. 两数之和.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/4. 两数之和.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/66. 加一.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/66. 加一.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/70. 爬楼梯.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/70. 爬楼梯.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/88.合并两个有序数组.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/1. 数组 Array/88.合并两个有序数组.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/141. 环形链表 I.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/141. 环形链表 I.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/142. 环形链表 II.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/142. 环形链表 II.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/206. 反转链表.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/206. 反转链表.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/21. 合并两个有序链表.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/21. 合并两个有序链表.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/24. 两两交换链表中的节点.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/24. 两两交换链表中的节点.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/25. K 个一组翻转链表.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/2.链表 LinkedList/25. K 个一组翻转链表.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/.DS_Store
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/155.最小栈.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/155.最小栈.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/20. 有效的括号.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/20. 有效的括号.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/42.接雨水 (重要).pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/42.接雨水 (重要).pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/84.柱状图中最大的矩形 (重要).pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/3. 栈 Stack/84.柱状图中最大的矩形 (重要).pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/4. 队列 Queue/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/4. 队列 Queue/.DS_Store
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/4. 队列 Queue/239.滑动窗口.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/4. 队列 Queue/239.滑动窗口.pdf
--------------------------------------------------------------------------------
/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/4. 队列 Queue/641.设计循环双端队列.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SongHaixiao/Algorithm/3ac9f413810f2e669832b817fb38e352fe566fa9/3. 算法训练营 --- 谭超专栏/Part III Leetcode 练习/4. 队列 Queue/641.设计循环双端队列.pdf
--------------------------------------------------------------------------------