diff --git a/notes/算法.md b/notes/算法.md index 48bf2bd0..18ba2521 100644 --- a/notes/算法.md +++ b/notes/算法.md @@ -125,7 +125,7 @@ public class ThreeSumFast { 例如对于暴力方法的 ThreeSum 算法,近似时间为 \~N3/6。进行如下实验:多次运行该算法,每次取的 N 值为前一次的两倍,统计每次执行的时间,并统计本次运行时间与前一次运行时间的比值,得到如下结果: -

+

可以看到,T(2N)/T(N)\~23,因此可以确定 T(N) \~ aN2logN。 @@ -476,7 +476,7 @@ private void exch(Comparable[] a, int i, int j){ 找到数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。 -

+

```java public class Selection { @@ -499,7 +499,7 @@ public class Selection { 插入排序从左到右进行,每次都将当前元素插入到左部已经排序的数组中,使得插入之后左部数组依然有序。 -

+

```java public class Insertion { @@ -530,7 +530,7 @@ public class Insertion { 希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。 -

+

```java public class Shell { @@ -558,7 +558,7 @@ public class Shell { 归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。 -

+

### 1. 归并方法 @@ -602,9 +602,9 @@ private static void sort(Comparable[] a, int lo, int hi) { } ``` -

+

-

+

因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(NlogN),因此该归并排序方法的时间复杂度也为 O(NlogN)。 @@ -614,7 +614,7 @@ private static void sort(Comparable[] a, int lo, int hi) { 先归并那些微型数组,然后成对归并得到的子数组。 -

+

```java public static void busort(Comparable[] a) { @@ -634,7 +634,7 @@ public static void busort(Comparable[] a) { 归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。 -

+

```java public class QuickSort { @@ -656,7 +656,7 @@ public class QuickSort { 取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程,就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。 -

+

```java private static int partition(Comparable[] a, int lo, int hi) { @@ -697,7 +697,7 @@ private static int partition(Comparable[] a, int lo, int hi) { 三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。 -

+

```java public class Quick3Way { @@ -727,7 +727,7 @@ public class Quick3Way { 堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。 -

+

```java public class MaxPQ { @@ -818,7 +818,7 @@ public Key delMax() { 无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。 -

+

```java public static void sort(Comparable[] a){ @@ -847,7 +847,7 @@ public static void sort(Comparable[] a){ ### 1. 排序算法的比较 -

+

快速排序时最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间增长数量级为 \~cNlogN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。 @@ -878,7 +878,7 @@ public static Comparable select(Comparable[] a, int k) { # 五、查找 -符号表示一种存储键值对的数据结构,支持两种操作:插入一个新的键值对;根据给定键得到值。 +符号表是一种存储键值对的数据结构,支持两种操作:插入一个新的键值对;根据给定键得到值。 ## 符号表 @@ -967,12 +967,11 @@ public class BinarySearchST, Value> { **二叉查找树** (BST)是一颗二叉树,并且每个节点的值都大于其左子树中的所有节点的值而小于右子树的所有节点的值。 -

+

BST 有一个重要性质,就是它的前序遍历结果递增排序。 -

- +

基本数据结构: @@ -1030,7 +1029,7 @@ private Value get(Node x, Key key) { 当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接使得该节点正确链接到树中。 -

+

```java public void put(Key key, Value val) { @@ -1051,7 +1050,7 @@ private Node put(Node x, Key key, Value val) { 二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。在最坏的情况下,树的高度为 N。 -

+

### 4. floor() @@ -1060,7 +1059,7 @@ floor(key):小于等于键的最大键 - 如果键小于根节点的键,那么 floor(key) 一定在左子树中; - 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就找到,否则根节点就是 floor(key)。 -

+

```java public Key floor(Key key) { @@ -1135,7 +1134,7 @@ public Node deleteMin(Node x) { - 如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可; - 否则,让右子树的最小节点替换该节点。 -

+

```java public void delete(Key key) { @@ -1209,7 +1208,7 @@ private void keys(Node x, Queue queue, Key lo, Key hi) { 2-3 查找树的查找和插入操作复杂度和插入顺序无关,在最坏的情况下查找和插入操作访问的节点必然不超过 logN 个,含有 10 亿个节点的 2-3 查找树最多只需要访问 30 个节点就能进行任意的查找和插入操作。 -

+

## 红黑二叉查找树 @@ -1224,7 +1223,7 @@ private void keys(Node x, Queue queue, Key lo, Key hi) { 画红黑树时可以将红链接画平。 -

+

```java public class RedBlackBST, Value> { @@ -1258,9 +1257,9 @@ public class RedBlackBST, Value> { 因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。 -

+

-

+

```java public Node rotateLeft(Node h) { @@ -1279,9 +1278,9 @@ public Node rotateLeft(Node h) { 进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。 -

+

-

+

```java public Node rotateRight(Node h) { @@ -1299,9 +1298,9 @@ public Node rotateRight(Node h) { 一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。 -

+

-

+

```java void flipColors(Node h){ @@ -1316,10 +1315,10 @@ void flipColors(Node h){ 先将一个节点按二叉查找树的方法插入到正确位置,然后再进行如下颜色操作: - 如果右子节点是红色的而左子节点是黑色的,进行左旋转; -- 如果左子节点是红色的且它的左子节点也是红色的,进行右旋转; +- 如果左子节点是红色的,而且左子节点的左子节点也是红色的,进行右旋转; - 如果左右子节点均为红色的,进行颜色转换。 -

+

```java public void put(Key key, Value val) { @@ -1349,27 +1348,29 @@ private Node put(Node x, Key key, Value val) { ### 5. 删除最小键 -如果最小键在一个 2- 节点中,那么删除该键会留下一个空链接,就破坏了平衡性,因此要确保最小键不在 2- 节点中。将 2- 节点转换成 3- 节点或者 4- 节点有两种方法,一种是向上层节点拿一个 key,一种是向兄弟节点拿一个 key。如果上层节点是 2- 节点,那么就没办法从上层节点拿 key 了,因此要保证删除路径上的所有节点都不是 2- 节点。在向下删除的过程中,保证以下情况之一发生: +如果最小键在一个 2- 节点中,那么删除该键会留下一个空链接,就破坏了平衡性,因此要确保最小键不在 2- 节点中。 + +将 2- 节点转换成 3- 节点或者 4- 节点有两种方法,一种是向上层节点拿一个 key,一种是向兄弟节点拿一个 key。如果上层节点是 2- 节点,那么就没办法从上层节点拿 key 了,因此要保证删除路径上的所有节点都不是 2- 节点。在向下删除的过程中,保证以下情况之一发生: 1. 如果当前节点的左子节点不是 2- 节点,完成; 2. 如果当前节点的左子节点是 2- 节点而它的兄弟节点不是 2- 节点,向兄弟节点拿一个 key 过来; 3. 如果当前节点的左子节点和它的兄弟节点都是 2- 节点,将左子节点、父节点中的最小键和最近的兄弟节点合并为一个 4- 节点。 -

+

最后得到一个含有最小键的 3- 节点或者 4- 节点,直接从中删除。然后再从头分解所有临时的 4- 节点。 -

+

### 6. 分析 -一颗大小为 N 的红黑树的高度不会超过 2logN。最坏的情况下是它所对应的 2-3 树中构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。 +一颗大小为 N 的红黑树的高度不会超过 2logN。最坏的情况下是它所对应的 2-3 树,构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。 红黑树大多数的操作所需要的时间都是对数级别的。 ## 散列表 -散列表类似于数组,可以把散列表的散列值看成数组的索引值。访问散列表和访问数组元素一样快速,它可以在常数时间内实现查找和插入的符号表。 +散列表类似于数组,可以把散列表的散列值看成数组的索引值。访问散列表和访问数组元素一样快速,它可以在常数时间内实现查找和插入操作。 由于无法通过散列值知道键的大小关系,因此散列表无法实现有序性操作。 @@ -1399,13 +1400,13 @@ for(int i = 0; i < s.length(); i++) hash = (R * hash + s.charAt(i)) % M; ``` -再比如,拥有多个成员的自定义类的哈希函数如下 +再比如,拥有多个成员的自定义类的哈希函数如下: ```java int hash = (((day * R + month) % M) * R + year) % M; ``` -R 的值不是很重要,通常取 31。 +R 通常取 31。 Java 中的 hashCode() 实现了 hash 函数,但是默认使用对象的内存地址值。在使用 hashCode() 函数时,应当结合除留余数法来使用。因为内存地址是 32 位整数,我们只需要 31 位的非负整数,因此应当屏蔽符号位之后再使用除留余数法。 @@ -1435,7 +1436,7 @@ public class Transaction{ 拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。 -

+

对于 N 个键,M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~N/M。 @@ -1443,7 +1444,7 @@ public class Transaction{ 线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 N(M>N)。 -

+

```java public class LinearProbingHashST { @@ -1472,7 +1473,7 @@ public class LinearProbingHashST { } ``` -#### 3.1 查找 +**(一)查找** ```java public Value get(Key key) { @@ -1485,7 +1486,7 @@ public Value get(Key key) { } ``` -#### 3.2 插入 +**(二)插入** ```java public void put(Key key, Value val) { @@ -1503,9 +1504,9 @@ public void put(Key key, Value val) { } ``` -#### 3.3 删除 +**(三)删除** -删除操作应当将右侧所有相邻的键值重新插入散列表中。 +删除操作应当将右侧所有相邻的键值对重新插入散列表中。 ```java public void delete(Key key) { @@ -1531,13 +1532,13 @@ public void delete(Key key) { } ``` -#### 3.4 调整数组大小 +**(四)调整数组大小** 线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。 α = N/M,把 α 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。 -

+

为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。 @@ -1562,13 +1563,13 @@ private void resize(int cap) { 虽然每次重新调整数组都需要重新把每个键值对插入到散列表,但是从摊还分析的角度来看,所需要的代价却是很小的。从下图可以看出,每次数组长度加倍后,累计平均值都会增加 1,因为表中每个键都需要重新计算散列值,但是随后平均值会下降。 -

+

## 应用 ### 1. 各种符号表实现的比较 -

+

应当优先考虑散列表,当需要有序性操作时使用红黑树。 diff --git a/pics/04662fa2-d19b-4de3-a829-50acb7af75d7.png b/pics/04662fa2-d19b-4de3-a829-50acb7af75d7.png new file mode 100644 index 00000000..18c04f3b Binary files /dev/null and b/pics/04662fa2-d19b-4de3-a829-50acb7af75d7.png differ diff --git a/pics/0f86eb11-3724-48de-9f27-499dfc7e96f1.png b/pics/0f86eb11-3724-48de-9f27-499dfc7e96f1.png new file mode 100644 index 00000000..defa899e Binary files /dev/null and b/pics/0f86eb11-3724-48de-9f27-499dfc7e96f1.png differ diff --git a/pics/12b458dd-526d-46e2-acca-cf3b501d580e.png b/pics/12b458dd-526d-46e2-acca-cf3b501d580e.png new file mode 100644 index 00000000..7f671c19 Binary files /dev/null and b/pics/12b458dd-526d-46e2-acca-cf3b501d580e.png differ diff --git a/pics/1c012d74-6b9d-4f25-a016-7ad4f1f1521898780376.png b/pics/1c012d74-6b9d-4f25-a016-7ad4f1f1521898780376.png new file mode 100644 index 00000000..0c959d2a Binary files /dev/null and b/pics/1c012d74-6b9d-4f25-a016-7ad4f1f1521898780376.png differ diff --git a/pics/40fe0669-6ac2-46f1-8666-a03785ad7412.png b/pics/40fe0669-6ac2-46f1-8666-a03785ad7412.png new file mode 100644 index 00000000..aa3a94d6 Binary files /dev/null and b/pics/40fe0669-6ac2-46f1-8666-a03785ad7412.png differ diff --git a/pics/5c0bb285-b917-446b-84a2-9810ee41521898714517.png b/pics/5c0bb285-b917-446b-84a2-9810ee41521898714517.png new file mode 100644 index 00000000..a064cc93 Binary files /dev/null and b/pics/5c0bb285-b917-446b-84a2-9810ee41521898714517.png differ diff --git a/pics/5e8493be-72cc-4a76-a68f-4852eacb2811.png b/pics/5e8493be-72cc-4a76-a68f-4852eacb2811.png new file mode 100644 index 00000000..5404e6d2 Binary files /dev/null and b/pics/5e8493be-72cc-4a76-a68f-4852eacb2811.png differ diff --git a/pics/691e8da5-fa65-4ee0-a4a9-bd9adba1521899256460.jpg b/pics/691e8da5-fa65-4ee0-a4a9-bd9adba1521899256460.jpg new file mode 100644 index 00000000..e65a4083 Binary files /dev/null and b/pics/691e8da5-fa65-4ee0-a4a9-bd9adba1521899256460.jpg differ diff --git a/pics/6e874d3e-9999-4d14-b6c9-fc7776c7ce30.png b/pics/6e874d3e-9999-4d14-b6c9-fc7776c7ce30.png new file mode 100644 index 00000000..6946dc22 Binary files /dev/null and b/pics/6e874d3e-9999-4d14-b6c9-fc7776c7ce30.png differ diff --git a/pics/78570a06-0781-4f9d-9093-70a87111521898809910.jpg b/pics/78570a06-0781-4f9d-9093-70a87111521898809910.jpg new file mode 100644 index 00000000..2aa55f8f Binary files /dev/null and b/pics/78570a06-0781-4f9d-9093-70a87111521898809910.jpg differ diff --git a/pics/7ca30d08-f7fa-4e39-b386-93c70631a900.png b/pics/7ca30d08-f7fa-4e39-b386-93c70631a900.png new file mode 100644 index 00000000..dd852349 Binary files /dev/null and b/pics/7ca30d08-f7fa-4e39-b386-93c70631a900.png differ diff --git a/pics/8459a13f-b0b8-4387-85a9-2525482bc25a.png b/pics/8459a13f-b0b8-4387-85a9-2525482bc25a.png new file mode 100644 index 00000000..3735f081 Binary files /dev/null and b/pics/8459a13f-b0b8-4387-85a9-2525482bc25a.png differ diff --git a/pics/88ae5997-50ab-4204-891f-88e212ba892e.png b/pics/88ae5997-50ab-4204-891f-88e212ba892e.png new file mode 100644 index 00000000..d17ca4c6 Binary files /dev/null and b/pics/88ae5997-50ab-4204-891f-88e212ba892e.png differ diff --git a/pics/9653b0c6-4232-4299-9f5c-79a616abafb8.png b/pics/9653b0c6-4232-4299-9f5c-79a616abafb8.png new file mode 100644 index 00000000..0a04997f Binary files /dev/null and b/pics/9653b0c6-4232-4299-9f5c-79a616abafb8.png differ diff --git a/pics/d3352e6a-483a-44f2-930e-28c1d677f9b9.png b/pics/d3352e6a-483a-44f2-930e-28c1d677f9b9.png new file mode 100644 index 00000000..0c3cf565 Binary files /dev/null and b/pics/d3352e6a-483a-44f2-930e-28c1d677f9b9.png differ diff --git a/pics/d3b06a52-cce1-4e05-aeff-1680a282b178.png b/pics/d3b06a52-cce1-4e05-aeff-1680a282b178.png new file mode 100644 index 00000000..7faf9bfb Binary files /dev/null and b/pics/d3b06a52-cce1-4e05-aeff-1680a282b178.png differ diff --git a/pics/d7c6c42d-a4d8-4b85-82fb-c21250bf5ca1.png b/pics/d7c6c42d-a4d8-4b85-82fb-c21250bf5ca1.png new file mode 100644 index 00000000..20ef25cf Binary files /dev/null and b/pics/d7c6c42d-a4d8-4b85-82fb-c21250bf5ca1.png differ diff --git a/pics/ed23a7cd-d55f-453d-b5bc-076d7b1c002e.jpg b/pics/ed23a7cd-d55f-453d-b5bc-076d7b1c002e.jpg new file mode 100644 index 00000000..30c81ddd Binary files /dev/null and b/pics/ed23a7cd-d55f-453d-b5bc-076d7b1c002e.jpg differ diff --git a/pics/f2af8957-d498-462f-9d11-f1c17876ba8e.png b/pics/f2af8957-d498-462f-9d11-f1c17876ba8e.png new file mode 100644 index 00000000..89c48683 Binary files /dev/null and b/pics/f2af8957-d498-462f-9d11-f1c17876ba8e.png differ diff --git a/pics/f47f9729-29ed-4c17-9924-76139342fac7.png b/pics/f47f9729-29ed-4c17-9924-76139342fac7.png new file mode 100644 index 00000000..d67672f7 Binary files /dev/null and b/pics/f47f9729-29ed-4c17-9924-76139342fac7.png differ