1.5.1 使用quick-find算法处理序列9-0 3-4 5-8 7-2 2-1 5-7 0-3 4-2。对于输入的每一对整数,给出id[]数组的内容和访问数组的次数。
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
public class quick_find_UF {
private int[] id; //触点
private int count; //连通分量
private int visitnum; //数组访问次数
public quick_find_UF(int n) {
visitnum = 0;
count = n;
id = new int[n];
for (int i = 0; i < n; i++) {
id[i] = i;
visitnum++;
}
}
public int getCount() {
return count;
}
public int getVisitnum() {
return visitnum;
}
public int find(int p) {
visitnum++;
return id[p];
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public void union(int p, int q) {
int a = find(p);
int b = find(q);
if(a == b)
return;
for(int i = 0; i < id.length; i++, visitnum++) {
if(id[i] == a)
id[i] = b;
}
count--;
System.out.println(p + "->" + q);
}
public static void main(String[] args) {
int n = StdIn.readInt();
int m = StdIn.readInt();
quick_find_UF uf = new quick_find_UF(n);
while (m-- > 0) {
int p = StdIn.readInt();
int q = StdIn.readInt();
if(uf.connected(p, q))
continue;
uf.union(p, q);
}
StdOut.println("共有" + uf.getCount() + "个连通分量");
StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
}
}
1.5.2 使用quick-union算法完成1.5.1。
public int find(int p) {
while (p != id[p]) {
p = id[p];
visitnum += 2;
}
visitnum++;
return p;
}
public void union(int p, int q) {
int a = find(p);
int b = find(q);
if(a == b)
return;
id[a] = b;
visitnum++;
count--;
System.out.println(p + "->" + q);
}
1.5.3 使用加权quick-union算法完成1.5.1。
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
public class WeightedQuickUnionUF {
private int[] id;
private int[] weight; //树的权重
private int count;
private int visitnum;
public WeightedQuickUnionUF(int n) {
count = n;
visitnum = 0;
id = new int[n];
weight = new int[n];
for (int i = 0; i < n; i++) {
id[i] = i;
weight[i] = 1;
visitnum++;
}
}
public int getCount() {
return count;
}
public int getVisitnum() {
return visitnum;
}
public int find(int p) {
while (p != id[p]) {
p = id[p];
visitnum += 2;
}
visitnum++;
return p;
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public void union(int p, int q) {
int a = find(p);
int b = find(q);
if(a == b)
return;
if(weight[a] < weight[b]) {
id[a] = b;
visitnum++;
weight[b] += weight[a];
}
else {
id[b] = a;
visitnum++;
weight[a] += weight[b];
}
count--;
System.out.println(p + "->" + q);
}
public static void main(String[] args) {
int n = StdIn.readInt();
int m = StdIn.readInt();
WeightedQuickUnionUF uf = new WeightedQuickUnionUF(n);
while (m-- > 0) {
int p = StdIn.readInt();
int q = StdIn.readInt();
if(uf.connected(p, q))
continue;
uf.union(p, q);
}
StdOut.println("共有" + uf.getCount() + "个连通分量");
StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
}
}
1.5.4 在正文的加权quick-union算法示例中,对于输入的每一对整数(包括对照输入和最坏情况下的输入),给出id[]和weight[]数组的内容以及访问数组的次数。
对照:
最坏:
1.5.8 用一个反例证明quick-find算法中的union()方法的以下直观实现是错误的:
public void union(int p, int q) {
if(connected(p, q))
return;
for(int i = 0; i < id.length; i++) {
if(id[i] == id[p])
id[i] = id[q];
}
count--;
}
原来的实现是用一个变量存放id[p]的值,让符合条件的id[i]改成变量的值,这里的问题在于id[p]有可能发生改变从而使得后面的过程错误。
当i == p时,符合id[i] == id[p]的条件,id[p]的值被改为id[q]的值,但若是在p之后还有属于这个分量的触点,那么由于id[p]的改变使得后面的点无法被修改。
如下面的id[]情况:
3 3 3 3 7 7 7 7
连接0 5,p == 0, q == 5
id[p] == 3
i == 0时,修改为
7 3 3 3 7 7 7 7
此时id[p]已经变成7了。
i == 1时,id[i] != id[p],不改变。然而它本来应该改变的。
1.5.9 画出下面的id[]数组对应的树。这可能是加权quick-union算法得到的结果吗?解释为什么不可能,或者给出能够得到该数组的一系列操作。
i 0 1 2 3 4 5 6 7 8 9
id[i] 1 1 3 1 5 6 1 3 4 5
这是不可能的。由书上定理可知树的高度不会超过lgN。这里的高度为4 > lg10。
1.5.10 在加权quick-union算法中,假设我们将id[find§]的值设为q而非id[find(q)],所得的算法是正确的吗?
是的,但是会增加树的高度,无法保证性能。
1.5.11 实现加权quick-find算法,其中我们总是将较小的分量重命名为较大的分量的标识符。这种改变会对性能产生怎样的影响?
public void union(int p, int q) {
int a = find(p);
int b = find(q);
if(a == b)
return;
if(weight[a] < weight[b]) {
for (int i = 0; i < id.length; i++, visitnum++) {
if (id[i] == a) {
id[i] = b;
weight[b] += weight[i];
}
}
}
else {
for (int i = 0; i < id.length; i++, visitnum++) {
if (id[i] == b) {
id[i] = a;
weight[a] += weight[i];
}
}
}
count--;
System.out.println(p + "->" + q);
}
把数量少的分量改成数量多的分量可以减少赋值操作,但总体上提升的性能有限,因为无论如何,都要遍历id[],并不会减少循环次数。
1.5.12 使用路径压缩的quick-union算法。
public int find(int p) {
int a = p;
while (p != id[p])
p = id[p];
while (a != id[a]) {
int b = id[a];
id[a] = p;
a = b;
}
return p;
}
改进之后,quick-union算法也可以在短时间内处理100万个触点和200万条链接了。
1.5.13 使用路径压缩的加权quick-union算法。
同上作修改。
对同一测试数据的访问数组次数大约为1.5.12的一半。
1.5.14 根据高度加权的quick-union算法。
把weight[]换成height[],除了union()其它函数不变。
把一棵高度低的树链接到高度高的树上,高度不变,只有两棵相同高度的树链接时,才会使被链接的树高度+1。
public void union(int p, int q) {
int a = find(p);
int b = find(q);
if(a == b)
return;
if(height[a] < height[b])
id[a] = b;
else {
id[b] = a;
if(height[a] == height[b])
height[a]++;
}
count--;
System.out.println(p + "--" + q);
}
1.5.15 二项树。请证明,对于加权quick-union算法,在最坏情况下树中每一层的节点数均为二项式系数。在这种情况下,计算含有
N
=
2
n
N=2^n
N=2n个节点的树中节点的平均深度。
倒数第二行的树,节点数自上而下为1 2 1,倒数第一行的树,节点数为1 3 3 1,这确实符合二项式系数,下面来证明。
在最坏情况下,每次合并的都是节点树相同的树,所以每次合并都会使树的深度增加。可以观察到,合并后的新树,第i层的节点树
k
i
′
k'_i
ki′由合并时的主树第i层的节点数
k
i
k_i
ki加上副树第i-1层的节点数
k
i
−
1
k_{i-1}
ki−1得到。
0 1 2 1
1 2 1 0
1 3 3 1
杨辉三角就是这样构造的。
由二项式定理,
(
x
+
y
)
n
=
∑
i
=
0
n
C
n
i
x
n
−
i
y
i
(x+y)^n = \sum_{i=0}^{n}C_n^ix^{n-i}y^i
(x+y)n=∑i=0nCnixn−iyi,当x和y取1时,就得到了如下公式:
2
n
=
∑
i
=
0
n
C
n
i
2^n=\sum_{i=0}^{n}C_n^i
2n=∑i=0nCni
所有节点的总的深度和为:
∑
i
=
0
n
i
⋅
C
n
i
=
n
∑
i
=
1
n
C
n
−
1
i
−
1
=
n
⋅
2
n
−
1
\sum_{i=0}^{n}i\cdot C_n^i=n\sum_{i=1}^{n}C_{n-1}^{i-1}=n\cdot 2^{n-1}
∑i=0ni⋅Cni=n∑i=1nCn−1i−1=n⋅2n−1
平均深度为
n
⋅
2
n
−
1
2
n
=
n
2
\frac{n\cdot 2^{n-1}}{2^n}=\frac{n}{2}
2nn⋅2n−1=2n
1.5.16 均摊成本图像。绘出quick-union和quick-find算法的均摊图像(mediumUF.txt)
quick-union:
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
public class quick_union_UF {
private int[] id;
private int count;
private int visitnum;
public void visualInit() {
StdDraw.setXscale(0, count + 1);
StdDraw.setYscale(0, count + 1);
StdDraw.setPenRadius(0.005);
}
public void drawPoint(int x, int y) {
StdDraw.setPenColor(StdDraw.RED);
StdDraw.point(x, y);
StdDraw.setPenColor(StdDraw.DARK_GRAY);
StdDraw.point(x, (double)visitnum / x);
}
public quick_union_UF(int n) {
count = n;
visitnum = 0;
id = new int[n];
for (int i = 0; i < n; i++) {
id[i] = i;
visitnum++;
}
}
public int getCount() {
return count;
}
public int getVisitnum() {
return visitnum;
}
public int[] find(int p) {
int arr[] = new int[2];
int cost = 0;
while (p != id[p]) {
p = id[p];
visitnum += 2;
cost += 2;
}
visitnum++;
cost++;
arr[0] = p;
arr[1] = cost;
return arr;
}
public int union(int p, int q) {
int prr[] = find(p);
int qrr[] = find(q);
int a = prr[0];
int b = qrr[0];
if(a == b)
return prr[1] + qrr[1];
id[a] = b;
visitnum++;
count--;
System.out.println(p + "--" + q);
return prr[1] + qrr[1] + 1;
}
public static void main(String[] args) {
int n = StdIn.readInt();
quick_union_UF uf = new quick_union_UF(n);
uf.visualInit();
int i = 1;
while (!StdIn.isEmpty()) {
int p = StdIn.readInt();
int q = StdIn.readInt();
int num = uf.union(p, q);
uf.drawPoint(i++, num);
}
StdOut.println("共有" + uf.getCount() + "个连通分量");
StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
}
}
quick-find:
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
public class quick_find_UF {
private int[] id;
private int count;
private int visitnum;
public quick_find_UF(int n) {
visitnum = 0;
count = n;
id = new int[n];
for (int i = 0; i < n; i++) {
id[i] = i;
visitnum++;
}
}
public void drawPoint(int x, int y) {
StdDraw.setPenColor(StdDraw.RED);
StdDraw.point(x, y);
StdDraw.setPenColor(StdDraw.DARK_GRAY);
StdDraw.point(x, (double)visitnum / x);
}
public void visualInit() {
StdDraw.setXscale(0, 1000);
StdDraw.setYscale(0, 2000);
StdDraw.setPenRadius(0.005);
}
public int getCount() {
return count;
}
public int getVisitnum() {
return visitnum;
}
public int find(int p) {
visitnum++;
return id[p];
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public int union(int p, int q) {
int a = find(p);
int b = find(q);
if(a == b)
return 2;
int num = 2;
for(int i = 0; i < id.length; i++, visitnum++) {
if(id[i] == a) {
id[i] = b;
num++;
}
num++;
}
count--;
System.out.println(p + "--" + q);
return num;
}
public static void main(String[] args) {
int n = StdIn.readInt();
quick_find_UF uf = new quick_find_UF(n);
uf.visualInit();
int i = 1;
while (!StdIn.isEmpty()) {
int p = StdIn.readInt();
int q = StdIn.readInt();
int num = uf.union(p, q);
uf.drawPoint(i++, num);
}
StdOut.println("共有" + uf.getCount() + "个连通分量");
StdOut.println("共访问了" + uf.getVisitnum() + "次数组");
}
}