溫習Algs4 (三):無向圖, 搜尋和連通分量
阿新 • • 發佈:2018-12-21
無向圖, 搜尋和連通分量
無向圖
對於一般演算法題來說圖的實現方式是鄰接矩陣, 但是對於稀疏圖來說用鄰接表來實現比較實惠一點.
圖的API多種多樣, 我這裡實現的API有:
- 新增邊
- 獲得該圖的頂點數和邊數
- 遍歷一個點的所有鄰接點
- 獲得一個點的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對所有點進行搜尋, 每次搜尋都會生成一個新的連通分量. 我這裡支援的操作有:
- 判斷兩點是否相連
- 連通分量的數量
- 查詢某一點對應的ID值 (0 ~ N-1)
- 返回某一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);
}
}
總結
無向圖的相關演算法相比於有向圖更為簡單, 但是一個難點是找出圖中所有的環, 該演算法我會另起一篇部落格介紹.