無向圖
1.定義
在數學上,一個圖(Graph)是表示物件與物件之間的關係的方法,是圖論的基本研究對象。一個圖看起來是由一些小圓點(稱爲頂點或結點)和連結這些圓點的直線或曲線(稱爲邊)組成的。
如果給圖的每條邊規定一個方向,那麼得到的圖稱爲有向圖,其邊也稱爲有向邊。在有向圖中,與一個節點相關聯的邊有出邊和入邊之分,而與一個有向邊關聯的兩個點也有始點和終點之分。相反,邊沒有方向的圖稱爲無向圖。
某個頂點的度數即爲連接它的邊的總數。路徑或者環的長度爲其中所包含的邊數。
2.代碼
我們可以抽象出以下的表示圖的API:
Graph的API的實現可以由多種不同的數據結構來表示,最基本的是維護一系列邊的集合,如下:
還可以使用鄰接矩陣來表示:
也可以使用鄰接列表來表示:
由於採用如上方式具有比較好的靈活性,採用鄰接列表來表示的話,可以定義如下數據結構來表示一個Graph對象。
import java.util.ArrayList;
import java.util.List;
public class Graph {
private int verticals;//頂點個數
private int edges;//邊的個數
private List<Integer>[] adjacency;//頂點聯接列表
@SuppressWarnings("unchecked")
public Graph(int vertical) {
this.verticals = vertical;
this.edges = 0;
adjacency = (List<Integer>[])new List[vertical];
for (int v = 0; v < vertical; v++) {
adjacency[v]=new ArrayList<Integer>();
}
}
public int GetVerticals () {
return verticals;
}
public int GetEdges() {
return edges;
}
public void AddEdge(int verticalStart, int verticalEnd) {
adjacency[verticalStart].add(verticalEnd);
adjacency[verticalEnd].add(verticalStart);
edges++;
}
public List<Integer> GetAdjacency(int vetical) {
return adjacency[vetical];
}
public static void main(String[] args) {
Graph G = new Graph(13);
G.AddEdge(0, 5);
G.AddEdge(4, 3);
G.AddEdge(0, 1);
G.AddEdge(9, 12);
G.AddEdge(6, 4);
G.AddEdge(5, 4);
G.AddEdge(0, 2);
G.AddEdge(11, 12);
G.AddEdge(9, 10);
G.AddEdge(0, 6);
G.AddEdge(7, 8);
G.AddEdge(9, 11);
G.AddEdge(5, 3);
G.AddEdge(4, 9);
List<Integer> list = G.GetAdjacency(4);
for(int w : list) {
System.out.print(w + " ");
}
}
}
輸出:
3 6 5 9
採用以上三種表示方式的效率如下:
深度優先搜索
1.原理
在談論深度優先算法之前,我們可以先看看迷宮探索問題。下面是一個迷宮和圖之間的對應關係: 迷宮中的每一個交會點代表圖中的一個頂點,每一條通道對應一個邊。
迷宮探索可以採用Trémaux繩索探索法。即:
- 選擇一條沒有標記過的通道,在你走過的路上鋪一條繩子;
- 標記所有你第一次路過的路口和通道;
- 當來到一個標記過的路口時(用繩子)回退到上個路口;
- 當會退到的路口已經沒有可走的通道時繼續回退。
圖示如下:
下面是迷宮探索的一個小動畫:
接下來我們看圖的搜索方法。
2.代碼
定義一個edgesTo變量來後向記錄所有到s的頂點的記錄,和僅記錄從當前節點到起始節點不同,我們記錄圖中的每一個節點到開始節點的路徑。爲了完成這一日任務,通過設置edgesTo[w]=v,我們記錄從v到w的邊,換句話說,v-w是最後一條從s到達w的邊。 edgesTo[]其實是一個指向其父節點的樹。
public class DepthFirstSearch {
private boolean[] marked;//記錄是否被dfs訪問過
private int[] edgesTo;//記錄最後一個到當前節點的頂點
private int s;//搜索的起始點
public DepthFirstSearch(Graph g, int s) {
marked = new boolean[g.GetVerticals()];
edgesTo = new int[g.GetVerticals()];
this.s = s;
dfs(g, s);
}
private void dfs(Graph g, int v) {
marked[v] = true;
for(Integer w : g.GetAdjacency(v)) {
if (!marked[w]) {
edgesTo[w] = v;
dfs(g,w);
}
}
}
public boolean HasPathTo(int v) {
return marked[v];
}
public Stack<Integer> PathTo(int v) {
if (!HasPathTo(v)) {
return null;
}
Stack<Integer> path = new Stack<Integer>();
for (int x = v; x!=s; x=edgesTo[x]) {
path.push(x);
}
path.push(s);
return path;
}
public static void main(String[] args) {
Graph G = new Graph(13);
G.AddEdge(0, 5);
G.AddEdge(4, 3);
G.AddEdge(0, 1);
G.AddEdge(9, 12);
G.AddEdge(6, 4);
G.AddEdge(5, 4);
G.AddEdge(0, 2);
G.AddEdge(11, 12);
G.AddEdge(9, 10);
G.AddEdge(0, 6);
G.AddEdge(7, 8);
G.AddEdge(9, 11);
G.AddEdge(5, 3);
G.AddEdge(4, 9);
DepthFirstSearch dfs = new DepthFirstSearch(G, 0);
Stack<Integer> path = dfs.PathTo(11);
while(path.size() != 0) {
System.out.print(path.pop() + " ");
}
}
}
輸出:
0 5 4 9 12 11
廣度優先搜索
1.原理
我們很自然地還經常對下面這些問題敢興趣。
單點最短路徑。給定一幅圖和一個起點s,回答“從s到給定目的頂點v是否存在一條路徑?如果有,找出其中最短的那條等類似問題。”
解決這個問題的經典方法叫做廣度優先搜索。
其主要原理是:
- 將 s放到FIFO中,並且將s標記爲已訪問
- 重複直到隊列爲空
- 移除最近最近添加的頂點v
- 將v未被訪問的節點添加到隊列中
- 標記他們爲已經訪問
廣度優先是以距離遞增的方式來搜索路徑的。
public class BreadthFirstSearch {
private boolean[] marked;
private int[] edgeTo;
private int s;//搜索的起始點
public BreadthFirstSearch(Graph g, int s) {
marked=new boolean[g.GetVerticals()];
edgeTo=new int[g.GetVerticals()];
this.s = s;
bfs(g, s);
}
private void bfs(Graph g, int s) {
Queue<Integer> queue = new Queue<Integer>();
marked[s] = true;
queue.enqueue(s);
while (queue.size()!=0) {
int v = queue.dequeue();
for(int w : g.GetAdjacency(v)) {
if (!marked[w]) {
edgeTo[w] = v;
marked[w] = true;
queue.enqueue(w);
}
}
}
}
public boolean HasPathTo(int v) {
return marked[v];
}
public Stack<Integer> PathTo(int v) {
if (!HasPathTo(v)) {
return null;
}
Stack<Integer> path = new Stack<Integer>();
for (int x = v; x!=s; x=edgeTo[x]) {
path.push(x);
}
path.push(s);
return path;
}
public static void main(String[] args) {
Graph G = new Graph(13);
G.AddEdge(0, 5);
G.AddEdge(4, 3);
G.AddEdge(0, 1);
G.AddEdge(9, 12);
G.AddEdge(6, 4);
G.AddEdge(5, 4);
G.AddEdge(0, 2);
G.AddEdge(11, 12);
G.AddEdge(9, 10);
G.AddEdge(0, 6);
G.AddEdge(7, 8);
G.AddEdge(9, 11);
G.AddEdge(5, 3);
G.AddEdge(4, 9);
BreadthFirstSearch bfs = new BreadthFirstSearch(G, 0);
Stack<Integer> path = bfs.PathTo(3);
while(path.size() != 0) {
System.out.print(path.pop() + " ");
}
}
}
輸出:
0 5 4 9 11 (最短路徑)
總結
本文簡要介紹了無向圖中的深度優先和廣度優先算法,這兩種算法時圖處理算法中的最基礎算法,也是後續更復雜算法的基礎。它們搜索路徑如下: