學習javascript數據結構與算法(六)——圖

前言

本文是博主深感算法方面的不足,做的一系列讀書筆記和源碼分析。
原文地址:學習javascript數據結構與算法(六)——圖,以爲有用的話能夠給個star,謝謝啦。
做者:wengjqjavascript

一、 圖

圖是網絡結構的抽象模型。圖是一組由邊鏈接的節點,任何二元關係均可以用圖來表示。前端

1.一、圖的相關概念

一個圖G = (V,E)由如下元素組成。java

  • V:一組頂點
  • E:一組邊,鏈接V中的頂點

下圖表示一個圖:git

由一條邊鏈接在一塊兒的頂點稱爲相鄰頂點。好比上圖的A和B是相鄰的,A和D是相鄰的,A和C是相鄰的,A和E不是相鄰的。一個頂點的度是其相鄰頂點的數量。好比,A和其餘三個頂點相鏈接,所以,A的度爲3;E和其餘兩個頂點相連,所以E的度爲2。路徑是頂點v1,v2,...,vk的一個連續序列,其中v[i]和v[i+1]是相鄰的。以上的圖爲例,其中的路徑有ABEI和ACDG。github

1.二、圖的表示

圖最多見的實現是鄰接矩陣。每一個節點都和一個整數相關聯,該整數做爲數組的索引。這裏不做討論。另外一種圖的表示方式是一種叫作鄰接表的動態數據結構。鄰接表由圖中每一個頂點的相鄰頂點列表所組成。咱們能夠用數組,鏈表,甚至是散列表或是字典來表示相鄰頂點列表。下面的示意圖展現了鄰接表的數據結構。咱們後面也會用代碼示例這種數據結構。算法

鄰接表

示例代碼以下:數組

function Graph() {
    var vertices = []; //存儲圖中全部的頂點名字
    var adjList = new Dictionary();//用以前的一個字典來存儲鄰接表
    this.addVertex = function(v){ //添加頂點
        vertices.push(v);
        adjList.set(v, []); //頂點爲鍵,字典值爲空數組
    };
    this.addEdge = function(v, w){ //添加邊
        adjList.get(v).push(w); //基於有向圖
        adjList.get(w).push(v); //基於無向圖
    };
    this.toString = function(){
        var s = '';
        for (var i=0; i<vertices.length; i++){
            s += vertices[i] + ' -> ';
            var neighbors = adjList.get(vertices[i]);
            for (var j=0; j<neighbors.length; j++){
                s += neighbors[j] + ' ';
            }
            s += '\n';
        }
        return s;
    };
    var initializeColor = function(){
        var color = [];
        for (var i=0; i<vertices.length; i++){
            color[vertices[i]] = 'white';
        }
        return color;
    };
}  
//測試
var graph = new Graph();
var myVertices = ['A','B','C','D','E','F','G','H','I'];
for (var i=0; i<myVertices.length; i++){
    graph.addVertex(myVertices[i]);
}
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('C', 'D');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('E', 'I');
console.log(graph.toString());
結果以下:
A -> B C D 
B -> A E F 
C -> A D G 
D -> A C G H 
E -> B I 
F -> B 
G -> C D 
H -> D 
I -> E 複製代碼

1.三、圖的遍歷

和樹的數據結構相似,咱們能夠訪問圖的全部節點。有兩種算法能夠對圖進行遍歷:廣度優先搜索(Breadth-First Search,BFS)和深度優先搜索(Depth-First Search,DFS)。圖遍歷能夠用來尋找特定的頂點或尋找兩個頂點之間的路徑,檢查圖是否連通,檢查圖是否含有環等。微信

圖遍歷算法的思想是必須追蹤每一個第一次訪問的節點,而且追蹤有哪些節點尚未被徹底探索。對於兩種圖遍歷算法,都須要明確指出第一個被訪問的頂點。徹底探索一個頂點要求咱們查看該頂點的每一條邊。對應每一條邊所鏈接的沒有被訪問過的頂點,將其標註爲被發現的,並將其加進待訪問頂點列表中。網絡

