@@ -82,7 +82,7 @@ N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 的增长数量级为 O(N<sup>3</sup>)。增
8282
8383### 5. 均摊分析
8484
85- 将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数 。
85+ 将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的次数为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素的操作次数,其余的都是调整数组大小时进行复制需要的访问数组次数),均摊后访问数组的平均次数为常数 。
8686
8787## ThreeSum
8888
@@ -266,7 +266,7 @@ public class StopWatch {
266266
267267待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。
268268
269- 研究排序算法的成本模型时,计算的是比较和交换的次数 。
269+ 研究排序算法的成本模型时,统计的是比较和交换的次数 。
270270
271271使用辅助函数 less() 和 swap() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。
272272
@@ -565,7 +565,7 @@ private int partition(T[] nums, int l, int h) {
565565
566566快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。
567567
568- 快速排序最好的情况下是每次都正好能将数组对半分 ,这样递归调用次数才是最少的。这种情况下比较次数为 C<sub >N</sub >=2C<sub >N/2</sub >+N,复杂度为 O(NlogN)。
568+ 快速排序最好的情况下是每次都正好将数组对半分 ,这样递归调用次数才是最少的。这种情况下比较次数为 C<sub >N</sub >=2C<sub >N/2</sub >+N,复杂度为 O(NlogN)。
569569
570570最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N<sup >2</sup >/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。
571571
@@ -577,13 +577,13 @@ private int partition(T[] nums, int l, int h) {
577577
578578#### 4.2 三数取中
579579
580- 最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好 。
580+ 最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。一种折中方法是取 3 个元素,并将大小居中的元素作为切分元素 。
581581
582582#### 4.3 三向切分
583583
584584对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。
585585
586- 三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序 。
586+ 三向切分快速排序对于有大量重复元素的随机数组可以在线性时间内完成排序 。
587587
588588``` java
589589public class ThreeWayQuickSort <T extends Comparable<T > > extends QuickSort<T > {
@@ -643,7 +643,7 @@ public T select(T[] nums, int k) {
643643
644644### 1. 堆
645645
646- 堆的某个节点的值总是大于等于子节点的值 ,并且堆是一颗完全二叉树。
646+ 堆中某个节点的值总是大于等于其子节点的值 ,并且堆是一颗完全二叉树。
647647
648648堆可以用数组来表示,这是因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。
649649
@@ -798,7 +798,7 @@ public class HeapSort<T extends Comparable<T>> extends Sort<T> {
798798
799799堆排序是一种原地排序,没有利用额外的空间。
800800
801- 现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较 。
801+ 现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较和交换 。
802802
803803## 小结
804804
@@ -809,13 +809,15 @@ public class HeapSort<T extends Comparable<T>> extends Sort<T> {
809809| 选择排序 | × | N<sup >2</sup > | 1 | |
810810| 冒泡排序 | √ | N<sup >2</sup > | 1 | |
811811| 插入排序 | √ | N \~ N<sup >2</sup > | 1 | 时间复杂度和初始顺序有关 |
812- | 希尔排序 | × | N 的若干倍乘于递增序列的长度 | 1 | |
812+ | 希尔排序 | × | N 的若干倍乘于递增序列的长度 | 1 | 改进版插入排序 |
813813| 快速排序 | × | NlogN | logN | |
814814| 三向切分快速排序 | × | N \~ NlogN | logN | 适用于有大量重复主键|
815815| 归并排序 | √ | NlogN | N | |
816- | 堆排序 | × | NlogN | 1 | | |
816+ | 堆排序 | × | NlogN | 1 | 无法利用局部性原理 |
817817
818- 快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 \~ cNlogN,这里的 c 比其它线性对数级别的排序算法都要小。使用三向切分快速排序,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
818+ 快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 \~ cNlogN,这里的 c 比其它线性对数级别的排序算法都要小。
819+
820+ 使用三向切分快速排序,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
819821
820822### 2. Java 的排序算法实现
821823
@@ -831,7 +833,7 @@ Java 主要排序方法为 java.util.Arrays.sort(),对于原始数据类型使
831833| :---: | :---: |
832834| UF(int N) | 构造一个大小为 N 的并查集 |
833835| void union(int p, int q) | 连接 p 和 q 节点 |
834- | int find(int p) | 查找 p 所在的连通分量 |
836+ | int find(int p) | 查找 p 所在的连通分量编号 |
835837| boolean connected(int p, int q) | 判断 p 和 q 节点是否连通 |
836838
837839``` java
@@ -858,7 +860,7 @@ public abstract class UF {
858860
859861## Quick Find
860862
861- 可以快速进行 find 操作,即可以快速判断两个节点是否连通 。
863+ 可以快速进行 find 操作,也就是可以快速判断两个节点是否连通 。
862864
863865需要保证同一连通分量的所有节点的 id 值相等。
864866
@@ -937,15 +939,15 @@ public class QuickUnionUF extends UF {
937939
938940这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为节点的数目。
939941
940- <div align =" center " > <img src =" pics/bfbb11e2-d208-4efa-b97b-24cd40467cd8.png " width =" 150 " /> </div ><br >
942+ <div align =" center " > <img src =" pics/bfbb11e2-d208-4efa-b97b-24cd40467cd8.png " width =" 130 " /> </div ><br >
941943
942944## 加权 Quick Union
943945
944946为了解决 quick-union 的树通常会很高的问题,加权 quick-union 在 union 操作时会让较小的树连接较大的树上面。
945947
946948理论研究证明,加权 quick-union 算法构造的树深度最多不超过 logN。
947949
948- <div align =" center " > <img src =" pics/a4c17d43-fa5e-4935-b74e-147e7f7e782c.png " width =" 200 " /> </div ><br >
950+ <div align =" center " > <img src =" pics/a4c17d43-fa5e-4935-b74e-147e7f7e782c.png " width =" 170 " /> </div ><br >
949951
950952``` java
951953public class WeightedQuickUnionUF extends UF {
@@ -1210,8 +1212,6 @@ public class ListStack<Item> implements MyStack<Item> {
12101212
12111213## 队列
12121214
1213- First-In-First-Out
1214-
12151215下面是队列的链表实现,需要维护 first 和 last 节点指针,分别指向队首和队尾。
12161216
12171217这里需要考虑 first 和 last 指针哪个作为链表的开头。因为出队列操作需要让队首元素的下一个元素成为队首,所以需要容易获取下一个元素,而链表的头部节点的 next 指针指向下一个元素,因此可以让 first 指针链表的开头。
@@ -2185,10 +2185,10 @@ private void resize(int cap) {
21852185
21862186| 算法 | 插入 | 查找 | 是否有序 |
21872187| :---: | :---: | :---: | :---: |
2188- | 二分查找实现的有序表 | N | logN | yes |
2188+ | 链表实现的无序符号表 | N | N | yes |
2189+ | 二分查找实现的有序符号表 | N | logN | yes |
21892190| 二叉查找树 | logN | logN | yes |
21902191| 2-3 查找树 | logN | logN | yes |
2191- | 链表实现的有序表 | N | N | no |
21922192| 拉链法实现的散列表 | N/M | N/M | no |
21932193| 线性探测法实现的散列表 | 1 | 1 | no |
21942194
@@ -2233,6 +2233,8 @@ public class SparseVector {
22332233
22342234<div align =" center " > <img src =" pics/54f1e052-0596-4b5e-833c-e80d75bf3f9b.png " width =" 300 " /> </div ><br >
22352235
2236+ 有三个柱子,分别为 from、buffer、to。需要将 from 上的圆盘全部移动到 to 上,并且要保证小圆盘始终在大圆盘上。
2237+
22362238这是一个经典的递归问题,分为三步求解:
22372239
22382240① 将 n-1 个圆盘从 from -> buffer
0 commit comments