上一次已經提到,圖的遍歷通常有兩種算法,即廣度優先和深度優先。其中深度優先搜索算法會從第一個指定的頂點開始遍歷圖,沿着路徑直到這條路徑最後一個頂點,接着原路回退並探索下一條路徑。換句話說,它是先深度後廣度地訪問頂點,以下圖1。javascript
圖1java
與廣度優先算法不一樣,深度優先算法不須要一個起始頂點,只要頂點v沒有被訪問,就訪問該頂點。
咱們用三種狀態來反映頂點的狀態:算法
訪問某一頂點v的步驟以下:segmentfault
所以深度優先搜索的步驟是遞歸的,這意味着深度優先搜索算法使用棧來存儲函數調用(由遞歸調用所建立的棧)。
與廣度優先算法相似,在算法開始以前,咱們須要將全部頂點置爲白色(本文的全部代碼都是在圖中實現的Graph類中添加的)。函數
function Graph() { var vertices = []; var adjList = new Dictionary(); // 圖類的其餘代碼省略... var initializeColor = function(){ var color = []; for (var i=0; i<vertices.length; i++){ color[vertices[i]] = 'white'; } return color; }; }
接下來實現深度優先算法的核心代碼:this
this.dfs = function(callback){ var color = initializeColor(); // 將全部頂點初始化爲白色 for (var i=0; i<vertices.length; i++){ if (color[vertices[i]] === 'white'){ // 對每個沒有被訪問過的頂點調用dfsVisit方法 dfsVisit(vertices[i], color, callback); // {1} } } function dfsVisit(u, color, callback){ color[u] = 'grey'; // 將頂點u置爲灰,代表訪問過但尚未徹底探索 callback && callback(u); // 執行回調函數 var neighbors = adjList.get(u); // 獲取頂點u的全部相鄰頂點 for (var i=0; i<neighbors.length; i++){ // 探索全部相鄰頂點 var w = neighbors[i]; if (color[w] === 'white'){ // 若是相鄰的頂點沒有被訪問過,則對其執行dfsVisit方法 dfsVisit(w, color, callback); } } color[u] = 'black'; // 將頂點u置爲黑,代表已經徹底訪問。 }; };
對圖1中的圖執行上述搜索算法的過程如圖2。spa
圖23d
因爲咱們是從起始點A開始搜索的,{1}處的代碼只會執行一次,由於全部其餘頂點都有一條路徑到達A點。若是從B點開始搜索,{1}處的代碼便會執行兩次。code
給定一個圖G,要獲得任意頂點u的發現時間(d[u])以及完成對該頂點的搜索時間(f[u]),能夠在上述代碼的基礎上作必定修改(修改的位置用空行隔開):blog
this.DFS = function(){ var color = initializeColor(), //{2} len = vertices.length, d = new Array(len).fill([]), // 用於保存任意頂點u的發現時間 f = new Array(len).fill([]), // 用於保存任意頂點u探索完成的時 time = 0; // 用於記錄探索時間 for (var i=0; i<vertices.length; i++){ if (color[vertices[i]] === 'white'){ DFSVisit(vertices[i], color, d, f); } } return { // 返回探索數據 discovery: d, finished: f }; function DFSVisit(u, color, d, f){ console.log('discovered ' + u); color[u] = 'grey'; d[u] = ++time; // 發現頂點u後,時間+1 var neighbors = adjList.get(u); for (var i=0; i<neighbors.length; i++){ var w = neighbors[i]; if (color[w] === 'white'){ DFSVisit(w,color, d, f); } } color[u] = 'black'; f[u] = ++time; // 探索完頂點u後,時間+1 console.log('explored ' + u); }; };
對於深度優先算法,邊是從最近發現的頂點u處被向外探索的。只有鏈接到未發現的頂點的邊被探索了。當u全部的邊都被探索了,該算法回退到u被發現的地方去探索其餘的邊。這個過程持續到咱們發現了全部從原始頂點可以觸及的頂點。若是還留有任何其餘未被發現的頂點,咱們對新源頂點重複這個過程。重複該算法,直到圖中全部的頂點都被探索了。
觀察上述代碼能夠發現,time的值只能在圖的頂點數量的一到兩倍之間,而且d[u]<f[u],即發現時間的值比完成時間的值小。
給定圖3,假定每一個頂點都是一個咱們須要去執行的任務。
圖3
這是一個有向無環圖(DAG),即任務的執行是有順序的,例如,任務F不能在任務A以前執行,而且該圖沒有環。
當咱們須要編排一些任務或步驟的執行順序時,這稱爲拓撲排序。好比,當咱們在開發一個項目時,首先咱們得從客戶那裏獲得需求,接着開發客戶要求的東西,最後交付項目,但不能先交付項目再去收集需求。
拓撲排序只能應用於DAG。用深度優先搜索算法對圖3中的任務圖進行拓撲排序:
graph = new Graph(); myVertices = ['A','B','C','D','E','F']; for (i=0; i<myVertices.length; i++){ graph.addVertex(myVertices[i]); } graph.addEdge('A', 'C'); graph.addEdge('A', 'D'); graph.addEdge('B', 'D'); graph.addEdge('B', 'E'); graph.addEdge('C', 'F'); graph.addEdge('F', 'E'); var result = graph.DFS();
最終各頂點的發現和探索完成時間會保存在result中。圖4直觀地註明了各個頂點的發現時間/探索完成時間。
圖4
那麼按照探索完成時間的倒序來排序獲得的拓撲排序爲:B - A - D - C - F - E須要注意的是,算法不一樣,獲得的拓撲排序可能不一樣,好比下面的拓撲排序也是可能的:A - B - C - D - F - E