深度優先遍歷與廣度優先遍歷
阿新 • • 發佈:2020-12-09
圖
圖是一種複雜的非線性結構,它由邊(邊Edge)和點(頂點Vertex)組成。一條邊連線的兩個點稱為相鄰頂點。
G = (V, E)
圖分為:
- 有向圖
- 無向圖
本文探討的是無向圖
圖的表示
圖的表示一般有以下兩種:
- 鄰接矩陣:使用二維陣列來表示點與點之間是否有邊,如
arr[i][j] = 1
表示節點 i 與節點 j 之間有邊,arr[i][j] = 0
表示節點 i 與節點 j 之間沒有邊 - 鄰接表:鄰接表是圖的一種鏈式儲存結構,這種結構類似樹的子連結串列,對於圖中的每一個頂點Vi,把所有鄰接於Vi的頂點Vj鏈成一個單鏈表,這個單鏈表就是頂點Vi的鄰接表,單鏈表一般由陣列或字典結構表示。
建立圖
下面宣告圖類,Vertex 用陣列結構表示,Edge 用 map結構表示
function Graph() { this.vertices = [] // 頂點集合 this.edges = new Map() // 邊集合 } Graph.prototype.addVertex = function(v) { // 新增頂點方法 this.vertices.push(v) this.edges.set(v, []) } Graph.prototype.addEdge = function(v, w) { // 新增邊方法 let vEdge = this.edges.get(v) vEdge.push(w) let wEdge = this.edges.get(w) wEdge.push(v) this.edges.set(v, vEdge) this.edges.set(w, wEdge) } Graph.prototype.toString = function() { var s = '' for (var i=0; i<this.vertices.length; i++) { s += this.vertices[i] + ' -> ' varneighors = this.edges.get(this.vertices[i]) for (var j=0; j<neighors.length; j++) { s += neighors[j] + ' ' } s += '\n' } return s } 測試: var graph = new Graph() var vertices = [1, 2, 3, 4, 5] for (var i=0; i<vertices.length; i++) { graph.addVertex(vertices[i]) } graph.addEdge(1, 4); //增加邊 graph.addEdge(1, 3); graph.addEdge(2, 3); graph.addEdge(2, 5); console.log(graph.toString()) // 1 -> 4 3 // 2 -> 3 5 // 3 -> 1 2 // 4 -> 1 // 5 -> 2 測試成功
圖的遍歷
兩種遍歷演算法:
- 深度優先遍歷
- 廣度優先遍歷
深度優先遍歷(DFS)
深度優先遍歷(Depth-First-Search),是搜尋演算法的一種,它沿著樹的深度遍歷樹的節點,儘可能深地搜尋樹的分支。當節點v的所有邊都已被探尋過,將回溯到發現節點v的那條邊的起始節點。這一過程一直進行到已探尋源節點到其他所有節點為止,如果還有未被發現的節點,則選擇其中一個未被發現的節點為源節點並重復以上操作,直到所有節點都被探尋完成。
簡單的說,DFS就是從圖中的一個節點開始追溯,直到最後一個節點,然後回溯,繼續追溯下一條路徑,直到到達所有的節點,如此往復,直到沒有路徑為止。
DFS 可以產生相應圖的拓撲排序表,利用拓撲排序表可以解決很多問題,例如最大路徑問題。一般用堆資料結構來輔助實現DFS演算法。
注意:深度DFS屬於盲目搜尋,無法保證搜尋到的路徑為最短路徑,也不是在搜尋特定的路徑,而是通過搜尋來檢視圖中有哪些路徑可以選擇。
步驟:
- 訪問頂點v
- 依次從v的未被訪問的鄰接點出發,對圖進行深度優先遍歷;直至圖中和v有路徑相通的頂點都被訪問
- 若此時途中尚有頂點未被訪問,則從一個未被訪問的頂點出發,重新進行深度優先遍歷,直到所有頂點均被訪問過為止
實現: Graph.prototype.dfs = function() { var marked = [] for (var i=0; i<this.vertices.length; i++) { if (!marked[this.vertices[i]]) { dfsVisit(this.vertices[i]) } } function dfsVisit(u) { let edges = this.edges marked[u] = true console.log(u) var neighbors = edges.get(u) for (var i=0; i<neighbors.length; i++) { var w = neighbors[i] if (!marked[w]) { dfsVisit(w) } } } } 測試: graph.dfs() // 1 // 4 // 3 // 2 // 5 測試成功
廣度優先遍歷(BFS)
廣度優先遍歷(Breadth-First-Search)是從根節點開始,沿著圖的寬度遍歷節點,如果所有節點均被訪問過,則演算法終止,BFS 同樣屬於盲目搜尋,一般用佇列資料結構來輔助實現BFS
BFS從一個節點開始,嘗試訪問儘可能靠近它的目標節點。本質上這種遍歷在圖上是逐層移動的,首先檢查最靠近第一個節點的層,再逐漸向下移動到離起始節點最遠的層
步驟:
- 建立一個佇列,並將開始節點放入佇列中
- 若佇列非空,則從佇列中取出第一個節點,並檢測它是否為目標節點
- 若是目標節點,則結束搜尋,並返回結果
- 若不是,則將它所有沒有被檢測過的位元組點都加入佇列中
- 若佇列為空,表示圖中並沒有目標節點,則結束遍歷
實現: Graph.prototype.bfs = function(v) { var queue = [], marked = [] marked[v] = true queue.push(v) // 新增到隊尾 while(queue.length > 0) { var s = queue.shift() // 從隊首移除 if (this.edges.has(s)) { console.log('visited vertex: ', s) } let neighbors = this.edges.get(s) for(let i=0;i<neighbors.length;i++) { var w = neighbors[i] if (!marked[w]) { marked[w] = true queue.push(w) } } } } 測試: graph.bfs(1) // visited vertex: 1 // visited vertex: 4 // visited vertex: 3 // visited vertex: 2 // visited vertex: 5 測試成功