爲了保證算法的效率,務必訪問每一個頂點至多兩次。連通圖中每條邊和頂點都會被訪問到。當要標註已經訪問過的頂點時,咱們用三種顏色來反映它們的狀態:數據結構

  • 白色:表示該頂點尚未被訪問。
  • 灰色:表示該頂點被訪問過,但並未被探索過。
  • 黑色:表示該頂點被訪問過且被徹底搜索過。

1.3.一、廣度優先搜索(BFS)

廣度優先搜索算法會從指定的第一個頂點開始遍歷圖,先訪問其全部的相鄰點,就像一次訪問圖的一層。換句話說,就是先寬後深的訪問頂點。如下是從頂點v開始的廣度優先搜索算法所遵循的步驟。

  • (1)建立一個隊列Q。
  • (2)將v標註爲被發現的(灰色),並將v入隊列Q。
  • (3)若是Q非空,則運行如下步驟:
    (a)將u從Q中出隊列;
        (b)將標註u爲被發現的(灰色);
        (c)將u全部未被訪問過的鄰點(白色)入隊列;
        (d)將u標註爲已被探索的(黑色);複製代碼

示例代碼以下:

var initializeColor = function(){
      var color = [];
      for (var i=0; i<vertices.length; i++){
          color[vertices[i]] = 'white'; //初始化全部的頂點都是白色
      }
      return color;
  };
  this.bfs = function(v, callback){
      var color = initializeColor(),
          queue = new Queue(); //建立一個隊列
      queue.enqueue(v); //入隊列
      while (!queue.isEmpty()){
          var u = queue.dequeue(), //出隊列
              neighbors = adjList.get(u); //鄰接表
          color[u] = 'grey'; //發現了但還未完成對其的搜素
          for (var i=0; i<neighbors.length; i++){
              var w = neighbors[i]; //頂點名
              if (color[w] === 'white'){
                  color[w] = 'grey'; //發現了它
                  queue.enqueue(w); //入隊列循環
              }
          }
          color[u] = 'black'; //已搜索過
          if (callback) {
              callback(u);
          }
      }
  };
      //測試以下:
     function printNode(value){
         console.log('Visited vertex: ' + value);
     }
     graph.bfs(myVertices[0], printNode);
     結果以下:
     Visited vertex: A
     Visited vertex: B
     Visited vertex: C
     Visited vertex: D
     Visited vertex: E
     Visited vertex: F
     Visited vertex: G
     Visited vertex: H
     Visited vertex: I複製代碼

1.3.二、深度優先搜索(BFS)

深度優先搜索算法將會是從第一個指定的頂點開始遍歷圖,沿着路徑直到這條路徑最後一個頂點被訪問了,接着原路回退並探索下一條路徑。換句話說,它是先深度後廣度地訪問頂點。深度優先搜索算法不須要一個源頂點。要訪問頂點v,照以下的步驟作:

  • (1)標註v爲被發現的(灰色)。
  • (2)對應v的全部未訪問的鄰點w。
    (a)訪問頂點w。複製代碼
  • (3)標註v爲已被探索的(黑色)。

如你所見,深度優先搜索的步驟是遞歸的,這意味着深度優先搜索算法使用棧來存儲函數調用(由遞歸調用所建立的棧)。示例代碼以下:

this.dfs = function(callback){
    var color = initializeColor(); //前面的顏色數組
    for (var i=0; i<vertices.length; i++){
        if (color[vertices[i]] === 'white'){
            dfsVisit(vertices[i], color, callback); //遞歸調用未被訪問過的頂點
        }
    }
};
var dfsVisit = function(u, color, callback){
    color[u] = 'grey';
    if (callback) {
        callback(u);
    }
    var neighbors = adjList.get(u); //鄰接表
    for (var i=0; i<neighbors.length; i++){
        var w = neighbors[i];
        if (color[w] === 'white'){
            dfsVisit(w, color, callback); //添加頂點w入棧
        }
    }
    color[u] = 'black';
};
//測試以下:
function printNode(value){
   console.log('Visited vertex: ' + value);
}
graph.dfs(printNode);
結果以下:
Visited vertex: A
Visited vertex: B
Visited vertex: E
Visited vertex: I
Visited vertex: F
Visited vertex: C
Visited vertex: D
Visited vertex: G
Visited vertex: H複製代碼

本文對你有幫助?歡迎掃碼加入前端學習小組微信羣:

相關文章
相關標籤/搜索