《演算法導論》——深度優先搜尋與拓撲排序
深度遍歷演算法描述
演算法描述參考自《演算法導論》深度優先搜尋演算法:
/*
註解:
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實現