1. 程式人生 > 實用技巧 >深度優先遍歷與廣度優先遍歷

深度優先遍歷與廣度優先遍歷

圖是一種複雜的非線性結構,它由邊(邊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] + ' -> ' var
neighors = 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
      測試成功