auto commit
This commit is contained in:
parent
97ca9ab198
commit
b0f73fd3d1
300
notes/算法.md
300
notes/算法.md
|
@ -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-4(N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。
|
将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。
|
||||||
|
|
||||||
|
# 栈和队列
|
||||||
|
|
||||||
|
## 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. 初级排序算法
|
||||||
|
|
BIN
pics/2b6037b2-ec69-4235-ad0e-886fa320d645.jpg
Normal file
BIN
pics/2b6037b2-ec69-4235-ad0e-886fa320d645.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
pics/365e5a18-cf63-4b80-bb12-da6b650653f7.jpg
Normal file
BIN
pics/365e5a18-cf63-4b80-bb12-da6b650653f7.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
pics/70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg
Normal file
BIN
pics/70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
pics/81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg
Normal file
BIN
pics/81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
BIN
pics/b0d94736-e157-4886-aff2-c303735b0a24.jpg
Normal file
BIN
pics/b0d94736-e157-4886-aff2-c303735b0a24.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
pics/f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg
Normal file
BIN
pics/f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Loading…
Reference in New Issue
Block a user