1. 程式人生 > >溫習Algs4 (三):無向圖, 搜尋和連通分量

溫習Algs4 (三):無向圖, 搜尋和連通分量

無向圖, 搜尋和連通分量

無向圖

對於一般演算法題來說圖的實現方式是鄰接矩陣, 但是對於稀疏圖來說用鄰接表來實現比較實惠一點.
圖的API多種多樣, 我這裡實現的API有:

  1. 新增邊
  2. 獲得該圖的頂點數和邊數
  3. 遍歷一個點的所有鄰接點
  4. 獲得一個點的degree

Graph.java

/******************************************************************************
 *  Compilation:  javac Graph.java
 *  Execution:    java Graph
 *  Author:       Chenghao Wang
 ******************************************************************************/
import java.util.Scanner; public class Graph { private int vertexCount; private int edgeCount; private Bag<Integer>[] adj; private int[] degree; Graph() { } Graph(int v) { vertexCount = v; edgeCount = 0; adj = (Bag<Integer>[]) new Bag[v]
; degree = new int[v]; for (int i = 0; i < v; i++) { adj[i] = new Bag<Integer>(); } } Graph(Scanner scan) { this(scan.nextInt()); int e = scan.nextInt(); for (int i = 0; i < e; i++) { int j = scan.nextInt(); int k = scan.nextInt(); addEdge(j, k); } } public int V() { return vertexCount; } public int E() { return edgeCount; } public void addEdge(int v, int w) { adj[w].add(v); adj[v].add(w); edgeCount++; degree[w]++; degree[v]++; } public Iterable<Integer> adj(int v) { return adj[v]; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("<graph>\n"); for (int i = 0; i < vertexCount; i++) { for (int v : adj[i]) { if (v < i) { continue; } sb.append(" " + i + " - " + v + "\n"); } } sb.append("</graph>"); return sb.toString(); } public int degree(int v) { return degree[v]; } }

搜尋

搜尋即遍歷圖的所有頂點, 分為深度優先搜尋和廣度優先搜尋, 因為二者具有共性, 所以我這裡實現一個抽象類作為他們的父類, 以及一個用來定製遍歷程式碼的輔助類 (java中沒有函式指標, 所以用類來代替).
Search支援的操作只有檢查某一點是否被訪問過.

Visitor.java

/******************************************************************************
 *  Compilation:  javac Visitor.java
 *  Execution:    java Visitor
 *  Author:       Chenghao Wang
 ******************************************************************************/

public abstract class Visitor {
    public abstract void visit(Graph g, int v);
}

Search.java

/******************************************************************************
 *  Compilation:  javac Search.java
 *  Execution:    java Search
 *  Author:       Chenghao Wang
 ******************************************************************************/

public class Search {
    protected boolean[] mark;
    protected Graph g;
    protected Visitor visitor;

    Search() { }

    Search(Graph g, int start) {
        this(g, start, null);
    } 

    Search(Graph g, int start, Visitor visitor) {
        mark = new boolean[g.V()];
        this.g = g;
        this.visitor = visitor;
    } 

    public boolean mark(int v) {
        return mark[v];
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < mark.length; i++) {
            if (!mark[i]) continue;
            sb.append(i + " ");
        }
        return sb.toString();
    }
}

深度優先搜尋

英文名 DepthFirstSearch 即 DFS. 出於簡便性我這裡採用遞迴呼叫的方式實現.

DepthFirstSearch.java

/******************************************************************************
 *  Compilation:  javac DepthFirstSearch.java
 *  Execution:    java DepthFirstSearch
 *  Author:       Chenghao Wang
 ******************************************************************************/

public class DepthFirstSearch extends Search {

    DepthFirstSearch() { }

    DepthFirstSearch(Graph g, int start) {
        this(g, start, null);
    } 

    DepthFirstSearch(Graph g, int start, Visitor visitor) {
        super(g, start, visitor);
        dfs(start);
    } 

    private void dfs(int v) {
        mark[v] = true;
        if (visitor != null) {
            visitor.visit(g, v);
        }
        for (int next : g.adj(v)) {
            if (mark[next]) continue;
            dfs(next);
        }
    }
}

廣度優先遍歷

英文名是 BreadthFirstSearch 即 BFS, 其程式碼實現需要之前寫到的Queue資料結構.

BreadthFirstSearch.java

/******************************************************************************
 *  Compilation:  javac BreadthFirstSearch.java
 *  Execution:    java BreadthFirstSearch
 *  Author:       Chenghao Wang
 ******************************************************************************/

public class BreadthFirstSearch extends Search {

    BreadthFirstSearch() { };

    BreadthFirstSearch(Graph g, int start) {
        this(g, start, null);
    }

    BreadthFirstSearch(Graph g, int start, Visitor visitor) {
        super(g, start, visitor);

        Queue<Integer> queue = new Queue<Integer>();
        mark[start] = true;
        queue.enqueue(start);

        while (!queue.isEmpty()) {
            int v = queue.dequeue();
            if (visitor != null) {
                visitor.visit(g, v);
            }
            for (int w : g.adj(v)) {
                if (mark[w]) continue;
                mark[w] = true;
                queue.enqueue(w);
            }
        }
    }
}

連通分量

英文名是 ConnectedComponent 即 CC, 實現方法很簡單, 即用DFS或BFS對所有點進行搜尋, 每次搜尋都會生成一個新的連通分量. 我這裡支援的操作有:

  1. 判斷兩點是否相連
  2. 連通分量的數量
  3. 查詢某一點對應的ID值 (0 ~ N-1)
  4. 返回某一ID對應的所有點

CC.java

/******************************************************************************
 *  Compilation:  javac CC.java
 *  Execution:    java CC
 *  Author:       Chenghao Wang
 ******************************************************************************/

public class CC {
    private boolean[] mark;
    private Vector<Bag<Integer>> components;
    private int[] id;
    private int currentId;

    CC(Graph g) {
        mark = new boolean[g.V()];
        id = new int[g.V()];
        components = new Vector<Bag<Integer>>();
        currentId = 0;

        Visitor visitor = new Visitor() {
            public void visit(Graph g, int v) {
                mark[v] = true;
                id[v] = currentId;
                components.get(currentId).add(v);
            }
        };

        for (int i = 0; i < g.V(); i++) {
            if (mark[i]) continue;
            components.add(new Bag<Integer>());
            new DepthFirstSearch(g, i, visitor);
            currentId++;
        }
    }

    public boolean connected(int v, int w) {
        return id[v] == id[w];
    }

    public int count() {
        return components.size();
    }

    public int id(int v) {
        return id[v];
    }

    public Iterable<Integer> component(int i) {
        return components.get(i);
    }
}

總結

無向圖的相關演算法相比於有向圖更為簡單, 但是一個難點是找出圖中所有的環, 該演算法我會另起一篇部落格介紹.