diff --git a/notes/算法.md b/notes/算法.md index 5687cf28..eb9acd1d 100644 --- a/notes/算法.md +++ b/notes/算法.md @@ -5,6 +5,15 @@ * [3. ThreeSum](#3-threesum) * [4. 倍率实验](#4-倍率实验) * [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 约定](#11-约定) @@ -191,6 +200,297 @@ public class ThreeSumFast { 将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。 +# 栈和队列 + +## 1. 栈 + +**数组实现** + +```java +public class ResizeArrayStack implements Iterable { + 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 iterator() { + // 需要返回逆序遍历的迭代器 + return new ReverseArrayIterator(); + } + + private class ReverseArrayIterator implements Iterator { + 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 { + + 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 { + 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 + +**概览** + +用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。 + +

+ +**API** + +

+ +**基本数据结构** + +```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 值。 + +

+ +```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 操作和树高成正比,最坏的情况下树的高度为触点的数目。 + +

+ +## 3. 加权 quick-union 算法 + +为了解决 quick-union 的树通常会很高的问题,加权 quick-union 在 union 操作时会让较小的树连接较大的树上面。 + +理论研究证明,加权 quick-union 算法构造的树深度最多不超过 lgN。 + +

+ +```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 算法的比较 + +

+ # 排序 ## 1. 初级排序算法 diff --git a/pics/2b6037b2-ec69-4235-ad0e-886fa320d645.jpg b/pics/2b6037b2-ec69-4235-ad0e-886fa320d645.jpg new file mode 100644 index 00000000..340fc750 Binary files /dev/null and b/pics/2b6037b2-ec69-4235-ad0e-886fa320d645.jpg differ diff --git a/pics/365e5a18-cf63-4b80-bb12-da6b650653f7.jpg b/pics/365e5a18-cf63-4b80-bb12-da6b650653f7.jpg new file mode 100644 index 00000000..76afb8a4 Binary files /dev/null and b/pics/365e5a18-cf63-4b80-bb12-da6b650653f7.jpg differ diff --git a/pics/70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg b/pics/70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg new file mode 100644 index 00000000..1057068a Binary files /dev/null and b/pics/70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg differ diff --git a/pics/81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg b/pics/81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg new file mode 100644 index 00000000..6acdc930 Binary files /dev/null and b/pics/81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg differ diff --git a/pics/b0d94736-e157-4886-aff2-c303735b0a24.jpg b/pics/b0d94736-e157-4886-aff2-c303735b0a24.jpg new file mode 100644 index 00000000..96ec26a7 Binary files /dev/null and b/pics/b0d94736-e157-4886-aff2-c303735b0a24.jpg differ diff --git a/pics/f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg b/pics/f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg new file mode 100644 index 00000000..8fc059bf Binary files /dev/null and b/pics/f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg differ