C++,java算法與數據結構(二)--慕課網筆記

第6章 並查集
6-1 並查集基礎

一種很不一樣的樹形結構

連接問題 Connectivity Problem

網絡中節點間的連接狀態
網絡是個抽象的概念:用戶之間形成的網絡

數據中的集合類實現

連接問題和路徑問題
比路徑問題要回答的問題少
和二分查找做比較
和select作比較
和堆作比較

6-2 Qucik Find

對於一組數據,主要支持兩個動作:
union(p,q)
find(p)

用來回答一個問題
isConnected(p,q)

Quick find 下的Union時間複雜度O(n)

// 我們的第一版Union-Find
public class UnionFind1 {

    private int[] id;    // 我們的第一版Union-Find本質就是一個數組
    private int count;   // 數據個數

    public UnionFind1(int n) {
        count = n;
        id = new int[n];
        // 初始化, 每一個id[i]指向自己, 沒有合併的元素
        for (int i = 0; i < n; i++)
            id[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(1)複雜度
    private int find(int p) {
        assert p >= 0 && p < count;
        return id[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(1)複雜度
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(n) 複雜度
    public void unionElements(int p, int q) {

        int pID = find(p);
        int qID = find(q);

        if (pID == qID)
            return;

        // 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併
        for (int i = 0; i < count; i++)
            if (id[i] == pID)
                id[i] = qID;
    }
}
// 測試並查集
public class UnionFindTestHelper {

    // 測試第一版本的並查集, 測試元素個數爲n
    public static void testUF1( int n ){

        UnionFind1 uf = new UnionFind1(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF1, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }
}
public class Main {

    // 測試UF1
    public static void main(String[] args) {

        // 使用10000的數據規模
        int n = 10000;

        // 雖然isConnected只需要O(1)的時間, 但由於union操作需要O(n)的時間
        // 總體測試過程的算法複雜度是O(n^2)的
        UnionFindTestHelper.testUF1(n);
    }
}
6-3 Quick Union

並查集的另一種思路

將每一個元素,看做是一個節點

public class Main {

    // 對比UF1和UF2的時間性能
    public static void main(String[] args) {

        // 使用10000的數據規模
        int n = 10000;

        // 雖然isConnected只需要O(1)的時間, 但由於union操作需要O(n)的時間
        // 總體測試過程的算法複雜度是O(n^2)的
        UnionFindTestHelper.testUF1(n);

        // 對於UF2來說, 其時間性能是O(n*h)的, h爲並查集表達的樹的最大高度
        // 這裏嚴格來講, h和logn沒有關係, 不過大家可以簡單這麼理解
        // 我們後續內容會對h進行優化, 總體而言, 這個h是遠小於n的
        // 所以我們實現的UF2測試結果遠遠好於UF1, n越大越明顯:)
        UnionFindTestHelper.testUF2(n);
    }
}
// 我們的第一版Union-Find
public class UnionFind1 {

    private int[] id;    // 我們的第一版Union-Find本質就是一個數組
    private int count;   // 數據個數

    public UnionFind1(int n) {
        count = n;
        id = new int[n];
        // 初始化, 每一個id[i]指向自己, 沒有合併的元素
        for (int i = 0; i < n; i++)
            id[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    private int find(int p) {
        assert p >= 0 && p < count;
        return id[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(1)複雜度
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(n) 複雜度
    public void unionElements(int p, int q) {

        int pID = find(p);
        int qID = find(q);

        if (pID == qID)
            return;

        // 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併
        for (int i = 0; i < count; i++)
            if (id[i] == pID)
                id[i] = qID;
    }
}
// 我們的第二版Union-Find
public class UnionFind2 {

    // 我們的第二版Union-Find, 使用一個數組構建一棵指向父節點的樹
    // parent[i]表示第一個元素所指向的父節點
    private int[] parent;
    private int count;  // 數據個數

    // 構造函數
    public UnionFind2(int count){
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ )
            parent[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        parent[pRoot] = qRoot;
    }
}
// 測試並查集
public class UnionFindTestHelper {

    // 測試第一版本的並查集, 測試元素個數爲n
    public static void testUF1( int n ){

        UnionFind1 uf = new UnionFind1(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF1, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第二版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    // 思考一下: 這樣的冗餘代碼如何消除?
    // 由於這個課程不是設計模式課程, 在這裏就不過多引入相關的問題講解了。留作給大家的思考題:)
    public static void testUF2( int n ){

        UnionFind2 uf = new UnionFind2(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF2, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }
}
6-4 基於size的優化
public class Main {

    // 對比UF1, UF2和UF3的時間性能
    public static void main(String[] args) {

        // 使用10000的數據規模
        int n = 10000;

        // 雖然isConnected只需要O(1)的時間, 但由於union操作需要O(n)的時間
        // 總體測試過程的算法複雜度是O(n^2)的
        UnionFindTestHelper.testUF1(n);

        // 對於UF2來說, 其時間性能是O(n*h)的, h爲並查集表達的樹的最大高度
        // 這裏嚴格來講, h和logn沒有關係, 不過大家可以簡單這麼理解
        // 我們後續內容會對h進行優化, 總體而言, 這個h是遠小於n的
        // 所以我們實現的UF2測試結果遠遠好於UF1, n越大越明顯:)
        UnionFindTestHelper.testUF2(n);

        // 對於UF3來說, 其時間性能依然是O(n*h)的, h爲並查集表達的樹的最大高度
        // 但由於UF3能更高概率的保證樹的平衡, 所以性能更優
        UnionFindTestHelper.testUF3(n);
    }
}
// 我們的第一版Union-Find
public class UnionFind1 {

    private int[] id;    // 我們的第一版Union-Find本質就是一個數組
    private int count;   // 數據個數

    public UnionFind1(int n) {
        count = n;
        id = new int[n];
        // 初始化, 每一個id[i]指向自己, 沒有合併的元素
        for (int i = 0; i < n; i++)
            id[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    private int find(int p) {
        assert p >= 0 && p < count;
        return id[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(1)複雜度
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(n) 複雜度
    public void unionElements(int p, int q) {

        int pID = find(p);
        int qID = find(q);

        if (pID == qID)
            return;

        // 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併
        for (int i = 0; i < count; i++)
            if (id[i] == pID)
                id[i] = qID;
    }
}
// 我們的第二版Union-Find
public class UnionFind2 {

    // 我們的第二版Union-Find, 使用一個數組構建一棵指向父節點的樹
    // parent[i]表示第一個元素所指向的父節點
    private int[] parent;
    private int count;  // 數據個數

    // 構造函數
    public UnionFind2(int count){
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ )
            parent[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        parent[pRoot] = qRoot;
    }
}
// 我們的第三版Union-Find
public class UnionFind3 {

    private int[] parent; // parent[i]表示第一個元素所指向的父節點
    private int[] sz;     // sz[i]表示以i爲根的集合中元素個數
    private int count;    // 數據個數

    // 構造函數
    public UnionFind3(int count){
        parent = new int[count];
        sz = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            sz[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( sz[pRoot] < sz[qRoot] ){
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }
        else{
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
}
// 測試並查集
public class UnionFindTestHelper {

    // 我們的測試類不允許創建實例
    private UnionFindTestHelper(){}

    // 測試第一版本的並查集, 測試元素個數爲n
    public static void testUF1( int n ){

        UnionFind1 uf = new UnionFind1(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF1, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第二版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    // 思考一下: 這樣的冗餘代碼如何消除?
    // 由於這個課程不是設計模式課程, 在這裏就不過多引入相關的問題講解了。留作給大家的思考題:)
    public static void testUF2( int n ){

        UnionFind2 uf = new UnionFind2(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF2, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第三版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF3( int n ){

        UnionFind3 uf = new UnionFind3(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF3, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }
}
6-5 基於rank的優化

基於rank的優化
rank[i]表示根節點爲i的樹的高度

public class Main {

    // 對比UF1, UF2, UF3和UF4的時間性能
    public static void main(String[] args) {

        // 使用1000000的數據規模
        int n = 1000000;

        // 雖然isConnected只需要O(1)的時間, 但由於union操作需要O(n)的時間
        // 總體測試過程的算法複雜度是O(n^2)的

        // 100萬數據對於UF1來說太慢了, 不再測試
        //UnionFindTestHelper.testUF1(n);

        // 對於UF2來說, 其時間性能是O(n*h)的, h爲並查集表達的樹的最大高度
        // 這裏嚴格來講, h和logn沒有關係, 不過大家可以簡單這麼理解
        // 我們後續內容會對h進行優化, 總體而言, 這個h是遠小於n的
        // 所以我們實現的UF2測試結果遠遠好於UF1, n越大越明顯:)

        // 100萬數據對於UF2來說也是很慢的, 不再測試
        //UnionFindTestHelper.testUF2(n);

        // 對於UF3來說, 其時間性能依然是O(n*h)的, h爲並查集表達的樹的最大高度
        // 但由於UF3能更高概率的保證樹的平衡, 所以性能更優
        UnionFindTestHelper.testUF3(n);

        // UF4雖然相對UF3進行有了優化, 但優化的地方出現的情況較少,
        // 所以性能更優表現的不明顯, 甚至在一些數據下性能會更差
        UnionFindTestHelper.testUF4(n);
    }
}
// 我們的第一版Union-Find
public class UnionFind1 {

    private int[] id;    // 我們的第一版Union-Find本質就是一個數組
    private int count;   // 數據個數

    public UnionFind1(int n) {
        count = n;
        id = new int[n];
        // 初始化, 每一個id[i]指向自己, 沒有合併的元素
        for (int i = 0; i < n; i++)
            id[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    private int find(int p) {
        assert p >= 0 && p < count;
        return id[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(1)複雜度
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(n) 複雜度
    public void unionElements(int p, int q) {

        int pID = find(p);
        int qID = find(q);

        if (pID == qID)
            return;

        // 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併
        for (int i = 0; i < count; i++)
            if (id[i] == pID)
                id[i] = qID;
    }
}
// 我們的第二版Union-Find
public class UnionFind2 {

    // 我們的第二版Union-Find, 使用一個數組構建一棵指向父節點的樹
    // parent[i]表示第一個元素所指向的父節點
    private int[] parent;
    private int count;  // 數據個數

    // 構造函數
    public UnionFind2(int count){
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ )
            parent[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        parent[pRoot] = qRoot;
    }
}
// 我們的第三版Union-Find
public class UnionFind3 {

    private int[] parent; // parent[i]表示第一個元素所指向的父節點
    private int[] sz;     // sz[i]表示以i爲根的集合中元素個數
    private int count;    // 數據個數

    // 構造函數
    public UnionFind3(int count){
        parent = new int[count];
        sz = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            sz[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( sz[pRoot] < sz[qRoot] ){
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }
        else{
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
}
// 我們的第四版Union-Find
public class UnionFind4 {

    private int[] rank;   // rank[i]表示以i爲根的集合所表示的樹的層數
    private int[] parent; // parent[i]表示第i個元素所指向的父節點
    private int count;    // 數據個數

    // 構造函數
    public UnionFind4(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }
}
// 測試並查集
public class UnionFindTestHelper {

    // 我們的測試類不允許創建實例
    private UnionFindTestHelper(){}

    // 測試第一版本的並查集, 測試元素個數爲n
    public static void testUF1( int n ){

        UnionFind1 uf = new UnionFind1(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF1, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第二版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    // 思考一下: 這樣的冗餘代碼如何消除?
    // 由於這個課程不是設計模式課程, 在這裏就不過多引入相關的問題講解了。留作給大家的思考題:)
    public static void testUF2( int n ){

        UnionFind2 uf = new UnionFind2(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF2, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第三版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF3( int n ){

        UnionFind3 uf = new UnionFind3(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF3, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第四版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF4( int n ){

        UnionFind4 uf = new UnionFind4(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF4, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }
}
6-6 路徑壓縮

並查集的操作,時間複雜度近乎是O(1)的前驅

public class Main {

    // 對比UF1, UF2, UF3, UF4和UF5的時間性能
    public static void main(String[] args) {

        // 使用1000000的數據規模
        int n = 1000000;

        // 雖然isConnected只需要O(1)的時間, 但由於union操作需要O(n)的時間
        // 總體測試過程的算法複雜度是O(n^2)的

        // 100萬數據對於UF1來說太慢了, 不再測試
        //UnionFindTestHelper.testUF1(n);

        // 對於UF2來說, 其時間性能是O(n*h)的, h爲並查集表達的樹的最大高度
        // 這裏嚴格來講, h和logn沒有關係, 不過大家可以簡單這麼理解
        // 我們後續內容會對h進行優化, 總體而言, 這個h是遠小於n的
        // 所以我們實現的UF2測試結果遠遠好於UF1, n越大越明顯:)

        // 100萬數據對於UF2來說也是很慢的, 不再測試
        //UnionFindTestHelper.testUF2(n);

        // 對於UF3來說, 其時間性能依然是O(n*h)的, h爲並查集表達的樹的最大高度
        // 但由於UF3能更高概率的保證樹的平衡, 所以性能更優
        UnionFindTestHelper.testUF3(n);

        // UF4雖然相對UF3進行有了優化, 但優化的地方出現的情況較少,
        // 所以性能更優表現的不明顯, 甚至在一些數據下性能會更差
        UnionFindTestHelper.testUF4(n);

        // UF5雖然相對UF4進行有了優化, 但優化的地方出現的情況較少,
        // 所以性能更優表現的不明顯, 甚至在一些數據下性能會更差
        UnionFindTestHelper.testUF5(n);
    }
}
// 我們的第一版Union-Find
public class UnionFind1 {

    private int[] id;    // 我們的第一版Union-Find本質就是一個數組
    private int count;   // 數據個數

    public UnionFind1(int n) {
        count = n;
        id = new int[n];
        // 初始化, 每一個id[i]指向自己, 沒有合併的元素
        for (int i = 0; i < n; i++)
            id[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    private int find(int p) {
        assert p >= 0 && p < count;
        return id[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(1)複雜度
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(n) 複雜度
    public void unionElements(int p, int q) {

        int pID = find(p);
        int qID = find(q);

        if (pID == qID)
            return;

        // 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併
        for (int i = 0; i < count; i++)
            if (id[i] == pID)
                id[i] = qID;
    }
}
// 我們的第二版Union-Find
public class UnionFind2 {

    // 我們的第二版Union-Find, 使用一個數組構建一棵指向父節點的樹
    // parent[i]表示第一個元素所指向的父節點
    private int[] parent;
    private int count;  // 數據個數

    // 構造函數
    public UnionFind2(int count){
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ )
            parent[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        parent[pRoot] = qRoot;
    }
}
// 我們的第三版Union-Find
public class UnionFind3 {

    private int[] parent; // parent[i]表示第一個元素所指向的父節點
    private int[] sz;     // sz[i]表示以i爲根的集合中元素個數
    private int count;    // 數據個數

    // 構造函數
    public UnionFind3(int count){
        parent = new int[count];
        sz = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            sz[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( sz[pRoot] < sz[qRoot] ){
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }
        else{
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
}
// 我們的第四版Union-Find
public class UnionFind4 {

    private int[] rank;   // rank[i]表示以i爲根的集合所表示的樹的層數
    private int[] parent; // parent[i]表示第i個元素所指向的父節點
    private int count;    // 數據個數

    // 構造函數
    public UnionFind4(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }
}
// 我們的第五版Union-Find
public class UnionFind5 {

    // rank[i]表示以i爲根的集合所表示的樹的層數
    // 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
    // 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
    // 關於這個問題,可以參考問答區:http://coding.imooc.com/learn/questiondetail/7287.html
    private int[] rank;
    private int[] parent; // parent[i]表示第i個元素所指向的父節點
    private int count;    // 數據個數

    // 構造函數
    public UnionFind5(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );

        // path compression 1
        while( p != parent[p] ){
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;

        // path compression 2, 遞歸算法
//            if( p != parent[p] )
//                parent[p] = find( parent[p] );
//            return parent[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }
}
// 測試並查集
public class UnionFindTestHelper {

    // 我們的測試類不允許創建實例
    private UnionFindTestHelper(){}

    // 測試第一版本的並查集, 測試元素個數爲n
    public static void testUF1( int n ){

        UnionFind1 uf = new UnionFind1(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF1, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第二版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    // 思考一下: 這樣的冗餘代碼如何消除?
    // 由於這個課程不是設計模式課程, 在這裏就不過多引入相關的問題講解了。留作給大家的思考題:)
    public static void testUF2( int n ){

        UnionFind2 uf = new UnionFind2(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF2, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第三版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF3( int n ){

        UnionFind3 uf = new UnionFind3(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF3, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第四版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF4( int n ){

        UnionFind4 uf = new UnionFind4(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF4, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第五版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF5( int n ){

        UnionFind5 uf = new UnionFind5(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF4, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }
}
補充1 使用相同的測試用例測試不同的UF實現
public class Main {

    // 對比UF1, UF2, UF3, UF4, UF5和UF6的時間性能
    // 在這裏, 我們對於不同的UnionFind的實現, 使用相同的測試用例, 讓測試結果更加準確
    public static void main(String[] args) {

        // 使用5000000的數據規模
        int n = 5000000;

        // 生成unionElements的測試用例
        Pair<Integer, Integer>[] unionTest = new Pair[n];
        for(int i = 0 ; i < n ; i ++){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            unionTest[i] = new Pair<Integer, Integer>(a, b);
        }

        // 生成isConnected的測試用例
        Pair<Integer, Integer>[] connectTest = new Pair[n];
        for(int i = 0 ; i < n ; i ++){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            connectTest[i] = new Pair<Integer, Integer>(a, b);
        }

        // 測試我們的UF1 ~ UF6

        // 100萬數據對於UF1和UF2來說太慢了, 不再測試
//        UnionFind1 uf1 = new UnionFind1(n);
//        UnionFindTestHelper.testUF("UF1", uf1, unionTest, connectTest);
//
//        UnionFind2 uf2 = new UnionFind2(n);
//        UnionFindTestHelper.testUF("UF2", uf2, unionTest, connectTest);

        UnionFind3 uf3 = new UnionFind3(n);
        UnionFindTestHelper.testUF("UF3", uf3, unionTest, connectTest);

        UnionFind4 uf4 = new UnionFind4(n);
        UnionFindTestHelper.testUF("UF4", uf4, unionTest, connectTest);

        UnionFind5 uf5 = new UnionFind5(n);
        UnionFindTestHelper.testUF("UF5", uf5, unionTest, connectTest);

        UnionFind6 uf6 = new UnionFind6(n);
        UnionFindTestHelper.testUF("UF6", uf6, unionTest, connectTest);
    }
}
public class Pair<A, B> {

    private A a;
    private B b;

    public Pair(A a, B b) {
        this.a = a;
        this.b = b;
    }

    public A a() {
        return a;
    }

    public B b() {
        return b;
    }
}
// 我們設計一個UF的接口,讓不同的UF實現具體實現這個接口
public interface UF {

    public boolean isConnected( int p , int q );
    public void unionElements(int p, int q);
}
// 我們的第一版Union-Find
public class UnionFind1 implements UF {

    private int[] id;    // 我們的第一版Union-Find本質就是一個數組
    private int count;   // 數據個數

    public UnionFind1(int n) {
        count = n;
        id = new int[n];
        // 初始化, 每一個id[i]指向自己, 沒有合併的元素
        for (int i = 0; i < n; i++)
            id[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    private int find(int p) {
        assert p >= 0 && p < count;
        return id[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(1)複雜度
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(n) 複雜度
    public void unionElements(int p, int q) {

        int pID = find(p);
        int qID = find(q);

        if (pID == qID)
            return;

        // 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併
        for (int i = 0; i < count; i++)
            if (id[i] == pID)
                id[i] = qID;
    }
}
// 我們的第二版Union-Find
public class UnionFind2 implements UF {

    // 我們的第二版Union-Find, 使用一個數組構建一棵指向父節點的樹
    // parent[i]表示第一個元素所指向的父節點
    private int[] parent;
    private int count;  // 數據個數

    // 構造函數
    public UnionFind2(int count){
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ )
            parent[i] = i;
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        parent[pRoot] = qRoot;
    }
}
// 我們的第三版Union-Find
public class UnionFind3 implements UF {

    private int[] parent; // parent[i]表示第一個元素所指向的父節點
    private int[] sz;     // sz[i]表示以i爲根的集合中元素個數
    private int count;    // 數據個數

    // 構造函數
    public UnionFind3(int count){
        parent = new int[count];
        sz = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            sz[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( sz[pRoot] < sz[qRoot] ){
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }
        else{
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
}
// 我們的第四版Union-Find
public class UnionFind4 implements UF {

    private int[] rank;   // rank[i]表示以i爲根的集合所表示的樹的層數
    private int[] parent; // parent[i]表示第i個元素所指向的父節點
    private int count;    // 數據個數

    // 構造函數
    public UnionFind4(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );
        // 不斷去查詢自己的父親節點, 直到到達根節點
        // 根節點的特點: parent[p] == p
        while( p != parent[p] )
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }
}
// 我們的第五版Union-Find, 路徑壓縮使用迭代實現
public class UnionFind5 implements UF {

    // rank[i]表示以i爲根的集合所表示的樹的層數
    // 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
    // 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
    // 關於這個問題,可以參考問答區:http://coding.imooc.com/learn/questiondetail/7287.html
    private int[] rank;
    private int[] parent; // parent[i]表示第i個元素所指向的父節點
    private int count;    // 數據個數

    // 構造函數
    public UnionFind5(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );

        // path compression 1
        while( p != parent[p] ){
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }
}
// 我們的第六版Union-Find, 路徑壓縮使用遞歸實現
public class UnionFind6 implements UF {

    // rank[i]表示以i爲根的集合所表示的樹的層數
    // 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
    // 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
    // 關於這個問題,可以參考問答區:http://coding.imooc.com/learn/questiondetail/7287.html
    private int[] rank;
    private int[] parent; // parent[i]表示第i個元素所指向的父節點
    private int count;    // 數據個數

    // 構造函數
    public UnionFind6(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    private int find(int p){
        assert( p >= 0 && p < count );

        // path compression 2, 遞歸算法
        if( p != parent[p] )
            parent[p] = find( parent[p] );
        return parent[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }
}
// 測試並查集
public class UnionFindTestHelper {

    // 我們的測試類不允許創建實例
    private UnionFindTestHelper(){}

    // 測試第一版本的並查集, 測試元素個數爲n
    public static void testUF1( int n ){

        UnionFind1 uf = new UnionFind1(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF1, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第二版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    // 思考一下: 這樣的冗餘代碼如何消除?
    // 由於這個課程不是設計模式課程, 在這裏就不過多引入相關的問題講解了。留作給大家的思考題:)
    public static void testUF2( int n ){

        UnionFind2 uf = new UnionFind2(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF2, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第三版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF3( int n ){

        UnionFind3 uf = new UnionFind3(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF3, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第四版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF4( int n ){

        UnionFind4 uf = new UnionFind4(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF4, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第五版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF5( int n ){

        UnionFind5 uf = new UnionFind5(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF5, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 測試第六版本的並查集, 測試元素個數爲n, 測試邏輯和之前是完全一樣的
    public static void testUF6( int n ){

        UnionFind6 uf = new UnionFind6(n);

        long startTime = System.currentTimeMillis();

        // 進行n次操作, 每次隨機選擇兩個元素進行合併操作
        for( int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.unionElements(a,b);
        }
        // 再進行n次操作, 每次隨機選擇兩個元素, 查詢他們是否同屬一個集合
        for(int i = 0 ; i < n ; i ++ ){
            int a = (int)(Math.random()*n);
            int b = (int)(Math.random()*n);
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        // 打印輸出對這2n個操作的耗時
        System.out.println("UF6, " + 2*n + " ops, " + (endTime-startTime) + "ms");
    }

    // 使用相同的測試數據測試UF的執行效率
    public static void testUF(String ufName, UF uf, Pair<Integer,Integer>[] unionTest, Pair<Integer,Integer>[] connectTest){

        long startTime = System.currentTimeMillis();
        for( int i = 0 ; i < unionTest.length ; i ++ ){
            int a = unionTest[i].a();
            int b = unionTest[i].b();
            uf.unionElements(a,b);
        }
        for(int i = 0 ; i < connectTest.length ; i ++ ){
            int a = connectTest[i].a();
            int b = connectTest[i].b();
            uf.isConnected(a,b);
        }
        long endTime = System.currentTimeMillis();

        System.out.print( ufName + " with " + unionTest.length + " unionElements ops, ");
        System.out.print( connectTest.length + " isConnected ops, ");
        System.out.println( (endTime-startTime) + "ms");
    }
}
補充2 迭代和遞歸實現兩種路徑壓縮的區別
public class Main {

    public static void main(String[] args) {

        // 爲了能夠方便地看出兩種路徑壓縮算法的不同,我們只使用有5個元素的並查集進行試驗
        int n = 5;

        UnionFind5 uf5 = new UnionFind5(n);
        UnionFind6 uf6 = new UnionFind6(n);

        // 我們將我們的並查集初始設置成如下的樣子
        //            0
        //           /
        //          1
        //         /
        //        2
        //       /
        //      3
        //     /
        //    4
        for(int i = 1 ; i < n ; i ++){
            uf5.parent[i] = i-1;
            uf6.parent[i] = i-1;
        }

        // 現在, 我們對兩個並查集調用find(4)操作
        uf5.find(n-1);
        uf6.find(n-1);

        // 通過show, 我們可以看出, 使用迭代的路徑壓縮, 我們的並查集變成這個樣子:
        //     0
        //    / \
        //   1   2
        //  / \
        // 3  4
        System.out.println("UF implements Path Compression by recursion:");
        uf5.show();

        System.out.println();

        // 使用遞歸的路徑壓縮, 我們的並查集變成這個樣子:
        //     0
        //  / / \ \
        // 1 2   3 4
        System.out.println("UF implements Path Compression without recursion:");
        uf6.show();

        // 大家也可以調大n的值, 看看結果的不同:)
    }
}
// 我們設計一個UF的接口,讓不同的UF實現具體實現這個接口
public interface UF {

    public boolean isConnected( int p , int q );
    public void unionElements(int p, int q);
}
// 我們的第五版Union-Find, 路徑壓縮使用迭代實現
public class UnionFind5 implements UF {

    // rank[i]表示以i爲根的集合所表示的樹的層數
    // 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
    // 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
    // 關於這個問題,可以參考問答區:http://coding.imooc.com/learn/questiondetail/7287.html
    private int[] rank;
    public int[] parent; // parent[i]表示第i個元素所指向的父節點
                         // 後續, 我們要在外部操控並查集的數據, 在這裏使用public
    private int count;   // 數據個數

    // 構造函數
    public UnionFind5(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    public int find(int p){
        assert( p >= 0 && p < count );

        // path compression 1
        while( p != parent[p] ){
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }

    // 打印輸出並查集中的parent數據
    public void show(){
        for( int i = 0 ; i < count ; i ++ )
            System.out.print( parent[i] + " ");
        System.out.println();
    }
}
// 我們的第六版Union-Find, 路徑壓縮使用遞歸實現
public class UnionFind6 implements UF {

    // rank[i]表示以i爲根的集合所表示的樹的層數
    // 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
    // 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
    // 關於這個問題,可以參考問答區:http://coding.imooc.com/learn/questiondetail/7287.html
    private int[] rank;
    public int[] parent; // parent[i]表示第i個元素所指向的父節點
                         // 後續, 我們要在外部操控並查集的數據, 在這裏使用public
    private int count;   // 數據個數

    // 構造函數
    public UnionFind6(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    public int find(int p){
        assert( p >= 0 && p < count );

        // path compression 2, 遞歸算法
        if( p != parent[p] )
            parent[p] = find( parent[p] );
        return parent[p];
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }

    // 打印輸出並查集中的parent數據
    public void show(){
        for( int i = 0 ; i < count ; i ++ )
            System.out.print( parent[i] + " ");
        System.out.println();
    }
}
第7章 圖的基礎
7-1 圖論基礎

節點(Vertex)
邊(Edge)

交通運輸
社交網絡
互聯網
工作安排
腦區活動
程序狀態執行

無向圖
有向圖

無向圖是一種特殊的有向圖

無權圖(Unweighted Graph)
有權圖(Weighted Graph)

圖的連通性

簡單圖(Simple Graph)
自還變(self-loop)
平行邊(parallel-edges)

7-2 圖的表示

鄰接矩陣(Adjacency Matrix)
鄰接表(Adjacency Lists)

鄰接表適合表示稀疏圖(Sparse Graph)
鄰接矩陣適合表示稠密圖(Dense Graph)

// 稠密圖 - 鄰接矩陣
public class DenseGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private boolean[][] g;      // 圖的具體數據

    // 構造函數
    public DenseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲false, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new boolean[n][n];
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        if( hasEdge( v , w ) )
            return;

        g[v][w] = true;
        if( !directed )
            g[w][v] = true;

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w];
    }
}
// 稀疏圖 - 鄰接表
public class SparseGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;    // 是否爲有向圖
    private Vector<Integer>[] g; // 圖的具體數據

    // 構造函數
    public SparseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Integer>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Integer>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v, int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        g[v].add(w);
        if( v != w && !directed )
            g[w].add(v);

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i) == w )
                return true;
        return false;
    }
}
7-3 相鄰點迭代器
// 稠密圖 - 鄰接矩陣
public class DenseGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private boolean[][] g;      // 圖的具體數據

    // 構造函數
    public DenseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲false, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new boolean[n][n];
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        if( hasEdge( v , w ) )
            return;

        g[v][w] = true;
        if( !directed )
            g[w][v] = true;

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w];
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Integer> adjV = new Vector<Integer>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] )
                adjV.add(i);
        return adjV;
    }
}
// 稀疏圖 - 鄰接表
public class SparseGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;    // 是否爲有向圖
    private Vector<Integer>[] g; // 圖的具體數據

    // 構造函數
    public SparseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Integer>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Integer>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v, int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        g[v].add(w);
        if( v != w && !directed )
            g[w].add(v);

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i) == w )
                return true;
        return false;
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
7-4 圖的算法框架
// 稠密圖 - 鄰接矩陣
public class DenseGraph implements Graph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private boolean[][] g;      // 圖的具體數據

    // 構造函數
    public DenseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲false, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new boolean[n][n];
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        if( hasEdge( v , w ) )
            return;

        g[v][w] = true;
        if( !directed )
            g[w][v] = true;

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w];
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                System.out.print(g[i][j]+"\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Integer> adjV = new Vector<Integer>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] )
                adjV.add(i);
        return adjV;
    }
}
// 圖的接口
public interface Graph {

    public int V();
    public int E();
    public void addEdge( int v , int w );
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Integer> adj(int v);
}
// 測試通過文件讀取圖的信息
public class Main {

    public static void main(String[] args) {

        // 使用兩種圖的存儲方式讀取testG1.txt文件
        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch7_4\\testG1.txt";
        SparseGraph g1 = new SparseGraph(13, false);
        ReadGraph readGraph1 = new ReadGraph(g1, filename);
        System.out.println("test G1 in Sparse Graph:");
        g1.show();

        System.out.println();

        DenseGraph g2 = new DenseGraph(13, false);
        ReadGraph readGraph2 = new ReadGraph(g2 , filename );
        System.out.println("test G1 in Dense Graph:");
        g2.show();

        System.out.println();

        // 使用兩種圖的存儲方式讀取testG2.txt文件
        filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch7_4\\testG2.txt";
        SparseGraph g3 = new SparseGraph(6, false);
        ReadGraph readGraph3 = new ReadGraph(g3, filename);
        System.out.println("test G2 in Sparse Graph:");
        g3.show();

        System.out.println();

        DenseGraph g4 = new DenseGraph(6, false);
        ReadGraph readGraph4 = new ReadGraph(g4, filename);
        System.out.println("test G2 in Dense Graph:");
        g4.show();
    }
}
public class ReadGraph {

    private Scanner scanner;

    public ReadGraph(Graph graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(v, w);
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + "doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }
}
// 稀疏圖 - 鄰接表
public class SparseGraph implements Graph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Integer>[] g; // 圖的具體數據

    // 構造函數
    public SparseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Integer>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Integer>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v, int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        g[v].add(w);
        if( v != w && !directed )
            g[w].add(v);

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ )
                System.out.print(g[i].elementAt(j) + "\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
7-5 深度優先遍歷和聯通分量
// 求無權圖的聯通分量
public class Components {

    Graph G;                    // 圖的引用
    private boolean[] visited;  // 記錄dfs的過程中節點是否被訪問
    private int ccount;         // 記錄聯通分量個數
    private int[] id;           // 每個節點所對應的聯通分量標記

    // 圖的深度優先遍歷
    void dfs( int v ){

        visited[v] = true;
        id[v] = ccount;

        for( int i: G.adj(v) ){
            if( !visited[i] )
                dfs(i);
        }
    }

    // 構造函數, 求出無權圖的聯通分量
    public Components(Graph graph){

        // 算法初始化
        G = graph;
        visited = new boolean[G.V()];
        id = new int[G.V()];
        ccount = 0;
        for( int i = 0 ; i < G.V() ; i ++ ){
            visited[i] = false;
            id[i] = -1;
        }

        // 求圖的聯通分量
        for( int i = 0 ; i < G.V() ; i ++ )
            if( !visited[i] ){
                dfs(i);
                ccount ++;
            }
    }

    // 返回圖的聯通分量個數
    int count(){
        return ccount;
    }

    // 查詢點v和點w是否聯通
    boolean isConnected( int v , int w ){
        assert v >= 0 && v < G.V();
        assert w >= 0 && w < G.V();
        return id[v] == id[w];
    }
}
// 稠密圖 - 鄰接矩陣
public class DenseGraph implements Graph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private boolean[][] g;      // 圖的具體數據

    // 構造函數
    public DenseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲false, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new boolean[n][n];
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        if( hasEdge( v , w ) )
            return;

        g[v][w] = true;
        if( !directed )
            g[w][v] = true;

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w];
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                System.out.print(g[i][j]+"\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Integer> adjV = new Vector<Integer>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] )
                adjV.add(i);
        return adjV;
    }
}
// 圖的接口
public interface Graph {

    public int V();
    public int E();
    public void addEdge( int v , int w );
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Integer> adj(int v);
}
// 測試圖的聯通分量算法
public class Main {

    public static void main(String[] args) {

        // TestG1.txt
        String filename1 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch7_5\\testG1.txt";
        SparseGraph g1 = new SparseGraph(13, false);
        ReadGraph readGraph1 = new ReadGraph(g1, filename1);
        Components component1 = new Components(g1);
        System.out.println("TestG1.txt, Component Count: " + component1.count());
        System.out.println();

        // TestG2.txt
        String filename2 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch7_5\\testG2.txt";
        SparseGraph g2 = new SparseGraph(6, false);
        ReadGraph readGraph2 = new ReadGraph(g2, filename2);
        Components component2 = new Components(g2);
        System.out.println("TestG2.txt, Component Count: " + component2.count());
    }
}
public class ReadGraph {

    private Scanner scanner;

    public ReadGraph(Graph graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(v, w);
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + "doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }
}
// 稀疏圖 - 鄰接表
public class SparseGraph implements Graph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Integer>[] g; // 圖的具體數據

    // 構造函數
    public SparseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Integer>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Integer>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v, int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        g[v].add(w);
        if( v != w && !directed )
            g[w].add(v);

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ )
                System.out.print(g[i].elementAt(j) + "\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
7-6 尋路

稀疏圖(鄰接表):O(V+E)
稠密圖(鄰接矩陣):O(V^2)

// 稠密圖 - 鄰接矩陣
public class DenseGraph implements Graph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private boolean[][] g;      // 圖的具體數據

    // 構造函數
    public DenseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲false, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new boolean[n][n];
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        if( hasEdge( v , w ) )
            return;

        g[v][w] = true;
        if( !directed )
            g[w][v] = true;

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w];
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                System.out.print(g[i][j]+"\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Integer> adjV = new Vector<Integer>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] )
                adjV.add(i);
        return adjV;
    }
}
// 圖的接口
public interface Graph {

    public int V();
    public int E();
    public void addEdge( int v , int w );
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Integer> adj(int v);
}
public class Main {

    // 測試尋路算法
    public static void main(String[] args) {

        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch7_6\\testG.txt";
        SparseGraph g = new SparseGraph(7, false);
        ReadGraph readGraph = new ReadGraph(g, filename);
        g.show();
        System.out.println();

        Path path = new Path(g,0);
        System.out.println("Path from 0 to 6 : ");
        path.showPath(6);
    }
}
public class Path {

    private Graph G;   // 圖的引用
    private int s;     // 起始點
    private boolean[] visited;  // 記錄dfs的過程中節點是否被訪問
    private int[] from;         // 記錄路徑, from[i]表示查找的路徑上i的上一個節點

    // 圖的深度優先遍歷
    private void dfs( int v ){
        visited[v] = true;
        for( int i : G.adj(v) )
            if( !visited[i] ){
                from[i] = v;
                dfs(i);
            }
    }

    // 構造函數, 尋路算法, 尋找圖graph從s點到其他點的路徑
    public Path(Graph graph, int s){

        // 算法初始化
        G = graph;
        assert s >= 0 && s < G.V();

        visited = new boolean[G.V()];
        from = new int[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            visited[i] = false;
            from[i] = -1;
        }
        this.s = s;

        // 尋路算法
        dfs(s);
    }

    // 查詢從s點到w點是否有路徑
    boolean hasPath(int w){
        assert w >= 0 && w < G.V();
        return visited[w];
    }

    // 查詢從s點到w點的路徑, 存放在vec中
    Vector<Integer> path(int w){

        assert hasPath(w) ;

        Stack<Integer> s = new Stack<Integer>();
        // 通過from數組逆向查找到從s到w的路徑, 存放到棧中
        int p = w;
        while( p != -1 ){
            s.push(p);
            p = from[p];
        }

        // 從棧中依次取出元素, 獲得順序的從s到w的路徑
        Vector<Integer> res = new Vector<Integer>();
        while( !s.empty() )
            res.add( s.pop() );

        return res;
    }

    // 打印出從s點到w點的路徑
    void showPath(int w){

        assert hasPath(w) ;

        Vector<Integer> vec = path(w);
        for( int i = 0 ; i < vec.size() ; i ++ ){
            System.out.print(vec.elementAt(i));
            if( i == vec.size() - 1 )
                System.out.println();
            else
                System.out.print(" -> ");
        }
    }
}
public class ReadGraph {

    private Scanner scanner;

    public ReadGraph(Graph graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(v, w);
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + "doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }
}
// 稀疏圖 - 鄰接表
public class SparseGraph implements Graph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Integer>[] g; // 圖的具體數據

    // 構造函數
    public SparseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Integer>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Integer>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v, int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        g[v].add(w);
        if( v != w && !directed )
            g[w].add(v);

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ )
                System.out.print(g[i].elementAt(j) + "\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
7-7 廣度優先遍歷和最短路徑
// 稠密圖 - 鄰接矩陣
public class DenseGraph implements Graph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private boolean[][] g;      // 圖的具體數據

    // 構造函數
    public DenseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲false, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new boolean[n][n];
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        if( hasEdge( v , w ) )
            return;

        g[v][w] = true;
        if( !directed )
            g[w][v] = true;

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w];
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                System.out.print(g[i][j]+"\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Integer> adjV = new Vector<Integer>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] )
                adjV.add(i);
        return adjV;
    }
}
// 圖的接口
public interface Graph {

    public int V();
    public int E();
    public void addEdge( int v , int w );
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Integer> adj(int v);
}
public class Main {

    // 測試無權圖最短路徑算法
    public static void main(String[] args) {

        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch7_7\\testG.txt";
        SparseGraph g = new SparseGraph(7, false);
        ReadGraph readGraph = new ReadGraph(g, filename);
        g.show();
        System.out.println();

        // 比較使用深度優先遍歷和廣度優先遍歷獲得路徑的不同
        // 廣度優先遍歷獲得的是無權圖的最短路徑
        Path dfs = new Path(g,0);
        System.out.print("DFS : ");
        dfs.showPath(6);

        ShortestPath bfs = new ShortestPath(g,0);
        System.out.print("BFS : ");
        bfs.showPath(6);
    }
}
public class Path {

    private Graph G;   // 圖的引用
    private int s;     // 起始點
    private boolean[] visited;  // 記錄dfs的過程中節點是否被訪問
    private int[] from;         // 記錄路徑, from[i]表示查找的路徑上i的上一個節點

    // 圖的深度優先遍歷
    private void dfs( int v ){
        visited[v] = true;
        for( int i : G.adj(v) )
            if( !visited[i] ){
                from[i] = v;
                dfs(i);
            }
    }

    // 構造函數, 尋路算法, 尋找圖graph從s點到其他點的路徑
    public Path(Graph graph, int s){

        // 算法初始化
        G = graph;
        assert s >= 0 && s < G.V();

        visited = new boolean[G.V()];
        from = new int[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            visited[i] = false;
            from[i] = -1;
        }
        this.s = s;

        // 尋路算法
        dfs(s);
    }

    // 查詢從s點到w點是否有路徑
    boolean hasPath(int w){
        assert w >= 0 && w < G.V();
        return visited[w];
    }

    // 查詢從s點到w點的路徑, 存放在vec中
    Vector<Integer> path(int w){

        assert hasPath(w) ;

        Stack<Integer> s = new Stack<Integer>();
        // 通過from數組逆向查找到從s到w的路徑, 存放到棧中
        int p = w;
        while( p != -1 ){
            s.push(p);
            p = from[p];
        }

        // 從棧中依次取出元素, 獲得順序的從s到w的路徑
        Vector<Integer> res = new Vector<Integer>();
        while( !s.empty() )
            res.add( s.pop() );

        return res;
    }

    // 打印出從s點到w點的路徑
    void showPath(int w){

        assert hasPath(w) ;

        Vector<Integer> vec = path(w);
        for( int i = 0 ; i < vec.size() ; i ++ ){
            System.out.print(vec.elementAt(i));
            if( i == vec.size() - 1 )
                System.out.println();
            else
                System.out.print(" -> ");
        }
    }
}
public class ReadGraph {

    private Scanner scanner;

    public ReadGraph(Graph graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(v, w);
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + " doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }
}
public class ShortestPath {

    private Graph G;   // 圖的引用
    private int s;     // 起始點
    private boolean[] visited;  // 記錄dfs的過程中節點是否被訪問
    private int[] from;         // 記錄路徑, from[i]表示查找的路徑上i的上一個節點
    private int[] ord;          // 記錄路徑中節點的次序。ord[i]表示i節點在路徑中的次序。


    // 構造函數, 尋路算法, 尋找圖graph從s點到其他點的路徑
    public ShortestPath(Graph graph, int s){

        // 算法初始化
        G = graph;
        assert s >= 0 && s < G.V();

        visited = new boolean[G.V()];
        from = new int[G.V()];
        ord = new int[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            visited[i] = false;
            from[i] = -1;
            ord[i] = -1;
        }
        this.s = s;

        // 無向圖最短路徑算法, 從s開始廣度優先遍歷整張圖
        LinkedList<Integer> q = new LinkedList<Integer>();

        q.push( s );
        visited[s] = true;
        ord[s] = 0;
        while( !q.isEmpty() ){
            int v = q.pop();
            for( int i : G.adj(v) )
                if( !visited[i] ){
                    q.push(i);
                    visited[i] = true;
                    from[i] = v;
                    ord[i] = ord[v] + 1;
                }
        }
    }

    // 查詢從s點到w點是否有路徑
    public boolean hasPath(int w){
        assert w >= 0 && w < G.V();
        return visited[w];
    }

    // 查詢從s點到w點的路徑, 存放在vec中
    public Vector<Integer> path(int w){

        assert hasPath(w) ;

        Stack<Integer> s = new Stack<Integer>();
        // 通過from數組逆向查找到從s到w的路徑, 存放到棧中
        int p = w;
        while( p != -1 ){
            s.push(p);
            p = from[p];
        }

        // 從棧中依次取出元素, 獲得順序的從s到w的路徑
        Vector<Integer> res = new Vector<Integer>();
        while( !s.empty() )
            res.add( s.pop() );

        return res;
    }

    // 打印出從s點到w點的路徑
    public void showPath(int w){

        assert hasPath(w) ;

        Vector<Integer> vec = path(w);
        for( int i = 0 ; i < vec.size() ; i ++ ){
            System.out.print(vec.elementAt(i));
            if( i == vec.size() - 1 )
                System.out.println();
            else
                System.out.print(" -> ");
        }
    }

    // 查看從s點到w點的最短路徑長度
    // 若從s到w不可達,返回-1
    public int length(int w){
        assert w >= 0 && w < G.V();
        return ord[w];
    }
}
// 稀疏圖 - 鄰接表
public class SparseGraph implements Graph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Integer>[] g; // 圖的具體數據

    // 構造函數
    public SparseGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Integer>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Integer>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge( int v, int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        g[v].add(w);
        if( v != w && !directed )
            g[w].add(v);

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ )
                System.out.print(g[i].elementAt(j) + "\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Integer> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
7-8 迷宮生成,ps摳圖–更多無權圖的應用
第8章 最小生成樹
8-1 有權圖
// 稠密圖 - 鄰接矩陣
public class DenseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Edge<Weight>[][] g;         // 圖的具體數據

    // 構造函數
    public DenseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲null, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new Edge[n][n];
        for(int i = 0 ; i < n ; i ++)
            for(int j = 0 ; j < n ; j ++)
                g[i][j] = null;
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        if( hasEdge( e.v() , e.w() ) )
            return;

        g[e.v()][e.w()] = new Edge(e);
        if( e.v() != e.w() && !directed )
            g[e.w()][e.v()] = new Edge(e.w(), e.v(), e.wt());

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w] != null;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                if( g[i][j] != null )
                    System.out.print(g[i][j].wt()+"\t");
                else
                    System.out.print("NULL\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Edge<Weight>> adjV = new Vector<Edge<Weight>>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] != null )
                adjV.add( g[v][i] );
        return adjV;
    }
}
// 邊
public class Edge<Weight extends Number & Comparable> implements Comparable<Edge>{

    private int a, b;    // 邊的兩個端點
    private Weight weight;  // 邊的權值

    public Edge(int a, int b, Weight weight)
    {
        this.a = a;
        this.b = b;
        this.weight = weight;
    }

    public Edge(Edge<Weight> e)
    {
        this.a = e.a;
        this.b = e.b;
        this.weight = e.weight;
    }

    public int v(){ return a;} // 返回第一個頂點
    public int w(){ return b;} // 返回第二個頂點
    public Weight wt(){ return weight;}    // 返回權值

    // 給定一個頂點, 返回另一個頂點
    public int other(int x){
        assert x == a || x == b;
        return x == a ? b : a;
    }

    // 輸出邊的信息
    public String toString(){
        return "" + a + "-" + b + ": " + weight;
    }

    // 邊之間的比較
    public int compareTo(Edge that)
    {
        if( weight.compareTo(that.wt()) < 0 )
            return -1;
        else if ( weight.compareTo(that.wt()) > 0 )
            return +1;
        else
            return  0;
    }
}
public class Main {

    // 測試通過文件讀取圖的信息
    public static void main(String[] args) {

        // 使用兩種圖的存儲方式讀取testG1.txt文件
        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_1\\testG1.txt";
        SparseWeightedGraph<Double> g1 = new SparseWeightedGraph<Double>(8, false);
        ReadWeightedGraph readGraph1 = new ReadWeightedGraph(g1, filename);
        System.out.println("test G1 in Sparse Weighted Graph:");
        g1.show();

        System.out.println();

        DenseWeightedGraph<Double> g2 = new DenseWeightedGraph<Double>(8, false);
        ReadWeightedGraph readGraph2 = new ReadWeightedGraph(g2 , filename );
        System.out.println("test G1 in Dense Graph:");
        g2.show();

        System.out.println();
    }
}
// 通過文件讀取有全圖的信息
public class ReadWeightedGraph{

    private Scanner scanner;

    // 由於文件格式的限制,我們的文件讀取類只能讀取權值爲Double類型的圖
    public ReadWeightedGraph(WeightedGraph<Double> graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                Double weight = scanner.nextDouble();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(new Edge<Double>(v, w, weight));
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + " doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }

}
// 稀疏圖 - 鄰接表
public class SparseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Edge<Weight>>[] g;   // 圖的具體數據

    // 構造函數
    public SparseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Edge<Weight>>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Edge<Weight>>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊, 權值爲weight
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        // 注意, 由於在鄰接表的情況, 查找是否有重邊需要遍歷整個鏈表
        // 我們的程序允許重邊的出現

        g[e.v()].add(new Edge(e));
        if( e.v() != e.w() && !directed )
            g[e.w()].add(new Edge(e.w(), e.v(), e.wt()));

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i).other(v) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ ){
                Edge e = g[i].elementAt(j);
                System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
            }
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
interface WeightedGraph<Weight extends Number & Comparable> {
    public int V();
    public int E();
    public void addEdge(Edge<Weight> e);
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Edge<Weight>> adj(int v);
}
8-2 最小生成樹問題和切分定理

電纜佈線設計
網絡設計
電路設計

針對帶權無向圖
針對連通圖

找V-1條邊
連接V個頂點
總權值最小

把圖中的節點分成兩部分,成爲一個切點(Cut)。

如果一個邊的兩個端點,屬於切分(Cut)不同的兩邊,
這個邊稱爲橫切邊(Crossing Edge).

切分定理:
給定任意切分,橫切邊中權值最小的邊必然屬於最小生成樹。

8-3 Prim算法的第一個實現
// 稠密圖 - 鄰接矩陣
public class DenseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Edge<Weight>[][] g;         // 圖的具體數據

    // 構造函數
    public DenseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲null, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new Edge[n][n];
        for(int i = 0 ; i < n ; i ++)
            for(int j = 0 ; j < n ; j ++)
                g[i][j] = null;
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        if( hasEdge( e.v() , e.w() ) )
            return;

        g[e.v()][e.w()] = new Edge(e);
        if( e.v() != e.w() && !directed )
            g[e.w()][e.v()] = new Edge(e.w(), e.v(), e.wt());

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w] != null;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                if( g[i][j] != null )
                    System.out.print(g[i][j].wt()+"\t");
                else
                    System.out.print("NULL\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Edge<Weight>> adjV = new Vector<Edge<Weight>>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] != null )
                adjV.add( g[v][i] );
        return adjV;
    }
}
// 邊
public class Edge<Weight extends Number & Comparable> implements Comparable<Edge<Weight>>{

    private int a, b;    // 邊的兩個端點
    private Weight weight;  // 邊的權值

    public Edge(int a, int b, Weight weight)
    {
        this.a = a;
        this.b = b;
        this.weight = weight;
    }

    public Edge(Edge<Weight> e)
    {
        this.a = e.a;
        this.b = e.b;
        this.weight = e.weight;
    }

    public int v(){ return a;} // 返回第一個頂點
    public int w(){ return b;} // 返回第二個頂點
    public Weight wt(){ return weight;}    // 返回權值

    // 給定一個頂點, 返回另一個頂點
    public int other(int x){
        assert x == a || x == b;
        return x == a ? b : a;
    }

    // 輸出邊的信息
    public String toString(){
        return "" + a + "-" + b + ": " + weight;
    }

    // 邊之間的比較
    public int compareTo(Edge<Weight> that)
    {
        if( weight.compareTo(that.wt()) < 0 )
            return -1;
        else if ( weight.compareTo(that.wt()) > 0 )
            return +1;
        else
            return  0;
    }
}
// 使用Prim算法求圖的最小生成樹
public class LazyPrimMST<Weight extends Number & Comparable> {

    private WeightedGraph<Weight> G;    // 圖的引用
    private MinHeap<Edge<Weight>> pq;   // 最小堆, 算法輔助數據結構
    private boolean[] marked;           // 標記數組, 在算法運行過程中標記節點i是否被訪問
    private Vector<Edge<Weight>> mst;   // 最小生成樹所包含的所有邊
    private Number mstWeight;           // 最小生成樹的權值

    // 構造函數, 使用Prim算法求圖的最小生成樹
    public LazyPrimMST(WeightedGraph<Weight> graph){

        // 算法初始化
        G = graph;
        pq = new MinHeap<Edge<Weight>>(G.E());
        marked = new boolean[G.V()];
        mst = new Vector<Edge<Weight>>();

        // Lazy Prim
        visit(0);
        while( !pq.isEmpty() ){
            // 使用最小堆找出已經訪問的邊中權值最小的邊
            Edge<Weight> e = pq.extractMin();
            // 如果這條邊的兩端都已經訪問過了, 則扔掉這條邊
            if( marked[e.v()] == marked[e.w()] )
                continue;
            // 否則, 這條邊則應該存在在最小生成樹中
            mst.add( e );

            // 訪問和這條邊連接的還沒有被訪問過的節點
            if( !marked[e.v()] )
                visit( e.v() );
            else
                visit( e.w() );
        }

        // 計算最小生成樹的權值

        mstWeight = mst.elementAt(0).wt();
        for( int i = 1 ; i < mst.size() ; i ++ )
            mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
    }

    // 訪問節點v
    private void visit(int v){

        assert !marked[v];
        marked[v] = true;

        // 將和節點v相連接的所有未訪問的邊放入最小堆中
        for( Edge<Weight> e : G.adj(v) )
            if( !marked[e.other(v)] )
                pq.insert(e);
    }

    // 返回最小生成樹的所有邊
    Vector<Edge<Weight>> mstEdges(){
        return mst;
    };

    // 返回最小生成樹的權值
    Number result(){
        return mstWeight;
    };
}
public class Main {

    public static void main(String[] args) {

        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_3\\testG1.txt";
        int V = 8;

        SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        // Test Lazy Prim MST
        System.out.println("Test Lazy Prim MST:");
        LazyPrimMST<Double> lazyPrimMST = new LazyPrimMST<Double>(g);
        Vector<Edge<Double>> mst = lazyPrimMST.mstEdges();
        for( int i = 0 ; i < mst.size() ; i ++ )
            System.out.println(mst.elementAt(i));
        System.out.println("The MST weight is: " + lazyPrimMST.result());

        System.out.println();
    }
}
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MinHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最小堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MinHeap(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最小堆中取出堆頂元素, 即堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小堆中的堆頂元素
    public Item getMin(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最小堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) > 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) < 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最小值

            if( data[k].compareTo(data[j]) <= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MinHeap
    public static void main(String[] args) {

        MinHeap<Integer> minHeap = new MinHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            minHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將minheap中的數據逐漸使用extractMin取出來
        // 取出來的順序應該是按照從小到大的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = minHeap.extractMin();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從小到大排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] <= arr[i];
    }
}
// 通過文件讀取有全圖的信息
public class ReadWeightedGraph {

    private Scanner scanner;

    // 由於文件格式的限制,我們的文件讀取類只能讀取權值爲Double類型的圖
    public ReadWeightedGraph(WeightedGraph<Double> graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                Double weight = scanner.nextDouble();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(new Edge<Double>(v, w, weight));
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + " doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }

}
// 稀疏圖 - 鄰接表
public class SparseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Edge<Weight>>[] g;   // 圖的具體數據

    // 構造函數
    public SparseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Edge<Weight>>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Edge<Weight>>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊, 權值爲weight
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        // 注意, 由於在鄰接表的情況, 查找是否有重邊需要遍歷整個鏈表
        // 我們的程序允許重邊的出現

        g[e.v()].add(new Edge(e));
        if( e.v() != e.w() && !directed )
            g[e.w()].add(new Edge(e.w(), e.v(), e.wt()));

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i).other(v) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ ){
                Edge e = g[i].elementAt(j);
                System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
            }
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
interface WeightedGraph<Weight extends Number & Comparable> {
    public int V();
    public int E();
    public void addEdge(Edge<Weight> e);
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Edge<Weight>> adj(int v);
}
8-4 Prim算法的優化

Lazy Prim的時間複雜度O(ElogE)

Prim的時間複雜度O(ElogV)

8-5 優化後的Prim算法的實現
// 稠密圖 - 鄰接矩陣
public class DenseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Edge<Weight>[][] g;         // 圖的具體數據

    // 構造函數
    public DenseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲null, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new Edge[n][n];
        for(int i = 0 ; i < n ; i ++)
            for(int j = 0 ; j < n ; j ++)
                g[i][j] = null;
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        if( hasEdge( e.v() , e.w() ) )
            return;

        g[e.v()][e.w()] = new Edge(e);
        if( e.v() != e.w() && !directed )
            g[e.w()][e.v()] = new Edge(e.w(), e.v(), e.wt());

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w] != null;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                if( g[i][j] != null )
                    System.out.print(g[i][j].wt()+"\t");
                else
                    System.out.print("NULL\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Edge<Weight>> adjV = new Vector<Edge<Weight>>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] != null )
                adjV.add( g[v][i] );
        return adjV;
    }
}
// 邊
public class Edge<Weight extends Number & Comparable> implements Comparable<Edge<Weight>>{

    private int a, b;    // 邊的兩個端點
    private Weight weight;  // 邊的權值

    public Edge(int a, int b, Weight weight)
    {
        this.a = a;
        this.b = b;
        this.weight = weight;
    }

    public Edge(Edge<Weight> e)
    {
        this.a = e.a;
        this.b = e.b;
        this.weight = e.weight;
    }

    public int v(){ return a;} // 返回第一個頂點
    public int w(){ return b;} // 返回第二個頂點
    public Weight wt(){ return weight;}    // 返回權值

    // 給定一個頂點, 返回另一個頂點
    public int other(int x){
        assert x == a || x == b;
        return x == a ? b : a;
    }

    // 輸出邊的信息
    public String toString(){
        return "" + a + "-" + b + ": " + weight;
    }

    // 邊之間的比較
    public int compareTo(Edge<Weight> that)
    {
        if( weight.compareTo(that.wt()) < 0 )
            return -1;
        else if ( weight.compareTo(that.wt()) > 0 )
            return +1;
        else
            return  0;
    }
}
// 最小索引堆
public class IndexMinHeap<Item extends Comparable> {

    protected Item[] data;      // 最小索引堆中的數據
    protected int[] indexes;    // 最小索引堆中的索引, indexes[x] = i 表示索引i在x的位置
    protected int[] reverse;    // 最小索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public IndexMinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        indexes = new int[capacity+1];
        reverse = new int[capacity+1];
        for( int i = 0 ; i <= capacity ; i ++ )
            reverse[i] = 0;

        count = 0;
        this.capacity = capacity;
    }

    // 返回索引堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示索引堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小索引堆中插入一個新的元素, 新元素的索引爲i, 元素爲item
    // 傳入的i對用戶而言,是從0索引的
    public void insert(int i, Item item){

        assert count + 1 <= capacity;
        assert i + 1 >= 1 && i + 1 <= capacity;

        // 再插入一個新元素前,還需要保證索引i所在的位置是沒有元素的。
        assert !contain(i);

        i += 1;
        data[i] = item;
        indexes[count+1] = i;
        reverse[i] = count + 1;
        count ++;

        shiftUp(count);
    }

    // 從最小索引堆中取出堆頂元素, 即索引堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;

        Item ret = data[indexes[1]];
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 從最小索引堆中取出堆頂元素的索引
    public int extractMinIndex(){
        assert count > 0;

        int ret = indexes[1] - 1;
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小索引堆中的堆頂元素
    public Item getMin(){
        assert count > 0;
        return data[indexes[1]];
    }

    // 獲取最小索引堆中的堆頂元素的索引
    public int getMinIndex(){
        assert count > 0;
        return indexes[1]-1;
    }

    // 看索引i所在的位置是否存在元素
    boolean contain( int i ){
        assert  i + 1 >= 1 && i + 1 <= capacity;
        return reverse[i+1] != 0;
    }

    // 獲取最小索引堆中索引爲i的元素
    public Item getItem( int i ){
        assert contain(i);
        return data[i+1];
    }

    // 將最小索引堆中索引爲i的元素修改爲newItem
    public void change( int i , Item newItem ){

        assert contain(i);

        i += 1;
        data[i] = newItem;

        // 有了 reverse 之後,
        // 我們可以非常簡單的通過reverse直接定位索引i在indexes中的位置
        shiftUp( reverse[i] );
        shiftDown( reverse[i] );
    }

    // 交換索引堆中的索引i和j
    // 由於有了反向索引reverse數組,
    // indexes數組發生改變以後, 相應的就需要維護reverse數組
    private void swapIndexes(int i, int j){
        int t = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = t;

        reverse[indexes[i]] = i;
        reverse[indexes[j]] = j;
    }

    //********************
    //* 最小索引堆核心輔助函數
    //********************

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftUp(int k){

        while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) > 0 ){
            swapIndexes(k, k/2);
            k /= 2;
        }
    }

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) < 0 )
                j ++;

            if( data[indexes[k]].compareTo(data[indexes[j]]) <= 0 )
                break;

            swapIndexes(k, j);
            k = j;
        }
    }

    // 測試 IndexMinHeap
    public static void main(String[] args) {

        int N = 1000000;
        IndexMinHeap<Integer> indexMinHeap = new IndexMinHeap<Integer>(N);
        for( int i = 0 ; i < N ; i ++ )
            indexMinHeap.insert( i , (int)(Math.random()*N) );

    }
}
// 使用Prim算法求圖的最小生成樹
public class LazyPrimMST<Weight extends Number & Comparable> {

    private WeightedGraph<Weight> G;    // 圖的引用
    private MinHeap<Edge<Weight>> pq;   // 最小堆, 算法輔助數據結構
    private boolean[] marked;           // 標記數組, 在算法運行過程中標記節點i是否被訪問
    private Vector<Edge<Weight>> mst;   // 最小生成樹所包含的所有邊
    private Number mstWeight;           // 最小生成樹的權值

    // 構造函數, 使用Prim算法求圖的最小生成樹
    public LazyPrimMST(WeightedGraph<Weight> graph){

        // 算法初始化
        G = graph;
        pq = new MinHeap<Edge<Weight>>(G.E());
        marked = new boolean[G.V()];
        mst = new Vector<Edge<Weight>>();

        // Lazy Prim
        visit(0);
        while( !pq.isEmpty() ){
            // 使用最小堆找出已經訪問的邊中權值最小的邊
            Edge<Weight> e = pq.extractMin();
            // 如果這條邊的兩端都已經訪問過了, 則扔掉這條邊
            if( marked[e.v()] == marked[e.w()] )
                continue;
            // 否則, 這條邊則應該存在在最小生成樹中
            mst.add( e );

            // 訪問和這條邊連接的還沒有被訪問過的節點
            if( !marked[e.v()] )
                visit( e.v() );
            else
                visit( e.w() );
        }

        // 計算最小生成樹的權值
        mstWeight = mst.elementAt(0).wt();
        for( int i = 1 ; i < mst.size() ; i ++ )
            mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
    }

    // 訪問節點v
    private void visit(int v){

        assert !marked[v];
        marked[v] = true;

        // 將和節點v相連接的所有未訪問的邊放入最小堆中
        for( Edge<Weight> e : G.adj(v) )
            if( !marked[e.other(v)] )
                pq.insert(e);
    }

    // 返回最小生成樹的所有邊
    Vector<Edge<Weight>> mstEdges(){
        return mst;
    }

    // 返回最小生成樹的權值
    Number result(){
        return mstWeight;
    }


    // 測試 Lazy Prim
    public static void main(String[] args) {

        String filename = "testG1.txt";
        int V = 8;

        SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        // Test Lazy Prim MST
        System.out.println("Test Lazy Prim MST:");
        LazyPrimMST<Double> lazyPrimMST = new LazyPrimMST<Double>(g);
        Vector<Edge<Double>> mst = lazyPrimMST.mstEdges();
        for( int i = 0 ; i < mst.size() ; i ++ )
            System.out.println(mst.elementAt(i));
        System.out.println("The MST weight is: " + lazyPrimMST.result());

        System.out.println();
    }
}
public class Main {

    // 測試我們實現的兩種Prim算法的性能差距
    // 可以看出這一節使用索引堆實現的Prim算法優於上一小節的Lazy Prim算法
    public static void main(String[] args) {

        String filename1 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_5\\testG1.txt";
        int V1 = 8;

        String filename2 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_5\\testG2.txt";
        int V2 = 250;

        String filename3 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_5\\testG3.txt";
        int V3 = 1000;

        String filename4 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_5\\testG4.txt";
        int V4 = 10000;

        //String filename5 = "testG5.txt";
        //int V5 = 1000000;


        // 文件讀取
        SparseWeightedGraph<Double> g1 = new SparseWeightedGraph<Double>(V1, false);
        ReadWeightedGraph readGraph1 = new ReadWeightedGraph(g1, filename1);
        System.out.println( filename1 + " load successfully.");

        SparseWeightedGraph<Double> g2 = new SparseWeightedGraph<Double>(V2, false);
        ReadWeightedGraph readGraph2 = new ReadWeightedGraph(g2, filename2);
        System.out.println( filename2 + " load successfully.");

        SparseWeightedGraph<Double> g3 = new SparseWeightedGraph<Double>(V3, false);
        ReadWeightedGraph readGraph3 = new ReadWeightedGraph(g3, filename3);
        System.out.println( filename3 + " load successfully.");

        SparseWeightedGraph<Double> g4 = new SparseWeightedGraph<Double>(V4, false);
        ReadWeightedGraph readGraph4 = new ReadWeightedGraph(g4, filename4);
        System.out.println( filename4 + " load successfully.");

//        SparseWeightedGraph<Double> g5 = new SparseWeightedGraph<Double>(V5, false);
//        ReadWeightedGraph readGraph5 = new ReadWeightedGraph(g5, filename5);
//        System.out.println( filename5 + " load successfully.");

        System.out.println();


        long startTime, endTime;

        // Test Lazy Prim MST
        System.out.println("Test Lazy Prim MST:");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST1 = new LazyPrimMST<Double>(g1);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G1: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST2 = new LazyPrimMST<Double>(g2);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G2: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST3 = new LazyPrimMST<Double>(g3);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G3: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST4 = new LazyPrimMST<Double>(g4);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G4: " + (endTime-startTime) + "ms.");

//        startTime = System.currentTimeMillis();
//        LazyPrimMST<Double> lazyPrimMST5 = new LazyPrimMST<Double>(g5);
//        endTime = System.currentTimeMillis();
//        System.out.println("Test for G5: " + (endTime-startTime) + "ms.");

        System.out.println();


        // Test Prim MST
        System.out.println("Test Prim MST:");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST1 = new PrimMST<Double>(g1);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G1: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST2 = new PrimMST<Double>(g2);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G2: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST3 = new PrimMST<Double>(g3);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G3: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST4 = new PrimMST<Double>(g4);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G4: " + (endTime-startTime) + "ms.");

//        startTime = System.currentTimeMillis();
//        PrimMST<Double> primMST5 = new PrimMST<Double>(g5);
//        endTime = System.currentTimeMillis();
//        System.out.println("Test for G5: " + (endTime-startTime) + "ms.");

        System.out.println();
    }
}
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MinHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最小堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MinHeap(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最小堆中取出堆頂元素, 即堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小堆中的堆頂元素
    public Item getMin(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最小堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) > 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) < 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最小值

            if( data[k].compareTo(data[j]) <= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MinHeap
    public static void main(String[] args) {

        MinHeap<Integer> minHeap = new MinHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            minHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將minheap中的數據逐漸使用extractMin取出來
        // 取出來的順序應該是按照從小到大的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = minHeap.extractMin();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從小到大排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] <= arr[i];
    }
}
// 使用優化的Prim算法求圖的最小生成樹
public class PrimMST<Weight extends Number & Comparable> {

    private WeightedGraph G;              // 圖的引用
    private IndexMinHeap<Weight> ipq;     // 最小索引堆, 算法輔助數據結構
    private Edge<Weight>[] edgeTo;        // 訪問的點所對應的邊, 算法輔助數據結構
    private boolean[] marked;             // 標記數組, 在算法運行過程中標記節點i是否被訪問
    private Vector<Edge<Weight>> mst;     // 最小生成樹所包含的所有邊
    private Number mstWeight;             // 最小生成樹的權值

    // 構造函數, 使用Prim算法求圖的最小生成樹
    public PrimMST(WeightedGraph graph){

        G = graph;
        assert( graph.E() >= 1 );
        ipq = new IndexMinHeap<Weight>(graph.V());

        // 算法初始化
        marked = new boolean[G.V()];
        edgeTo = new Edge[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            marked[i] = false;
            edgeTo[i] = null;
        }
        mst = new Vector<Edge<Weight>>();

        // Prim
        visit(0);
        while( !ipq.isEmpty() ){
            // 使用最小索引堆找出已經訪問的邊中權值最小的邊
            // 最小索引堆中存儲的是點的索引, 通過點的索引找到相對應的邊
            int v = ipq.extractMinIndex();
            assert( edgeTo[v] != null );
            mst.add( edgeTo[v] );
            visit( v );
        }

        // 計算最小生成樹的權值
        mstWeight = mst.elementAt(0).wt();
        for( int i = 1 ; i < mst.size() ; i ++ )
            mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
    }

    // 訪問節點v
    void visit(int v){

        assert !marked[v];
        marked[v] = true;

        // 將和節點v相連接的未訪問的另一端點, 和與之相連接的邊, 放入最小堆中
        for( Object item : G.adj(v) ){
            Edge<Weight> e = (Edge<Weight>)item;
            int w = e.other(v);
            // 如果邊的另一端點未被訪問
            if( !marked[w] ){
                // 如果從沒有考慮過這個端點, 直接將這個端點和與之相連接的邊加入索引堆
                if( edgeTo[w] == null ){
                    edgeTo[w] = e;
                    ipq.insert(w, e.wt());
                }
                // 如果曾經考慮這個端點, 但現在的邊比之前考慮的邊更短, 則進行替換
                else if( e.wt().compareTo(edgeTo[w].wt()) < 0 ){
                    edgeTo[w] = e;
                    ipq.change(w, e.wt());
                }
            }
        }

    }

    // 返回最小生成樹的所有邊
    Vector<Edge<Weight>> mstEdges(){
        return mst;
    }

    // 返回最小生成樹的權值
    Number result(){
        return mstWeight;
    }


    // 測試 Prim
    public static void main(String[] args) {

        String filename = "testG1.txt";
        int V = 8;

        SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        // Test Prim MST
        System.out.println("Test Prim MST:");
        PrimMST<Double> primMST = new PrimMST<Double>(g);
        Vector<Edge<Double>> mst = primMST.mstEdges();
        for( int i = 0 ; i < mst.size() ; i ++ )
            System.out.println(mst.elementAt(i));
        System.out.println("The MST weight is: " + primMST.result());

        System.out.println();
    }
}
// 通過文件讀取有全圖的信息
public class ReadWeightedGraph {

    private Scanner scanner;

    // 由於文件格式的限制,我們的文件讀取類只能讀取權值爲Double類型的圖
    public ReadWeightedGraph(WeightedGraph<Double> graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                Double weight = scanner.nextDouble();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(new Edge<Double>(v, w, weight));
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + " doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }

}
// 稀疏圖 - 鄰接表
public class SparseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Edge<Weight>>[] g;   // 圖的具體數據

    // 構造函數
    public SparseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Edge<Weight>>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Edge<Weight>>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊, 權值爲weight
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        // 注意, 由於在鄰接表的情況, 查找是否有重邊需要遍歷整個鏈表
        // 我們的程序允許重邊的出現

        g[e.v()].add(new Edge(e));
        if( e.v() != e.w() && !directed )
            g[e.w()].add(new Edge(e.w(), e.v(), e.wt()));

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i).other(v) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ ){
                Edge e = g[i].elementAt(j);
                System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
            }
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
interface WeightedGraph<Weight extends Number & Comparable> {
    public int V();
    public int E();
    public void addEdge(Edge<Weight> e);
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Edge<Weight>> adj(int v);
}
8-6 Krusk算法
// 稠密圖 - 鄰接矩陣
public class DenseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Edge<Weight>[][] g;         // 圖的具體數據

    // 構造函數
    public DenseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲null, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new Edge[n][n];
        for(int i = 0 ; i < n ; i ++)
            for(int j = 0 ; j < n ; j ++)
                g[i][j] = null;
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        if( hasEdge( e.v() , e.w() ) )
            return;

        g[e.v()][e.w()] = new Edge(e);
        if( e.v() != e.w() && !directed )
            g[e.w()][e.v()] = new Edge(e.w(), e.v(), e.wt());

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w] != null;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                if( g[i][j] != null )
                    System.out.print(g[i][j].wt()+"\t");
                else
                    System.out.print("NULL\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Edge<Weight>> adjV = new Vector<Edge<Weight>>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] != null )
                adjV.add( g[v][i] );
        return adjV;
    }
}
// 邊
public class Edge<Weight extends Number & Comparable> implements Comparable<Edge<Weight>>{

    private int a, b;    // 邊的兩個端點
    private Weight weight;  // 邊的權值

    public Edge(int a, int b, Weight weight)
    {
        this.a = a;
        this.b = b;
        this.weight = weight;
    }

    public Edge(Edge<Weight> e)
    {
        this.a = e.a;
        this.b = e.b;
        this.weight = e.weight;
    }

    public int v(){ return a;} // 返回第一個頂點
    public int w(){ return b;} // 返回第二個頂點
    public Weight wt(){ return weight;}    // 返回權值

    // 給定一個頂點, 返回另一個頂點
    public int other(int x){
        assert x == a || x == b;
        return x == a ? b : a;
    }

    // 輸出邊的信息
    public String toString(){
        return "" + a + "-" + b + ": " + weight;
    }

    // 邊之間的比較
    public int compareTo(Edge<Weight> that)
    {
        if( weight.compareTo(that.wt()) < 0 )
            return -1;
        else if ( weight.compareTo(that.wt()) > 0 )
            return +1;
        else
            return  0;
    }
}
// 最小索引堆
public class IndexMinHeap<Item extends Comparable> {

    protected Item[] data;      // 最小索引堆中的數據
    protected int[] indexes;    // 最小索引堆中的索引, indexes[x] = i 表示索引i在x的位置
    protected int[] reverse;    // 最小索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public IndexMinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        indexes = new int[capacity+1];
        reverse = new int[capacity+1];
        for( int i = 0 ; i <= capacity ; i ++ )
            reverse[i] = 0;

        count = 0;
        this.capacity = capacity;
    }

    // 返回索引堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示索引堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小索引堆中插入一個新的元素, 新元素的索引爲i, 元素爲item
    // 傳入的i對用戶而言,是從0索引的
    public void insert(int i, Item item){

        assert count + 1 <= capacity;
        assert i + 1 >= 1 && i + 1 <= capacity;

        // 再插入一個新元素前,還需要保證索引i所在的位置是沒有元素的。
        assert !contain(i);

        i += 1;
        data[i] = item;
        indexes[count+1] = i;
        reverse[i] = count + 1;
        count ++;

        shiftUp(count);
    }

    // 從最小索引堆中取出堆頂元素, 即索引堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;

        Item ret = data[indexes[1]];
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 從最小索引堆中取出堆頂元素的索引
    public int extractMinIndex(){
        assert count > 0;

        int ret = indexes[1] - 1;
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小索引堆中的堆頂元素
    public Item getMin(){
        assert count > 0;
        return data[indexes[1]];
    }

    // 獲取最小索引堆中的堆頂元素的索引
    public int getMinIndex(){
        assert count > 0;
        return indexes[1]-1;
    }

    // 看索引i所在的位置是否存在元素
    boolean contain( int i ){
        assert  i + 1 >= 1 && i + 1 <= capacity;
        return reverse[i+1] != 0;
    }

    // 獲取最小索引堆中索引爲i的元素
    public Item getItem( int i ){
        assert contain(i);
        return data[i+1];
    }

    // 將最小索引堆中索引爲i的元素修改爲newItem
    public void change( int i , Item newItem ){

        assert contain(i);

        i += 1;
        data[i] = newItem;

        // 有了 reverse 之後,
        // 我們可以非常簡單的通過reverse直接定位索引i在indexes中的位置
        shiftUp( reverse[i] );
        shiftDown( reverse[i] );
    }

    // 交換索引堆中的索引i和j
    // 由於有了反向索引reverse數組,
    // indexes數組發生改變以後, 相應的就需要維護reverse數組
    private void swapIndexes(int i, int j){
        int t = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = t;

        reverse[indexes[i]] = i;
        reverse[indexes[j]] = j;
    }

    //********************
    //* 最小索引堆核心輔助函數
    //********************

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftUp(int k){

        while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) > 0 ){
            swapIndexes(k, k/2);
            k /= 2;
        }
    }

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) < 0 )
                j ++;

            if( data[indexes[k]].compareTo(data[indexes[j]]) <= 0 )
                break;

            swapIndexes(k, j);
            k = j;
        }
    }

    // 測試 IndexMinHeap
    public static void main(String[] args) {

        int N = 1000000;
        IndexMinHeap<Integer> indexMinHeap = new IndexMinHeap<Integer>(N);
        for( int i = 0 ; i < N ; i ++ )
            indexMinHeap.insert( i , (int)(Math.random()*N) );

    }
}
// Kruskal算法求最小生成樹
public class KruskalMST<Weight extends Number & Comparable> {

    private Vector<Edge<Weight>> mst;   // 最小生成樹所包含的所有邊
    private Number mstWeight;           // 最小生成樹的權值

    // 構造函數, 使用Kruskal算法計算graph的最小生成樹
    public KruskalMST(WeightedGraph graph){

        mst = new Vector<Edge<Weight>>();

        // 將圖中的所有邊存放到一個最小堆中
        MinHeap<Edge<Weight>> pq = new MinHeap<Edge<Weight>>( graph.E() );
        for( int i = 0 ; i < graph.V() ; i ++ )
            for( Object item : graph.adj(i) ){
                Edge<Weight> e = (Edge<Weight>)item;
                if( e.v() <= e.w() )
                    pq.insert(e);
            }

        // 創建一個並查集, 來查看已經訪問的節點的聯通情況
        UnionFind uf = new UnionFind(graph.V());
        while( !pq.isEmpty() && mst.size() < graph.V() - 1 ){

            // 從最小堆中依次從小到大取出所有的邊
            Edge<Weight> e = pq.extractMin();
            // 如果該邊的兩個端點是聯通的, 說明加入這條邊將產生環, 扔掉這條邊
            if( uf.isConnected( e.v() , e.w() ) )
                continue;

            // 否則, 將這條邊添加進最小生成樹, 同時標記邊的兩個端點聯通
            mst.add( e );
            uf.unionElements( e.v() , e.w() );
        }

        // 計算最小生成樹的權值
        mstWeight = mst.elementAt(0).wt();
        for( int i = 1 ; i < mst.size() ; i ++ )
            mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
    }

    // 返回最小生成樹的所有邊
    Vector<Edge<Weight>> mstEdges(){
        return mst;
    }

    // 返回最小生成樹的權值
    Number result(){
        return mstWeight;
    }


    // 測試 Kruskal
    public static void main(String[] args) {

        String filename = "testG1.txt";
        int V = 8;

        SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        // Test Kruskal
        System.out.println("Test Kruskal:");
        KruskalMST<Double> kruskalMST = new KruskalMST<Double>(g);
        Vector<Edge<Double>> mst = kruskalMST.mstEdges();
        for( int i = 0 ; i < mst.size() ; i ++ )
            System.out.println(mst.elementAt(i));
        System.out.println("The MST weight is: " + kruskalMST.result());

        System.out.println();
    }
}
// 使用Prim算法求圖的最小生成樹
public class LazyPrimMST<Weight extends Number & Comparable> {

    private WeightedGraph<Weight> G;    // 圖的引用
    private MinHeap<Edge<Weight>> pq;   // 最小堆, 算法輔助數據結構
    private boolean[] marked;           // 標記數組, 在算法運行過程中標記節點i是否被訪問
    private Vector<Edge<Weight>> mst;   // 最小生成樹所包含的所有邊
    private Number mstWeight;           // 最小生成樹的權值

    // 構造函數, 使用Prim算法求圖的最小生成樹
    public LazyPrimMST(WeightedGraph<Weight> graph){

        // 算法初始化
        G = graph;
        pq = new MinHeap<Edge<Weight>>(G.E());
        marked = new boolean[G.V()];
        mst = new Vector<Edge<Weight>>();

        // Lazy Prim
        visit(0);
        while( !pq.isEmpty() ){
            // 使用最小堆找出已經訪問的邊中權值最小的邊
            Edge<Weight> e = pq.extractMin();
            // 如果這條邊的兩端都已經訪問過了, 則扔掉這條邊
            if( marked[e.v()] == marked[e.w()] )
                continue;
            // 否則, 這條邊則應該存在在最小生成樹中
            mst.add( e );

            // 訪問和這條邊連接的還沒有被訪問過的節點
            if( !marked[e.v()] )
                visit( e.v() );
            else
                visit( e.w() );
        }

        // 計算最小生成樹的權值
        mstWeight = mst.elementAt(0).wt();
        for( int i = 1 ; i < mst.size() ; i ++ )
            mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
    }

    // 訪問節點v
    private void visit(int v){

        assert !marked[v];
        marked[v] = true;

        // 將和節點v相連接的所有未訪問的邊放入最小堆中
        for( Edge<Weight> e : G.adj(v) )
            if( !marked[e.other(v)] )
                pq.insert(e);
    }

    // 返回最小生成樹的所有邊
    Vector<Edge<Weight>> mstEdges(){
        return mst;
    }

    // 返回最小生成樹的權值
    Number result(){
        return mstWeight;
    }


    // 測試 Lazy Prim
    public static void main(String[] args) {

        String filename = "testG1.txt";
        int V = 8;

        SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        // Test Lazy Prim MST
        System.out.println("Test Lazy Prim MST:");
        LazyPrimMST<Double> lazyPrimMST = new LazyPrimMST<Double>(g);
        Vector<Edge<Double>> mst = lazyPrimMST.mstEdges();
        for( int i = 0 ; i < mst.size() ; i ++ )
            System.out.println(mst.elementAt(i));
        System.out.println("The MST weight is: " + lazyPrimMST.result());

        System.out.println();
    }
}
public class Main {

    // 比較Lazy Prim, Prim和Kruskal的時間性能
    public static void main(String[] args) {

        String filename1 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_6\\testG1.txt";
        int V1 = 8;

        String filename2 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_6\\testG2.txt";
        int V2 = 250;

        String filename3 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_6\\testG3.txt";
        int V3 = 1000;

        String filename4 = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch8_6\\testG4.txt";
        int V4 = 10000;

        //String filename5 = "testG5.txt";
        //int V5 = 1000000;


        // 文件讀取
        SparseWeightedGraph<Double> g1 = new SparseWeightedGraph<Double>(V1, false);
        ReadWeightedGraph readGraph1 = new ReadWeightedGraph(g1, filename1);
        System.out.println( filename1 + " load successfully.");

        SparseWeightedGraph<Double> g2 = new SparseWeightedGraph<Double>(V2, false);
        ReadWeightedGraph readGraph2 = new ReadWeightedGraph(g2, filename2);
        System.out.println( filename2 + " load successfully.");

        SparseWeightedGraph<Double> g3 = new SparseWeightedGraph<Double>(V3, false);
        ReadWeightedGraph readGraph3 = new ReadWeightedGraph(g3, filename3);
        System.out.println( filename3 + " load successfully.");

        SparseWeightedGraph<Double> g4 = new SparseWeightedGraph<Double>(V4, false);
        ReadWeightedGraph readGraph4 = new ReadWeightedGraph(g4, filename4);
        System.out.println( filename4 + " load successfully.");

//        SparseWeightedGraph<Double> g5 = new SparseWeightedGraph<Double>(V5, false);
//        ReadWeightedGraph readGraph5 = new ReadWeightedGraph(g5, filename5);
//        System.out.println( filename5 + " load successfully.");

        System.out.println();


        long startTime, endTime;

        // Test Lazy Prim MST
        System.out.println("Test Lazy Prim MST:");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST1 = new LazyPrimMST<Double>(g1);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G1: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST2 = new LazyPrimMST<Double>(g2);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G2: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST3 = new LazyPrimMST<Double>(g3);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G3: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        LazyPrimMST<Double> lazyPrimMST4 = new LazyPrimMST<Double>(g4);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G4: " + (endTime-startTime) + "ms.");

//        startTime = System.currentTimeMillis();
//        LazyPrimMST<Double> lazyPrimMST5 = new LazyPrimMST<Double>(g5);
//        endTime = System.currentTimeMillis();
//        System.out.println("Test for G5: " + (endTime-startTime) + "ms.");

        System.out.println();


        // Test Prim MST
        System.out.println("Test Prim MST:");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST1 = new PrimMST<Double>(g1);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G1: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST2 = new PrimMST<Double>(g2);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G2: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST3 = new PrimMST<Double>(g3);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G3: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        PrimMST<Double> primMST4 = new PrimMST<Double>(g4);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G4: " + (endTime-startTime) + "ms.");

//        startTime = System.currentTimeMillis();
//        PrimMST<Double> primMST5 = new PrimMST<Double>(g5);
//        endTime = System.currentTimeMillis();
//        System.out.println("Test for G5: " + (endTime-startTime) + "ms.");

        System.out.println();


        // Test Kruskal MST
        System.out.println("Test Kruskal MST:");

        startTime = System.currentTimeMillis();
        KruskalMST<Double> kruskalMST1 = new KruskalMST<Double>(g1);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G1: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        KruskalMST<Double> kruskalMST2 = new KruskalMST<Double>(g2);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G2: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        KruskalMST<Double> kruskalMST3 = new KruskalMST<Double>(g3);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G3: " + (endTime-startTime) + "ms.");

        startTime = System.currentTimeMillis();
        KruskalMST<Double> kruskalMST4 = new KruskalMST<Double>(g4);
        endTime = System.currentTimeMillis();
        System.out.println("Test for G4: " + (endTime-startTime) + "ms.");

//        startTime = System.currentTimeMillis();
//        KruskalMST<Double> kruskalMST5 = new KruskalMST<Double>(g5);
//        endTime = System.currentTimeMillis();
//        System.out.println("Test for G5: " + (endTime-startTime) + "ms.");

        System.out.println();
    }
}
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MinHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最小堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MinHeap(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最小堆中取出堆頂元素, 即堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小堆中的堆頂元素
    public Item getMin(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最小堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) > 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) < 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最小值

            if( data[k].compareTo(data[j]) <= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MinHeap
    public static void main(String[] args) {

        MinHeap<Integer> minHeap = new MinHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            minHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將minheap中的數據逐漸使用extractMin取出來
        // 取出來的順序應該是按照從小到大的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = minHeap.extractMin();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從小到大排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] <= arr[i];
    }
}
// 使用優化的Prim算法求圖的最小生成樹
public class PrimMST<Weight extends Number & Comparable> {

    private WeightedGraph G;              // 圖的引用
    private IndexMinHeap<Weight> ipq;     // 最小索引堆, 算法輔助數據結構
    private Edge<Weight>[] edgeTo;        // 訪問的點所對應的邊, 算法輔助數據結構
    private boolean[] marked;             // 標記數組, 在算法運行過程中標記節點i是否被訪問
    private Vector<Edge<Weight>> mst;     // 最小生成樹所包含的所有邊
    private Number mstWeight;             // 最小生成樹的權值

    // 構造函數, 使用Prim算法求圖的最小生成樹
    public PrimMST(WeightedGraph graph){

        G = graph;
        assert( graph.E() >= 1 );
        ipq = new IndexMinHeap<Weight>(graph.V());

        // 算法初始化
        marked = new boolean[G.V()];
        edgeTo = new Edge[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            marked[i] = false;
            edgeTo[i] = null;
        }
        mst = new Vector<Edge<Weight>>();

        // Prim
        visit(0);
        while( !ipq.isEmpty() ){
            // 使用最小索引堆找出已經訪問的邊中權值最小的邊
            // 最小索引堆中存儲的是點的索引, 通過點的索引找到相對應的邊
            int v = ipq.extractMinIndex();
            assert( edgeTo[v] != null );
            mst.add( edgeTo[v] );
            visit( v );
        }

        // 計算最小生成樹的權值
        mstWeight = mst.elementAt(0).wt();
        for( int i = 1 ; i < mst.size() ; i ++ )
            mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
    }

    // 訪問節點v
    void visit(int v){

        assert !marked[v];
        marked[v] = true;

        // 將和節點v相連接的未訪問的另一端點, 和與之相連接的邊, 放入最小堆中
        for( Object item : G.adj(v) ){
            Edge<Weight> e = (Edge<Weight>)item;
            int w = e.other(v);
            // 如果邊的另一端點未被訪問
            if( !marked[w] ){
                // 如果從沒有考慮過這個端點, 直接將這個端點和與之相連接的邊加入索引堆
                if( edgeTo[w] == null ){
                    edgeTo[w] = e;
                    ipq.insert(w, e.wt());
                }
                // 如果曾經考慮這個端點, 但現在的邊比之前考慮的邊更短, 則進行替換
                else if( e.wt().compareTo(edgeTo[w].wt()) < 0 ){
                    edgeTo[w] = e;
                    ipq.change(w, e.wt());
                }
            }
        }

    }

    // 返回最小生成樹的所有邊
    Vector<Edge<Weight>> mstEdges(){
        return mst;
    }

    // 返回最小生成樹的權值
    Number result(){
        return mstWeight;
    }


    // 測試 Prim
    public static void main(String[] args) {

        String filename = "testG1.txt";
        int V = 8;

        SparseWeightedGraph<Double> g = new SparseWeightedGraph<Double>(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        // Test Prim MST
        System.out.println("Test Prim MST:");
        PrimMST<Double> primMST = new PrimMST<Double>(g);
        Vector<Edge<Double>> mst = primMST.mstEdges();
        for( int i = 0 ; i < mst.size() ; i ++ )
            System.out.println(mst.elementAt(i));
        System.out.println("The MST weight is: " + primMST.result());

        System.out.println();
    }
}
// 通過文件讀取有全圖的信息
public class ReadWeightedGraph {

    private Scanner scanner;

    // 由於文件格式的限制,我們的文件讀取類只能讀取權值爲Double類型的圖
    public ReadWeightedGraph(WeightedGraph<Double> graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                Double weight = scanner.nextDouble();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(new Edge<Double>(v, w, weight));
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + " doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }

}
// 稀疏圖 - 鄰接表
public class SparseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Edge<Weight>>[] g;   // 圖的具體數據

    // 構造函數
    public SparseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Edge<Weight>>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Edge<Weight>>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊, 權值爲weight
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        // 注意, 由於在鄰接表的情況, 查找是否有重邊需要遍歷整個鏈表
        // 我們的程序允許重邊的出現

        g[e.v()].add(new Edge(e));
        if( e.v() != e.w() && !directed )
            g[e.w()].add(new Edge(e.w(), e.v(), e.wt()));

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i).other(v) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ ){
                Edge e = g[i].elementAt(j);
                System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
            }
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
// Union-Find
public class UnionFind {

    // rank[i]表示以i爲根的集合所表示的樹的層數
    // 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
    // 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
    // 關於這個問題,可以參考問答區:http://coding.imooc.com/learn/questiondetail/7287.html
    private int[] rank;
    private int[] parent; // parent[i]表示第i個元素所指向的父節點
    private int count;    // 數據個數

    // 構造函數
    public UnionFind(int count){
        rank = new int[count];
        parent = new int[count];
        this.count = count;
        // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
        for( int i = 0 ; i < count ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    // 查找過程, 查找元素p所對應的集合編號
    // O(h)複雜度, h爲樹的高度
    int find(int p){
        assert( p >= 0 && p < count );

        // path compression 1
        while( p != parent[p] ){
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }

    // 查看元素p和元素q是否所屬一個集合
    // O(h)複雜度, h爲樹的高度
    boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合併元素p和元素q所屬的集合
    // O(h)複雜度, h爲樹的高度
    void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        // 根據兩個元素所在樹的元素個數不同判斷合併方向
        // 將元素個數少的集合合併到元素個數多的集合上
        if( rank[pRoot] < rank[qRoot] ){
            parent[pRoot] = qRoot;
        }
        else if( rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此時, 我維護rank的值
        }
    }
}
interface WeightedGraph<Weight extends Number & Comparable> {
    public int V();
    public int E();
    public void addEdge(Edge<Weight> e);
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Edge<Weight>> adj(int v);
}
8-7 最小生成樹算法的思考

Lazy Prim O(ElogE)
Prim O(ElogV)
Kruskal O(ElogE)

如果橫切邊有相等的邊

根據算法的具體實現,每次選擇一個邊
此時,圖存在多個最小生成樹

Vyssotsky’s Algorithm
將邊逐漸添加到生成樹中
一旦形成環,刪除環中權值最大的邊

第9章 最短路徑
9-1 最短路徑問題和鬆弛操作

路徑規劃
工作任務規劃

最短路徑樹
Shortest Path Tree

單源最短路徑
Single Source Shortest Path

鬆弛操作 Relaxation

鬆弛操作是最短路徑求解的核心

9-2 Dijkstra算法的思想

dijkstra單源最短路徑算法

前提:圖中不能有負權邊
複雜度O(Elog(V))

9-3 實現Dijkstra算法
// 稠密圖 - 鄰接矩陣
public class DenseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Edge<Weight>[][] g;         // 圖的具體數據

    // 構造函數
    public DenseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲null, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new Edge[n][n];
        for(int i = 0 ; i < n ; i ++)
            for(int j = 0 ; j < n ; j ++)
                g[i][j] = null;
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        if( hasEdge( e.v() , e.w() ) )
            return;

        g[e.v()][e.w()] = new Edge(e);
        if( e.v() != e.w() && !directed )
            g[e.w()][e.v()] = new Edge(e.w(), e.v(), e.wt());

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w] != null;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                if( g[i][j] != null )
                    System.out.print(g[i][j].wt()+"\t");
                else
                    System.out.print("NULL\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Edge<Weight>> adjV = new Vector<Edge<Weight>>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] != null )
                adjV.add( g[v][i] );
        return adjV;
    }
}
// Dijkstra算法求最短路徑
public class Dijkstra<Weight extends Number & Comparable> {

    private WeightedGraph G;           // 圖的引用
    private int s;                     // 起始點
    private Number[] distTo;           // distTo[i]存儲從起始點s到i的最短路徑長度
    private boolean[] marked;          // 標記數組, 在算法運行過程中標記節點i是否被訪問
    private Edge<Weight>[] from;       // from[i]記錄最短路徑中, 到達i點的邊是哪一條
                                       // 可以用來恢復整個最短路徑

    // 構造函數, 使用Dijkstra算法求最短路徑
    public Dijkstra(WeightedGraph graph, int s){

        // 算法初始化
        G = graph;
        assert s >= 0 && s < G.V();
        this.s = s;
        distTo = new Number[G.V()];
        marked = new boolean[G.V()];
        from = new Edge[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            distTo[i] = 0.0;
            marked[i] = false;
            from[i] = null;
        }

        // 使用索引堆記錄當前找到的到達每個頂點的最短距離
        IndexMinHeap<Weight> ipq = new IndexMinHeap<Weight>(G.V());

        // 對於其實點s進行初始化
        distTo[s] = 0.0;
        from[s] = new Edge<Weight>(s, s, (Weight)(Number)(0.0));
        ipq.insert(s, (Weight)distTo[s] );
        marked[s] = true;
        while( !ipq.isEmpty() ){
            int v = ipq.extractMinIndex();

            // distTo[v]就是s到v的最短距離
            marked[v] = true;

            // 對v的所有相鄰節點進行更新
            for( Object item : G.adj(v) ){
                Edge<Weight> e = (Edge<Weight>)item;
                int w = e.other(v);
                // 如果從s點到w點的最短路徑還沒有找到
                if( !marked[w] ){
                    // 如果w點以前沒有訪問過,
                    // 或者訪問過, 但是通過當前的v點到w點距離更短, 則進行更新
                    if( from[w] == null || distTo[v].doubleValue() + e.wt().doubleValue() < distTo[w].doubleValue() ){
                        distTo[w] = distTo[v].doubleValue() + e.wt().doubleValue();
                        from[w] = e;
                        if( ipq.contain(w) )
                            ipq.change(w, (Weight)distTo[w] );
                        else
                            ipq.insert(w, (Weight)distTo[w] );
                    }
                }
            }
        }
    }

    // 返回從s點到w點的最短路徑長度
    Number shortestPathTo( int w ){
        assert w >= 0 && w < G.V();
        assert hasPathTo(w);
        return distTo[w];
    }

    // 判斷從s點到w點是否聯通
    boolean hasPathTo( int w ){
        assert w >= 0 && w < G.V() ;
        return marked[w];
    }

    // 尋找從s到w的最短路徑, 將整個路徑經過的邊存放在vec中
    Vector<Edge<Weight>> shortestPath( int w){

        assert w >= 0 && w < G.V();
        assert hasPathTo(w);

        // 通過from數組逆向查找到從s到w的路徑, 存放到棧中
        Stack<Edge<Weight>> s = new Stack<Edge<Weight>>();
        Edge<Weight> e = from[w];
        while( e.v() != this.s ){
            s.push(e);
            e = from[e.v()];
        }
        s.push(e);

        // 從棧中依次取出元素, 獲得順序的從s到w的路徑
        Vector<Edge<Weight>> res = new Vector<Edge<Weight>>();
        while( !s.empty() ){
            e = s.pop();
            res.add( e );
        }

        return res;
    }

    // 打印出從s點到w點的路徑
    void showPath(int w){

        assert w >= 0 && w < G.V();
        assert hasPathTo(w);

        Vector<Edge<Weight>> path =  shortestPath(w);
        for( int i = 0 ; i < path.size() ; i ++ ){
            System.out.print( path.elementAt(i).v() + " -> ");
            if( i == path.size()-1 )
                System.out.println(path.elementAt(i).w());
        }
    }
}
// 邊
public class Edge<Weight extends Number & Comparable> implements Comparable<Edge<Weight>>{

    private int a, b;    // 邊的兩個端點
    private Weight weight;  // 邊的權值

    public Edge(int a, int b, Weight weight)
    {
        this.a = a;
        this.b = b;
        this.weight = weight;
    }

    public Edge(Edge<Weight> e)
    {
        this.a = e.a;
        this.b = e.b;
        this.weight = e.weight;
    }

    public int v(){ return a;} // 返回第一個頂點
    public int w(){ return b;} // 返回第二個頂點
    public Weight wt(){ return weight;}    // 返回權值

    // 給定一個頂點, 返回另一個頂點
    public int other(int x){
        assert x == a || x == b;
        return x == a ? b : a;
    }

    // 輸出邊的信息
    public String toString(){
        return "" + a + "-" + b + ": " + weight;
    }

    // 邊之間的比較
    public int compareTo(Edge<Weight> that)
    {
        if( weight.compareTo(that.wt()) < 0 )
            return -1;
        else if ( weight.compareTo(that.wt()) > 0 )
            return +1;
        else
            return  0;
    }
}
// 最小索引堆
public class IndexMinHeap<Item extends Comparable> {

    protected Item[] data;      // 最小索引堆中的數據
    protected int[] indexes;    // 最小索引堆中的索引, indexes[x] = i 表示索引i在x的位置
    protected int[] reverse;    // 最小索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public IndexMinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        indexes = new int[capacity+1];
        reverse = new int[capacity+1];
        for( int i = 0 ; i <= capacity ; i ++ )
            reverse[i] = 0;

        count = 0;
        this.capacity = capacity;
    }

    // 返回索引堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示索引堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小索引堆中插入一個新的元素, 新元素的索引爲i, 元素爲item
    // 傳入的i對用戶而言,是從0索引的
    public void insert(int i, Item item){

        assert count + 1 <= capacity;
        assert i + 1 >= 1 && i + 1 <= capacity;

        // 再插入一個新元素前,還需要保證索引i所在的位置是沒有元素的。
        assert !contain(i);

        i += 1;
        data[i] = item;
        indexes[count+1] = i;
        reverse[i] = count + 1;
        count ++;

        shiftUp(count);
    }

    // 從最小索引堆中取出堆頂元素, 即索引堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;

        Item ret = data[indexes[1]];
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 從最小索引堆中取出堆頂元素的索引
    public int extractMinIndex(){
        assert count > 0;

        int ret = indexes[1] - 1;
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小索引堆中的堆頂元素
    public Item getMin(){
        assert count > 0;
        return data[indexes[1]];
    }

    // 獲取最小索引堆中的堆頂元素的索引
    public int getMinIndex(){
        assert count > 0;
        return indexes[1]-1;
    }

    // 看索引i所在的位置是否存在元素
    boolean contain( int i ){
        assert  i + 1 >= 1 && i + 1 <= capacity;
        return reverse[i+1] != 0;
    }

    // 獲取最小索引堆中索引爲i的元素
    public Item getItem( int i ){
        assert contain(i);
        return data[i+1];
    }

    // 將最小索引堆中索引爲i的元素修改爲newItem
    public void change( int i , Item newItem ){

        assert contain(i);

        i += 1;
        data[i] = newItem;

        // 有了 reverse 之後,
        // 我們可以非常簡單的通過reverse直接定位索引i在indexes中的位置
        shiftUp( reverse[i] );
        shiftDown( reverse[i] );
    }

    // 交換索引堆中的索引i和j
    // 由於有了反向索引reverse數組,
    // indexes數組發生改變以後, 相應的就需要維護reverse數組
    private void swapIndexes(int i, int j){
        int t = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = t;

        reverse[indexes[i]] = i;
        reverse[indexes[j]] = j;
    }

    //********************
    //* 最小索引堆核心輔助函數
    //********************

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftUp(int k){

        while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) > 0 ){
            swapIndexes(k, k/2);
            k /= 2;
        }
    }

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) < 0 )
                j ++;

            if( data[indexes[k]].compareTo(data[indexes[j]]) <= 0 )
                break;

            swapIndexes(k, j);
            k = j;
        }
    }

    // 測試 IndexMinHeap
    public static void main(String[] args) {

        int N = 1000000;
        IndexMinHeap<Integer> indexMinHeap = new IndexMinHeap<Integer>(N);
        for( int i = 0 ; i < N ; i ++ )
            indexMinHeap.insert( i , (int)(Math.random()*N) );

    }
}
public class Main {

    // 測試我們的Dijkstra最短路徑算法
    public static void main(String[] args) {

        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch9_3\\testG1.txt";
        int V = 5;

        SparseWeightedGraph<Integer> g = new SparseWeightedGraph<Integer>(V, true);
        // Dijkstra最短路徑算法同樣適用於有向圖
        //SparseGraph<int> g = SparseGraph<int>(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        System.out.println("Test Dijkstra:\n");
        Dijkstra<Integer> dij = new Dijkstra<Integer>(g,0);
        for( int i = 1 ; i < V ; i ++ ){
            if(dij.hasPathTo(i)) {
                System.out.println("Shortest Path to " + i + " : " + dij.shortestPathTo(i));
                dij.showPath(i);
            }
            else
                System.out.println("No Path to " + i );

            System.out.println("----------");
        }

    }
}
// 通過文件讀取有全圖的信息
public class ReadWeightedGraph {

    private Scanner scanner;

    // 由於文件格式的限制,我們的文件讀取類只能讀取權值爲Double類型的圖
    public ReadWeightedGraph(WeightedGraph<Double> graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                Double weight = scanner.nextDouble();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(new Edge<Double>(v, w, weight));
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + " doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }

}
// 稀疏圖 - 鄰接表
public class SparseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Edge<Weight>>[] g;   // 圖的具體數據

    // 構造函數
    public SparseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Edge<Weight>>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Edge<Weight>>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊, 權值爲weight
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        // 注意, 由於在鄰接表的情況, 查找是否有重邊需要遍歷整個鏈表
        // 我們的程序允許重邊的出現

        g[e.v()].add(new Edge(e));
        if( e.v() != e.w() && !directed )
            g[e.w()].add(new Edge(e.w(), e.v(), e.wt()));

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i).other(v) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ ){
                Edge e = g[i].elementAt(j);
                System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
            }
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
interface WeightedGraph<Weight extends Number & Comparable> {
    public int V();
    public int E();
    public void addEdge(Edge<Weight> e);
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Edge<Weight>> adj(int v);
}
9-4 負權邊和Bellman-Ford算法

擁有負權環的圖
沒有最短路徑

前提:圖中不能有負權環
Bellman-Ford可以判讀圖中是否有負權環
複雜度O(EV)

如果一個圖沒有負權環

從一個到另一個點的最短路徑,最多經過所有的V個頂線,有V-1條邊

否則,存在頂點經過了兩次,既存在負權環

對一個點的一次鬆弛操作,就是找到經過這個點的另外一條路徑,多一條邊,權值更小。

如果一個圖沒有負權環,從一個點到另外一點的最短路徑,最多經過所有的V個頂線,有V-1條邊

對所有的點進行V-1次鬆弛操作

9-5 實現Bellman-Ford算法
// 使用BellmanFord算法求最短路徑
public class BellmanFord<Weight extends Number & Comparable> {

    private WeightedGraph G;    // 圖的引用
    private int s;              // 起始點
    private Number[] distTo;    // distTo[i]存儲從起始點s到i的最短路徑長度
    Edge<Weight>[] from;        // from[i]記錄最短路徑中, 到達i點的邊是哪一條
                                // 可以用來恢復整個最短路徑
    boolean hasNegativeCycle;   // 標記圖中是否有負權環

    // 構造函數, 使用BellmanFord算法求最短路徑
    public BellmanFord(WeightedGraph graph, int s){

        G = graph;
        this.s = s;
        distTo = new Number[G.V()];
        from = new Edge[G.V()];
        // 初始化所有的節點s都不可達, 由from數組來表示
        for( int i = 0 ; i < G.V() ; i ++ )
            from[i] = null;

        // 設置distTo[s] = 0, 並且讓from[s]不爲NULL, 表示初始s節點可達且距離爲0
        distTo[s] = 0.0;
        from[s] = new Edge<Weight>(s, s, (Weight)(Number)(0.0)); // 這裏我們from[s]的內容是new出來的, 注意要在析構函數裏delete掉

        // Bellman-Ford的過程
        // 進行V-1次循環, 每一次循環求出從起點到其餘所有點, 最多使用pass步可到達的最短距離
        for( int pass = 1 ; pass < G.V() ; pass ++ ){

            // 每次循環中對所有的邊進行一遍鬆弛操作
            // 遍歷所有邊的方式是先遍歷所有的頂點, 然後遍歷和所有頂點相鄰的所有邊
            for( int i = 0 ; i < G.V() ; i ++ ){
                // 使用我們實現的鄰邊迭代器遍歷和所有頂點相鄰的所有邊
                for( Object item : G.adj(i) ){
                    Edge<Weight> e = (Edge<Weight>)item;
                    // 對於每一個邊首先判斷e->v()可達
                    // 之後看如果e->w()以前沒有到達過, 顯然我們可以更新distTo[e->w()]
                    // 或者e->w()以前雖然到達過, 但是通過這個e我們可以獲得一個更短的距離, 即可以進行一次鬆弛操作, 我們也可以更新distTo[e->w()]
                    if( from[e.v()] != null && (from[e.w()] == null || distTo[e.v()].doubleValue() + e.wt().doubleValue() < distTo[e.w()].doubleValue()) ){
                        distTo[e.w()] = distTo[e.v()].doubleValue() + e.wt().doubleValue();
                        from[e.w()] = e;
                    }
                }
            }
        }

        hasNegativeCycle = detectNegativeCycle();
    }

    // 判斷圖中是否有負權環
    boolean detectNegativeCycle(){

        for( int i = 0 ; i < G.V() ; i ++ ){
            for( Object item : G.adj(i) ){
                Edge<Weight> e = (Edge<Weight>)item;
                if( from[e.v()] != null && distTo[e.v()].doubleValue() + e.wt().doubleValue() < distTo[e.w()].doubleValue() )
                    return true;
            }
        }

        return false;
    }

    // 返回圖中是否有負權環
    boolean negativeCycle(){
        return hasNegativeCycle;
    }

    // 返回從s點到w點的最短路徑長度
    Number shortestPathTo( int w ){
        assert w >= 0 && w < G.V();
        assert !hasNegativeCycle;
        assert hasPathTo(w);
        return distTo[w];
    }

    // 判斷從s點到w點是否聯通
    boolean hasPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        return from[w] != null;
    }

    // 尋找從s到w的最短路徑, 將整個路徑經過的邊存放在vec中
    Vector<Edge<Weight>> shortestPath(int w){

        assert w >= 0 && w < G.V() ;
        assert !hasNegativeCycle ;
        assert hasPathTo(w) ;

        // 通過from數組逆向查找到從s到w的路徑, 存放到棧中
        Stack<Edge<Weight>> s = new Stack<Edge<Weight>>();
        Edge<Weight> e = from[w];
        while( e.v() != this.s ){
            s.push(e);
            e = from[e.v()];
        }
        s.push(e);

        // 從棧中依次取出元素, 獲得順序的從s到w的路徑
        Vector<Edge<Weight>> res = new Vector<Edge<Weight>>();
        while( !s.empty() ){
            e = s.pop();
            res.add(e);
        }

        return res;
    }

    // 打印出從s點到w點的路徑
    void showPath(int w){

        assert( w >= 0 && w < G.V() );
        assert( !hasNegativeCycle );
        assert( hasPathTo(w) );

        Vector<Edge<Weight>> res = shortestPath(w);
        for( int i = 0 ; i < res.size() ; i ++ ){
            System.out.print(res.elementAt(i).v() + " -> ");
            if( i == res.size()-1 )
                System.out.println(res.elementAt(i).w());
        }
    }
}
// 稠密圖 - 鄰接矩陣
public class DenseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph{

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Edge<Weight>[][] g;         // 圖的具體數據

    // 構造函數
    public DenseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n*n的布爾矩陣, 每一個g[i][j]均爲null, 表示沒有任和邊
        // false爲boolean型變量的默認值
        g = new Edge[n][n];
        for(int i = 0 ; i < n ; i ++)
            for(int j = 0 ; j < n ; j ++)
                g[i][j] = null;
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        if( hasEdge( e.v() , e.w() ) )
            return;

        g[e.v()][e.w()] = new Edge(e);
        if( e.v() != e.w() && !directed )
            g[e.w()][e.v()] = new Edge(e.w(), e.v(), e.wt());

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w] != null;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            for( int j = 0 ; j < n ; j ++ )
                if( g[i][j] != null )
                    System.out.print(g[i][j].wt()+"\t");
                else
                    System.out.print("NULL\t");
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        Vector<Edge<Weight>> adjV = new Vector<Edge<Weight>>();
        for(int i = 0 ; i < n ; i ++ )
            if( g[v][i] != null )
                adjV.add( g[v][i] );
        return adjV;
    }
}
// 邊
public class Edge<Weight extends Number & Comparable> implements Comparable<Edge<Weight>>{

    private int a, b;    // 邊的兩個端點
    private Weight weight;  // 邊的權值

    public Edge(int a, int b, Weight weight)
    {
        this.a = a;
        this.b = b;
        this.weight = weight;
    }

    public Edge(Edge<Weight> e)
    {
        this.a = e.a;
        this.b = e.b;
        this.weight = e.weight;
    }

    public int v(){ return a;} // 返回第一個頂點
    public int w(){ return b;} // 返回第二個頂點
    public Weight wt(){ return weight;}    // 返回權值

    // 給定一個頂點, 返回另一個頂點
    public int other(int x){
        assert x == a || x == b;
        return x == a ? b : a;
    }

    // 輸出邊的信息
    public String toString(){
        return "" + a + "-" + b + ": " + weight;
    }

    // 邊之間的比較
    public int compareTo(Edge<Weight> that)
    {
        if( weight.compareTo(that.wt()) < 0 )
            return -1;
        else if ( weight.compareTo(that.wt()) > 0 )
            return +1;
        else
            return  0;
    }
}
public class Main {

    // 測試我們的Bellman-Ford最短路徑算法
    public static void main(String[] args) {

        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch9_5\\testG2.txt";
        //String filename = "testG_negative_circle.txt";
        int V = 5;

        SparseWeightedGraph<Integer> g = new SparseWeightedGraph<Integer>(V, true);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        System.out.println("Test Bellman-Ford:\n");
        BellmanFord<Integer> bellmanFord = new BellmanFord<Integer>(g,0);
        if( bellmanFord.negativeCycle() )
            System.out.println("The graph contain negative cycle!");
        else
            for( int i = 1 ; i < V ; i ++ ){
                if(bellmanFord.hasPathTo(i)) {
                    System.out.println("Shortest Path to " + i + " : " + bellmanFord.shortestPathTo(i));
                    bellmanFord.showPath(i);
                }
                else
                    System.out.println("No Path to " + i );

            System.out.println("----------");
        }

    }
}
// 通過文件讀取有全圖的信息
public class ReadWeightedGraph {

    private Scanner scanner;

    // 由於文件格式的限制,我們的文件讀取類只能讀取權值爲Double類型的圖
    public ReadWeightedGraph(WeightedGraph<Double> graph, String filename){

        readFile(filename);

        try {
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("number of vertices in a Graph must be nonnegative");
            assert V == graph.V();

            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("number of edges in a Graph must be nonnegative");

            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                Double weight = scanner.nextDouble();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(new Edge<Double>(v, w, weight));
            }
        }
        catch (InputMismatchException e) {
            String token = scanner.next();
            throw new InputMismatchException("attempts to read an 'int' value from input stream, but the next token is \"" + token + "\"");
        }
        catch (NoSuchElementException e) {
            throw new NoSuchElementException("attemps to read an 'int' value from input stream, but there are no more tokens available");
        }
    }

    private void readFile(String filename){
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                throw new IllegalArgumentException(filename + " doesn't exist.");
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }

}
// 稀疏圖 - 鄰接表
public class SparseWeightedGraph<Weight extends Number & Comparable>
        implements WeightedGraph {

    private int n;  // 節點數
    private int m;  // 邊數
    private boolean directed;   // 是否爲有向圖
    private Vector<Edge<Weight>>[] g;   // 圖的具體數據

    // 構造函數
    public SparseWeightedGraph( int n , boolean directed ){
        assert n >= 0;
        this.n = n;
        this.m = 0;    // 初始化沒有任何邊
        this.directed = directed;
        // g初始化爲n個空的vector, 表示每一個g[i]都爲空, 即沒有任和邊
        g = (Vector<Edge<Weight>>[])new Vector[n];
        for(int i = 0 ; i < n ; i ++)
            g[i] = new Vector<Edge<Weight>>();
    }

    public int V(){ return n;} // 返回節點個數
    public int E(){ return m;} // 返回邊的個數

    // 向圖中添加一個邊, 權值爲weight
    public void addEdge(Edge e){

        assert e.v() >= 0 && e.v() < n ;
        assert e.w() >= 0 && e.w() < n ;

        // 注意, 由於在鄰接表的情況, 查找是否有重邊需要遍歷整個鏈表
        // 我們的程序允許重邊的出現

        g[e.v()].add(new Edge(e));
        if( e.v() != e.w() && !directed )
            g[e.w()].add(new Edge(e.w(), e.v(), e.wt()));

        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    public boolean hasEdge( int v , int w ){

        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;

        for( int i = 0 ; i < g[v].size() ; i ++ )
            if( g[v].elementAt(i).other(v) == w )
                return true;
        return false;
    }

    // 顯示圖的信息
    public void show(){

        for( int i = 0 ; i < n ; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for( int j = 0 ; j < g[i].size() ; j ++ ){
                Edge e = g[i].elementAt(j);
                System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
            }
            System.out.println();
        }
    }

    // 返回圖中一個頂點的所有鄰邊
    // 由於java使用引用機制,返回一個Vector不會帶來額外開銷,
    public Iterable<Edge<Weight>> adj(int v) {
        assert v >= 0 && v < n;
        return g[v];
    }
}
interface WeightedGraph<Weight extends Number & Comparable> {
    public int V();
    public int E();
    public void addEdge(Edge<Weight> e);
    boolean hasEdge( int v , int w );
    void show();
    public Iterable<Edge<Weight>> adj(int v);
}
9-6 更多和最短路徑相關的思考

利用隊列數據結構
queue-based bellman-ford算法

dijkstra 無負權邊 有向無向圖均可 O(ElogV)
Bellman-Ford 無負權環 有向圖 O(VE)
利用拓撲排序 有向無環圖DAG 有向圖 O(V+E)

Floyed算法,處理無負權環的圖
O(V^3)

最長路徑問題不能有正權環。
無權環的最長路徑問題是指數級難度的。
對於有權圖,不能使用Dijkstra求最長路徑問題。
可以使用Bellman-Ford算法。

第10章 結束語
10-1 總結,算法思想,大家加油

線性 -> 樹形結構 -> 圖形結構

O(n^2) 選擇排序 插入排序
O(nlogn) 歸併排序 快速排序
partition -> 隨機化 -> 大量重複元素

求逆數的個數 k-selection

堆(Heap) 堆排序 優先隊列 索引堆

二叉查找樹(Binary Search Tree) 解決查找問題

並查集(Union Find) 基於rank優化 -> 路徑壓縮 Kruskal

圖論問題

圖的表示:鄰接表和鄰接矩陣
有向圖和無向圖
有權圖和無權圖

圖的遍歷:DFS,BFS
聯通分量 Flood Fill 尋路 走迷宮 迷宮生成
無權圖的最短路徑 環的判斷

最小生成樹問題(Minimum Spanning Tree) Prim Kruskal
最短路徑問題(Shortest Path) Dijkstra Belman-Ford

更多算法問題

數據結構相關
雙向隊列 斐波那契堆 紅黑樹 區間樹 KD樹…

具體領域相關
數學:數論;計算幾何 圖論:網絡流…

算法設計相關
分治 歸併排序;快速排序;樹結構
貪心 選擇排序;堆;Kruskal;Prim;Dijkstra
遞歸回溯 樹的遍歷;圖的遍歷
動態規劃 Prim;Dijkstra


《算法與數據結構》示例代碼

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