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算法,在最壞情況下樹中每一層的節點數均爲二項式係數。在這種情況下,計算含有個節點的樹中節點的平均深度。
倒數第二行的樹,節點數自上而下爲1 2 1,倒數第一行的樹,節點數爲1 3 3 1,這確實符合二項式係數,下面來證明。
在最壞情況下,每次合併的都是節點樹相同的樹,所以每次合併都會使樹的深度增加。可以觀察到,合併後的新樹,第i層的節點樹由合併時的主樹第i層的節點數加上副樹第i-1層的節點數得到。
0 1 2 1
1 2 1 0
1 3 3 1
楊輝三角就是這樣構造的。
由二項式定理,,當x和y取1時,就得到了如下公式:
所有節點的總的深度和爲:
平均深度爲
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() + "次數組");
}
}