深度遍歷算法描述
算法描述參考自《算法導論》深度優先搜索算法:
/* 注解: 1、G.v表示途中節點的集合,其中G是一個有向圖 2、G:Adj[u] 表示再有向圖中以u為起始節點的鄰接節點集合 3、color 白色表示節點未被發現;灰色表示節點已經被發現但沒有深搜完畢;黑色節點表示節點深搜完畢 4、u.d 表示節點訪問的開始時間。u.f表示節點訪問的結束時間。u.parent表示u的父節點,NIL表示父節點為空。 */ DFS(G) for each vertex u belong to G.V u.color=WHITE u.parent=NIL time=0
for each vertex u belong to G.V if u.color=WHITE DFS-VISIT(G,u) DFS-VISIT(G,u) time=time+1 u.d=time u.color=GRAY for each v belong to G:Adj[u] if v.color==WHITE v.parent=u DFS-VISIT(G,v) u.coloe=BALCK time=time+1 u.f=time
什么是拓撲排序
對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊∈E(G),則u在線性序列中出現在v之前。通常,這樣的線性序列稱為滿足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序得到該集合上的一個全序,這個操作稱之為拓撲排序。——百度百科
注意:若有向圖中存在回路,則該有向圖無法進行拓撲排序
常用的拓撲排序方法如下:
方法一:入度統計
(1)從有向圖中選擇一個沒有前驅(即入度為0)的頂點並且輸出它。
(2)從圖中刪去該頂點,並且刪去從該頂點發出的所有邊。
(3)重復上述步驟(1)和(2),直到當前有向圖中不存在沒有前驅結點的頂點為止,或者當前有向圖中的所有結點均已輸出為止。
(4)如果當前有向圖中不存在沒有前驅結點的頂點,並且當前有向圖中的所有結點尚未完全輸出,則可以判斷當前有向圖中有環
方法二:深度優先搜索
思路是記錄各個節點深度遍歷時完成訪問的結束時間,然后根據結束時間的先后順序組成一個列表,則該列表就是一個拓撲序列
拓撲排序DFS算法描述
算法描述參考自《算法導論》拓撲排序:
TOPOLOGICAL-SORT(G) call DFS(G) to compute finishing times v.f for each vertex v as each vertex is finished,insert it onto the front of a linked list return the linked list of vertices
需要注意的是,基於深度優先搜索來實現拓撲排序,所用到的圖必須是有向無環圖
DFS代碼實現
package myDFS; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; public class DFS { private int nodeNum;//圖的節點個數
private int[][] graph;//鄰接矩陣存儲圖信息
private List<Edge> edges = new ArrayList<>();//存儲邊信息
private List<Integer> res = new ArrayList<>();//存放深度遍歷的路徑
private List<Integer> topoSort = new ArrayList<>();//存放拓撲排序路徑
public static void main(String[] args) { DFS dfs = new DFS(); dfs.inputInfo(); //輸入圖的信息
dfs.creatGraph(); //創建有向圖
dfs.outputGraph(); //打印有向圖
dfs.dfs(); //進行深度遍歷
dfs.outputRes(); //輸出結果
} /** * 用戶輸入,並創建邊集合 */
public void inputInfo() { Scanner scanner = new Scanner(System.in); System.out.print("請輸入節點個數:"); nodeNum = scanner.nextInt(); int size = nodeNum; System.out.println("請依次輸入邊,格式為:起始節點 終止節點(-1 -1作為結束符)"); while (true) { int node1 = scanner.nextInt(); int node2 = scanner.nextInt(); if (node1 < 0 || node2 < 0) break; edges.add(new Edge(node1, node2)); } } /** * 創建有圖 */
public void creatGraph() { graph = new int[nodeNum + 1][nodeNum + 1];//因為節點從1開始編號,所以加一
for (int i = 0; i < edges.size(); i++) { graph[edges.get(i).node1][edges.get(i).node2] = 1; } } /** * 深度遍歷遞歸調用 */
static int time = 0; public void dfs() { // visited訪問標志符數組,有三種狀態: // visited=0表示節點未被發現 // visited=1表示節點已經被發現,但沒有完成深搜 // visited=2表示節點已經深搜完畢
int visited[] = new int[nodeNum + 1]; int startTime[] = new int[nodeNum + 1];//用於記錄各節點被發現的時間
int endTime[] = new int[nodeNum + 1];//用於記錄各節點完成深搜的時間
time = 0;//全局時鍾 //選擇開始節點進行深搜
for (int i = 1; i <= nodeNum; i++) { if (visited[i] == 0) { dfs(graph, i, visited, startTime, endTime); } } System.out.println("startT:" + Arrays.toString(startTime)); System.out.println("endT:" + Arrays.toString(endTime)); } /** * 遞歸實現深度遍歷 * * @param graph 有向圖 * @param node 當前節點 * @param visited 訪問標識符數組 * @param startT 開始時間數組 * @param endT 結束時間數組 */
public void dfs(int graph[][], int node, int visited[], int startT[], int endT[]) { res.add(node);//記錄訪問路徑
time++; //節點被發現,更新時鍾
visited[node] = 1; startT[node] = time; while (findNext(graph, node, visited) != -1) { int nestNode = findNext(graph, node, visited); if (visited[nestNode] == 0) { dfs(graph, nestNode, visited, startT, endT); } } time++;//節點深搜結束,更新時鍾
endT[node] = time; visited[node] = 2; topoSort.add(node); } /** * 獲取當前節點的下一個節點 * * @param graph 有向圖 * @param node 當前節點 * @param visited 訪問標志符數組 * @return -1表示沒有下一個節點 */
private int findNext(int[][] graph, int node, int visited[]) { for (int i = 0; i < edges.size(); i++) { Edge edge = edges.get(i); if (edge.node1 == node && visited[edge.node2] == 0) { return edge.node2; } } return -1; } /** * 打印有向圖 */
public void outputGraph() { for (int i = 1; i <= nodeNum; i++) { for (int j = 1; j <= nodeNum; j++) { System.out.print(graph[i][j] + " "); } System.out.println(); } } /** * 輸出深度遍歷的路徑以及拓撲排序路徑 */
public void outputRes() { System.out.println("訪問路徑" + res); System.out.println("拓撲路徑" + topoSort); } } class Edge { int node1; int node2; int weight; public Edge(int node1, int node2) { this.node1 = node1; this.node2 = node2; } }
測試用例:

6
1 2
1 3
1 4
2 3
2 5
3 4
3 5
4 6
5 6
-1 -1
輸出:
0 1 1 1 0 0
0 0 1 0 1 0
0 0 0 1 1 0
0 0 0 0 0 1
0 0 0 0 0 1
0 0 0 0 0 0
startT:[0, 1, 2, 3, 4, 8, 5]
endT:[0, 12, 11, 10, 7, 9, 6]
訪問路徑[1, 2, 3, 4, 6, 5]
拓撲路徑[6, 4, 5, 3, 2, 1]
參考資料
- 《算法導論》深度優先搜索
- 什么是拓撲排序
- 拓撲排序及其Java實現
