《算法導論》——深度優先搜索與拓撲排序


深度遍歷算法描述

算法描述參考自《算法導論》深度優先搜索算法:

/* 注解: 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]

 

參考資料

  1. 《算法導論》深度優先搜索
  2. 什么是拓撲排序
  3. 拓撲排序及其Java實現

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM