auto commit

This commit is contained in:
CyC2018
2018-03-14 20:44:44 +08:00
parent 9f986651b0
commit 55628c8a42
21 changed files with 317 additions and 315 deletions

View File

@ -95,7 +95,7 @@
<div align="center"> <img src="https://latex.codecogs.com/gif.latex?T(N)=aN^3"/> 转换为 <img src="https://latex.codecogs.com/gif.latex?lg(T(N))=3lgN+lga"/> </div><br>
<div align="center"> <img src="index_files/5510045a-8f32-487f-a756-463e51a6dab0.png"/> </div><br>
<div align="center"> <img src="../pics//5510045a-8f32-487f-a756-463e51a6dab0.png"/> </div><br>
## 2. 数学模型
@ -103,13 +103,13 @@
使用 \~f(N) 来表示所有随着 N 的增大除以 f(N) 的结果趋近于 1 的函数 , 例如 N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 \~ N<sup>3</sup>/6。
<div align="center"> <img src="index_files/ca3a793e-06e5-4ff3-b28e-a9c20540d164.png"/> </div><br>
<div align="center"> <img src="../pics//ca3a793e-06e5-4ff3-b28e-a9c20540d164.png"/> </div><br>
**增长数量级**
增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 N<sup>3</sup> 与它是否用 Java 实现,是否运行于特定计算机上无关。
<div align="center"> <img src="index_files/1ea4dc9a-c4dd-46b5-bb11-49f98d57ded1.png"/> </div><br>
<div align="center"> <img src="../pics//1ea4dc9a-c4dd-46b5-bb11-49f98d57ded1.png"/> </div><br>
**内循环**
@ -174,7 +174,7 @@ public class ThreeSumFast {
如果 T(N) \~ aN<sup>b</sup>lgN那么 T(2N)/T(N) \~ 2<sup>b</sup>,例如对于暴力方法的 ThreeSum 算法,近似时间为 \~N<sup>3</sup>/6对它进行倍率实验得到如下结果
<div align="center"> <img src="index_files/6f5ed46f-86d7-4852-a34f-c1cf1b6343a0.png"/> </div><br>
<div align="center"> <img src="../pics//6f5ed46f-86d7-4852-a34f-c1cf1b6343a0.png"/> </div><br>
可见 T(2N)/T(N)\~2<sup>3</sup>,也就是 b 为 3。
@ -365,11 +365,11 @@ public class Queue<Item> {
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。
<div align="center"> <img src="index_files/365e5a18-cf63-4b80-bb12-da6b650653f7.jpg"/> </div><br>
<div align="center"> <img src="../pics//365e5a18-cf63-4b80-bb12-da6b650653f7.jpg"/> </div><br>
**API**
<div align="center"> <img src="index_files/f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg"/> </div><br>
<div align="center"> <img src="../pics//f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg"/> </div><br>
**基本数据结构**
@ -416,7 +416,7 @@ public class UF {
在 union 时只将触点的 id 值指向另一个触点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,根节点需要指向自己。在进行查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id 值。
<div align="center"> <img src="index_files/81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg"/> </div><br>
<div align="center"> <img src="../pics//81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg"/> </div><br>
```java
public int find(int p) {
@ -434,7 +434,7 @@ public class UF {
这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。
<div align="center"> <img src="index_files/70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg"/> </div><br>
<div align="center"> <img src="../pics//70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg"/> </div><br>
## 3. 加权 quick-union 算法
@ -442,7 +442,7 @@ public class UF {
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 lgN。
<div align="center"> <img src="index_files/b0d94736-e157-4886-aff2-c303735b0a24.jpg"/> </div><br>
<div align="center"> <img src="../pics//b0d94736-e157-4886-aff2-c303735b0a24.jpg"/> </div><br>
```java
public class WeightedQuickUnionUF {
@ -489,7 +489,7 @@ public class WeightedQuickUnionUF {
## 5. 各种 union-find 算法的比较
<div align="center"> <img src="index_files/2b6037b2-ec69-4235-ad0e-886fa320d645.jpg"/> </div><br>
<div align="center"> <img src="../pics//2b6037b2-ec69-4235-ad0e-886fa320d645.jpg"/> </div><br>
# 排序
@ -519,7 +519,7 @@ private void exch(Comparable[] a, int i, int j){
找到数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
<div align="center"> <img src="index_files/222768a7-914f-4d64-b874-d98f3b926fb6.jpg"/> </div><br>
<div align="center"> <img src="../pics//222768a7-914f-4d64-b874-d98f3b926fb6.jpg"/> </div><br>
```java
public class Selection {
@ -542,7 +542,7 @@ public class Selection {
入排序从左到右进行,每次都将当前元素插入到左部已经排序的数组中,使得插入之后左部数组依然有序。
<div align="center"> <img src="index_files/065c3bbb-3ea0-4dbf-8f26-01d0e0ba7db7.png"/> </div><br>
<div align="center"> <img src="../pics//065c3bbb-3ea0-4dbf-8f26-01d0e0ba7db7.png"/> </div><br>
```java
public class Insertion {
@ -573,7 +573,7 @@ public class Insertion {
希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h最后令 h=1就可以使得整个数组是有序的。
<div align="center"> <img src="index_files/8320bad6-3f91-4a15-8e3d-68e8f39649b5.png"/> </div><br>
<div align="center"> <img src="../pics//8320bad6-3f91-4a15-8e3d-68e8f39649b5.png"/> </div><br>
```java
public class Shell {
@ -601,7 +601,7 @@ public class Shell {
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。
<div align="center"> <img src="index_files/dcf265ad-fe35-424d-b4b7-d149cdf239f4.png"/> </div><br>
<div align="center"> <img src="../pics//dcf265ad-fe35-424d-b4b7-d149cdf239f4.png"/> </div><br>
### 2.1 归并方法
@ -645,9 +645,9 @@ private static void sort(Comparable[] a, int lo, int hi) {
}
```
<div align="center"> <img src="index_files/6468a541-3a9a-4008-82b6-03a0fe941d2a.png"/> </div><br>
<div align="center"> <img src="../pics//6468a541-3a9a-4008-82b6-03a0fe941d2a.png"/> </div><br>
<div align="center"> <img src="index_files/c7665f73-c52f-4ce4-aed3-592bbd76265b.png"/> </div><br>
<div align="center"> <img src="../pics//c7665f73-c52f-4ce4-aed3-592bbd76265b.png"/> </div><br>
因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(Nlg<sub>N</sub>),因此该归并排序方法的时间复杂度也为 O(Nlg<sub>N</sub>)。
@ -657,7 +657,7 @@ private static void sort(Comparable[] a, int lo, int hi) {
先归并那些微型数组,然后成对归并得到的子数组。
<div align="center"> <img src="index_files/c7b9b4c8-83d1-4eb0-8408-ea6576a9ed90.png"/> </div><br>
<div align="center"> <img src="../pics//c7b9b4c8-83d1-4eb0-8408-ea6576a9ed90.png"/> </div><br>
```java
public static void busort(Comparable[] a) {
@ -677,7 +677,7 @@ public static void busort(Comparable[] a) {
归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
<div align="center"> <img src="index_files/61b4832d-71f3-413c-84b6-237e219b9fdc.png"/> </div><br>
<div align="center"> <img src="../pics//61b4832d-71f3-413c-84b6-237e219b9fdc.png"/> </div><br>
```java
public class QuickSort {
@ -699,7 +699,7 @@ public class QuickSort {
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程,就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。
<div align="center"> <img src="index_files/e198c201-f386-4491-8ad6-f7e433bf992d.png"/> </div><br>
<div align="center"> <img src="../pics//e198c201-f386-4491-8ad6-f7e433bf992d.png"/> </div><br>
```java
private static int partition(Comparable[] a, int lo, int hi) {
@ -740,7 +740,7 @@ private static int partition(Comparable[] a, int lo, int hi) {
三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。
<div align="center"> <img src="index_files/9d2226dc-c4a3-40ec-9b3e-a46bf86af499.png"/> </div><br>
<div align="center"> <img src="../pics//9d2226dc-c4a3-40ec-9b3e-a46bf86af499.png"/> </div><br>
```java
public class Quick3Way {
@ -770,7 +770,7 @@ public class Quick3Way {
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。
<div align="center"> <img src="index_files/a9b6c1db-0f4a-4e91-8ac8-6b19bd106b51.png"/> </div><br>
<div align="center"> <img src="../pics//a9b6c1db-0f4a-4e91-8ac8-6b19bd106b51.png"/> </div><br>
```java
public class MaxPQ<Key extends Comparable<Key> {
@ -861,7 +861,7 @@ public Key delMax() {
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
<div align="center"> <img src="index_files/a2670745-a7b1-497b-90a4-dbddc4e2006d.jpg"/> </div><br>
<div align="center"> <img src="../pics//a2670745-a7b1-497b-90a4-dbddc4e2006d.jpg"/> </div><br>
```java
public static void sort(Comparable[] a){
@ -890,7 +890,7 @@ public static void sort(Comparable[] a){
### 5.1 排序算法的比较
<div align="center"> <img src="index_files/be53c00b-2534-4dc6-ad03-c55995c47db9.jpg"/> </div><br>
<div align="center"> <img src="../pics//be53c00b-2534-4dc6-ad03-c55995c47db9.jpg"/> </div><br>
快速排序时最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间增长数量级为 \~cNlgN这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
@ -927,11 +927,11 @@ public static Comparable select(Comparable[] a, int k) {
### 1.1 无序符号表
<div align="center"> <img src="index_files/b69d7184-ab62-4957-ba29-fb4fa25f9b65.jpg"/> </div><br>
<div align="center"> <img src="../pics//b69d7184-ab62-4957-ba29-fb4fa25f9b65.jpg"/> </div><br>
### 1.2 有序符号表
<div align="center"> <img src="index_files/ba6ae411-82da-4d86-a434-6776d1731e8e.jpg"/> </div><br>
<div align="center"> <img src="../pics//ba6ae411-82da-4d86-a434-6776d1731e8e.jpg"/> </div><br>
有序符号表的键需要实现 Comparable 接口。
@ -1010,7 +1010,7 @@ public class BinarySearchST<Key extends Comparable<Key>, Value> {
**二叉查找树** BST是一颗二叉树并且每个节点的键都大于其左子树中的任意节点的键而小于右子树的任意节点的键。
<div align="center"> <img src="index_files/25226bb2-92cc-40cb-9e7f-c44e79fbb64a.jpg"/> </div><br>
<div align="center"> <img src="../pics//25226bb2-92cc-40cb-9e7f-c44e79fbb64a.jpg"/> </div><br>
二叉查找树的查找操作每次迭代都会让区间减少一半,和二分查找类似。
@ -1083,7 +1083,7 @@ private Node put(Node x, Key key, Value val) {
二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 lgN。在最坏的情况下树的高度为 N。
<div align="center"> <img src="index_files/73a3983d-dd18-4373-897e-64b706a7e370.jpg"/> </div><br>
<div align="center"> <img src="../pics//73a3983d-dd18-4373-897e-64b706a7e370.jpg"/> </div><br>
复杂度:查找和插入操作都为对数级别。
@ -1139,7 +1139,7 @@ private Node min(Node x) {
令指向最小节点的链接指向最小节点的右子树。
<div align="center"> <img src="index_files/6e2cb20a-8d2a-46fe-9ac7-68a2126b7bd5.jpg"/> </div><br>
<div align="center"> <img src="../pics//6e2cb20a-8d2a-46fe-9ac7-68a2126b7bd5.jpg"/> </div><br>
```java
public void deleteMin() {
@ -1157,7 +1157,7 @@ public Node deleteMin(Node x) {
如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可;否则,让右子树的最小节点替换该节点。
<div align="center"> <img src="index_files/b488282d-bfe0-464f-9e91-1f5b83a975bd.jpg"/> </div><br>
<div align="center"> <img src="../pics//b488282d-bfe0-464f-9e91-1f5b83a975bd.jpg"/> </div><br>
```java
public void delete(Key key) {
@ -1209,7 +1209,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
### 3.1 2-3 查找树
<div align="center"> <img src="index_files/2548f2ec-7b00-4ec7-b286-20fc3022e084.jpg"/> </div><br>
<div align="center"> <img src="../pics//2548f2ec-7b00-4ec7-b286-20fc3022e084.jpg"/> </div><br>
一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
@ -1217,7 +1217,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
当插入之后产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中。如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。
<div align="center"> <img src="index_files/912174d8-0786-4222-b7ef-a611d36e5db9.jpg"/> </div><br>
<div align="center"> <img src="../pics//912174d8-0786-4222-b7ef-a611d36e5db9.jpg"/> </div><br>
#### 3.1.2 性质
@ -1229,7 +1229,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。
<div align="center"> <img src="index_files/7080a928-06ba-4e10-9792-b8dd190dc8e2.jpg"/> </div><br>
<div align="center"> <img src="../pics//7080a928-06ba-4e10-9792-b8dd190dc8e2.jpg"/> </div><br>
红黑树具有以下性质:
@ -1238,7 +1238,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
画红黑树时可以将红链接画平。
<div align="center"> <img src="index_files/62077f5d-a06d-4129-9b43-78715b82cb03.png"/> </div><br>
<div align="center"> <img src="../pics//62077f5d-a06d-4129-9b43-78715b82cb03.png"/> </div><br>
```java
public class RedBlackBST<Key extends Comparable<Key>, Value> {
@ -1272,9 +1272,9 @@ public class RedBlackBST<Key extends Comparable<Key>, Value> {
因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。
<div align="center"> <img src="index_files/33a4e822-2dd0-481e-ac89-7f6161034402.jpg"/> </div><br>
<div align="center"> <img src="../pics//33a4e822-2dd0-481e-ac89-7f6161034402.jpg"/> </div><br>
<div align="center"> <img src="index_files/5e0cef33-4087-4f21-a428-16d5fddda671.jpg"/> </div><br>
<div align="center"> <img src="../pics//5e0cef33-4087-4f21-a428-16d5fddda671.jpg"/> </div><br>
```java
public Node rotateLeft(Node h) {
@ -1293,9 +1293,9 @@ public Node rotateLeft(Node h) {
进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。
<div align="center"> <img src="index_files/dfd078b2-aa4f-4c50-8319-232922d822b8.jpg"/> </div><br>
<div align="center"> <img src="../pics//dfd078b2-aa4f-4c50-8319-232922d822b8.jpg"/> </div><br>
<div align="center"> <img src="index_files/3f8d8c9d-a9a9-4d7a-813c-2de05ee5a97e.jpg"/> </div><br>
<div align="center"> <img src="../pics//3f8d8c9d-a9a9-4d7a-813c-2de05ee5a97e.jpg"/> </div><br>
```java
public Node rotateRight(Node h) {
@ -1313,9 +1313,9 @@ public Node rotateRight(Node h) {
一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。
<div align="center"> <img src="index_files/de7c5a31-55f5-4e9d-92ec-4ed5b2ec3828.jpg"/> </div><br>
<div align="center"> <img src="../pics//de7c5a31-55f5-4e9d-92ec-4ed5b2ec3828.jpg"/> </div><br>
<div align="center"> <img src="index_files/e5ad625e-729d-4a8d-923a-7c3df5773e1c.jpg"/> </div><br>
<div align="center"> <img src="../pics//e5ad625e-729d-4a8d-923a-7c3df5773e1c.jpg"/> </div><br>
```java
void flipColors(Node h){
@ -1333,7 +1333,7 @@ void flipColors(Node h){
- 如果左子节点是红色的且它的左子节点也是红色的,进行右旋转;
- 如果左右子节点均为红色的,进行颜色转换。
<div align="center"> <img src="index_files/40639782-5df2-4e96-a4f3-f9dd664d0ca1.jpg"/> </div><br>
<div align="center"> <img src="../pics//40639782-5df2-4e96-a4f3-f9dd664d0ca1.jpg"/> </div><br>
```java
public void put(Key key, Value val) {
@ -1369,11 +1369,11 @@ private Node put(Node x, Key key, Value val) {
2. 如果当前节点的左子节点是 2- 节点而它的兄弟节点不是 2- 节点,向兄弟节点拿一个 key 过来;
3. 如果当前节点的左子节点和它的兄弟节点都是 2- 节点,将左子节点、父节点中的最小键和最近的兄弟节点合并为一个 4- 节点。
<div align="center"> <img src="index_files/b001fa64-307c-49af-b4b2-2043fc26154e.png"/> </div><br>
<div align="center"> <img src="../pics//b001fa64-307c-49af-b4b2-2043fc26154e.png"/> </div><br>
最后得到一个含有最小键的 3- 节点或者 4- 节点,直接从中删除。然后再从头分解所有临时的 4- 节点。
<div align="center"> <img src="index_files/70b66757-755c-4e17-a7b7-5ce808023643.png"/> </div><br>
<div align="center"> <img src="../pics//70b66757-755c-4e17-a7b7-5ce808023643.png"/> </div><br>
#### 3.2.6 分析
@ -1449,7 +1449,7 @@ public class Transaction{
拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
<div align="center"> <img src="index_files/540133af-aaaf-4208-8f7f-33cb89ac9621.png"/> </div><br>
<div align="center"> <img src="../pics//540133af-aaaf-4208-8f7f-33cb89ac9621.png"/> </div><br>
对于 N 个键M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M因此未命中的查找和插入操作所需要的比较次数为 \~N/M。
@ -1457,7 +1457,7 @@ public class Transaction{
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 NM>N)。
<div align="center"> <img src="index_files/2b3410f1-9559-4dd1-bc3d-e3e572247be2.png"/> </div><br>
<div align="center"> <img src="../pics//2b3410f1-9559-4dd1-bc3d-e3e572247be2.png"/> </div><br>
```java
public class LinearProbingHashST<Key, Value> {
@ -1551,7 +1551,7 @@ public void delete(Key key) {
α = N/Mα 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。
<div align="center"> <img src="index_files/0ddebc5c-7c24-46b1-98db-4fa5e54db16b.png"/> </div><br>
<div align="center"> <img src="../pics//0ddebc5c-7c24-46b1-98db-4fa5e54db16b.png"/> </div><br>
为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。
@ -1576,13 +1576,13 @@ private void resize(int cap) {
虽然每次重新调整数组都需要重新把每个键值对插入到散列表,但是从摊还分析的角度来看,所需要的代价却是很小的。从下图可以看出,每次数组长度加倍后,累计平均值都会增加 1因为表中每个键都需要重新计算散列值但是随后平均值会下降。
<div align="center"> <img src="index_files/01658047-0d86-4a7a-a8ca-7ea20fa1fdde.png"/> </div><br>
<div align="center"> <img src="../pics//01658047-0d86-4a7a-a8ca-7ea20fa1fdde.png"/> </div><br>
## 5. 应用
### 5.1 各种符号表实现的比较
<div align="center"> <img src="index_files/9ee83c8c-1165-476c-85a6-e6e434e5307a.jpg"/> </div><br>
<div align="center"> <img src="../pics//9ee83c8c-1165-476c-85a6-e6e434e5307a.jpg"/> </div><br>
应当优先考虑散列表,当需要有序性操作时使用红黑树。