auto commit

This commit is contained in:
CyC2018 2018-03-13 10:45:42 +08:00
parent 97ca9ab198
commit b0f73fd3d1
7 changed files with 300 additions and 0 deletions

View File

@ -5,6 +5,15 @@
* [3. ThreeSum](#3-threesum) * [3. ThreeSum](#3-threesum)
* [4. 倍率实验](#4-倍率实验) * [4. 倍率实验](#4-倍率实验)
* [5. 注意事项](#5-注意事项) * [5. 注意事项](#5-注意事项)
* [栈和队列](#栈和队列)
* [1. 栈](#1-栈)
* [2. 队列](#2-队列)
* [union-find](#union-find)
* [1. quick-find 算法](#1-quick-find-算法)
* [2. quick-union 算法](#2-quick-union-算法)
* [3. 加权 quick-union 算法](#3-加权-quick-union-算法)
* [4. 路径压缩的加权 quick-union 算法](#4-路径压缩的加权-quick-union-算法)
* [5. 各种 union-find 算法的比较](#5-各种-union-find-算法的比较)
* [排序](#排序) * [排序](#排序)
* [1. 初级排序算法](#1-初级排序算法) * [1. 初级排序算法](#1-初级排序算法)
* [1.1 约定](#11-约定) * [1.1 约定](#11-约定)
@ -191,6 +200,297 @@ public class ThreeSumFast {
将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。 将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。
# 栈和队列
## 1. 栈
**数组实现**
```java
public class ResizeArrayStack<Item> implements Iterable<Item> {
private Item[] a = (Item[]) new Object[1];
private int N = 0;
public void push(Item item) {
if (N >= a.length) {
resize(2 * a.length);
}
a[N++] = item;
}
public Item pop() {
Item item = a[--N];
if (N <= a.length / 4) {
resize(a.length / 2);
}
return item;
}
// 调整数组大小,使得栈具有伸缩性
private void resize(int size) {
Item[] tmp = (Item[]) new Object[size];
for (int i = 0; i < N; i++) {
tmp[i] = a[i];
}
a = tmp;
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
@Override
public Iterator<Item> iterator() {
// 需要返回逆序遍历的迭代器
return new ReverseArrayIterator();
}
private class ReverseArrayIterator implements Iterator<Item> {
private int i = N;
@Override
public boolean hasNext() {
return i > 0;
}
@Override
public Item next() {
return a[--i];
}
}
}
```
上面实现使用了泛型Java 不能直接创建泛型数组,只能使用转型来创建。
```java
Item[] arr = (Item[]) new Object[N];
```
**链表实现**
需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素使就可以让前一个压入栈的元素称为栈顶元素。
```java
public class Stack<Item> {
private Node top = null;
private int N = 0;
private class Node {
Item item;
Node next;
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
public void push(Item item) {
Node newTop = new Node();
newTop.item = item;
newTop.next = top;
top = newTop;
N++;
}
public Item pop() {
Item item = top.item;
top = top.next;
N--;
return item;
}
}
```
## 2. 队列
下面是队列的链表实现,需要维护 first 和 last 节点指针,分别指向队首和队尾。
这里需要考虑让哪个指针指针链表头部节点,哪个指针指向链表尾部节点。因为出队列操作需要让队首元素的下一个元素成为队首,就需要容易获取下一个元素,而链表的头部节点的 next 指针指向下一个元素,因此让队首指针 first 指针链表的开头。
```java
public class Queue<Item> {
private Node first;
private Node last;
int N = 0;
private class Node{
Item item;
Node next;
}
public boolean isEmpty(){
return N == 0;
}
public int size(){
return N;
}
// 入队列
public void enqueue(Item item){
Node newNode = new Node();
newNode.item = item;
newNode.next = null;
if(isEmpty()){
last = newNode;
first = newNode;
} else{
last.next = newNode;
last = newNode;
}
N++;
}
// 出队列
public Item dequeue(){
Node node = first;
first = first.next;
N--;
return node.item;
}
}
```
# union-find
**概览**
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。
<div align="center"> <img src="../pics//365e5a18-cf63-4b80-bb12-da6b650653f7.jpg"/> </div><br>
**API**
<div align="center"> <img src="../pics//f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg"/> </div><br>
**基本数据结构**
```java
public class UF {
// 使用 id 数组来保存点的连通信息
private int[] id;
public UF(int N) {
id = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;
}
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
}
```
## 1. quick-find 算法
保证在同一连通分量的所有触点的 id 值相等。
这种方法可以快速取得一个触点的 id 值,并且判断两个触点是否连通,但是 union 的操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。
```java
public int find(int p) {
return id[p];
}
public void union(int p, int q) {
int pID = find(p);
int qID = find(q);
if (pID == qID) return;
for (int i = 0; i < id.length; i++) {
if (id[i] == pID) id[i] = qID;
}
}
```
## 2. quick-union 算法
在 union 时只将触点的 id 值指向另一个触点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,根节点需要指向自己。在进行查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id 值。
<div align="center"> <img src="../pics//81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg"/> </div><br>
```java
public int find(int p) {
while (p != id[p]) p = id[p];
return p;
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) return;
id[pRoot] = qRoot;
}
```
这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。
<div align="center"> <img src="../pics//70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg"/> </div><br>
## 3. 加权 quick-union 算法
为了解决 quick-union 的树通常会很高的问题,加权 quick-union 在 union 操作时会让较小的树连接较大的树上面。
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 lgN。
<div align="center"> <img src="../pics//b0d94736-e157-4886-aff2-c303735b0a24.jpg"/> </div><br>
```java
public class WeightedQuickUnionUF {
private int[] id;
// 保存节点的数量信息
private int[] sz;
public WeightedQuickUnionUF(int N) {
id = new int[N];
sz = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;
sz[i] = 1;
}
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public int find(int p) {
while (p != id[p]) p = id[p];
return p;
}
public void union(int p, int q) {
int i = find(p);
int j = find(q);
if (i == j) return;
if (sz[i] < sz[j]) {
id[i] = j;
sz[j] += sz[i];
} else {
id[j] = i;
sz[i] += sz[j];
}
}
}
```
## 4. 路径压缩的加权 quick-union 算法
在检查节点的同时将它们直接链接到根节点,只需要在 find 中添加一个循环即可。
## 5. 各种 union-find 算法的比较
<div align="center"> <img src="../pics//2b6037b2-ec69-4235-ad0e-886fa320d645.jpg"/> </div><br>
# 排序 # 排序
## 1. 初级排序算法 ## 1. 初级排序算法

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB