《算法》1.5部分題解

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=2nN=2^n個節點的樹中節點的平均深度。
在這裏插入圖片描述

倒數第二行的樹,節點數自上而下爲1 2 1,倒數第一行的樹,節點數爲1 3 3 1,這確實符合二項式係數,下面來證明。
在最壞情況下,每次合併的都是節點樹相同的樹,所以每次合併都會使樹的深度增加。可以觀察到,合併後的新樹,第i層的節點樹kik'_i由合併時的主樹第i層的節點數kik_i加上副樹第i-1層的節點數ki1k_{i-1}得到。
0 1 2 1
1 2 1 0
1 3 3 1
楊輝三角就是這樣構造的。
由二項式定理,(x+y)n=i=0nCnixniyi(x+y)^n = \sum_{i=0}^{n}C_n^ix^{n-i}y^i,當x和y取1時,就得到了如下公式:
2n=i=0nCni2^n=\sum_{i=0}^{n}C_n^i
所有節點的總的深度和爲:
i=0niCni=ni=1nCn1i1=n2n1\sum_{i=0}^{n}i\cdot C_n^i=n\sum_{i=1}^{n}C_{n-1}^{i-1}=n\cdot 2^{n-1}
平均深度爲n2n12n=n2\frac{n\cdot 2^{n-1}}{2^n}=\frac{n}{2}

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() + "次數組");
    }


}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章