本文全部的算法嚴格按照《算法導論》,本文將詳細的對BFS
和DFS
進行分析,並提供算法的 js
實現,同時會對建立鏈表的方式進行優化node
圖的表示分爲對頂點集 V
的表示和對邊集 E
的表示,這裏的重點是如何表示邊,邊的表示分爲鄰接矩陣
和鄰接鏈表
這兩種表示方法,鄰接矩陣
適合表示邊稠密的圖,其消耗空間爲|V|*|V|
,若是是無向圖,則能夠用上三角矩陣或者下三角矩陣來表示,是空間消耗變爲|V|*|V|/2
,鄰接鏈表
適合表示邊稀疏的圖,其消耗的空間爲 O(|V|+|E|)
,用鄰接鏈表表示圖很緊湊,沒有空間浪費,用《算法導論》中的原話就是,鄰接鏈表表示圖,魯棒性很高。本文涉及的圖,所有用鄰接鏈表表示。算法
從上圖能夠看到咱們將圖的分爲兩部分,頂點和邊,咱們分別對這兩部分進行表示,咱們用數組去存放頂點,用鏈表去描述邊。A-E
作爲節點的標識。數字表示頂點在數組中的位置。由這幅圖能夠看到從節點 A
發出的邊有兩條,分別是 <A,C>
,和 <A,D>
數組
廣度優先搜索的思想是,對於圖G和給定的節點s
,廣度優先搜索須要一個輔助的先進先出的隊列 Q
瀏覽器
s
加入到Q
中s
從Q
總移出,用臨時變量接受s
,若是s
沒有被訪問過,從s
出發,發現s
的全部鄰接節點並放入Q
中s
Q
隊列的第一個元素移除隊列做爲新的s
執行2-4
過程直到隊列Q
爲空function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.color = this.WHITE; //初始爲 白色 this.pi = null; //初始爲 無前驅 this.d = this.INFINITY; //初始爲 無窮大 this.edges = null; //由頂點發出的全部邊 this.value = null; //節點的值 默認爲空 } Vertex.prototype = { constructor: Vertex, WHITE: 'white', //白色 GRAY: 'gray', //灰色 BLACK: 'black', //黑色 INFINITY: null, //d 爲 null 時表示無窮大 }
爲了跟蹤算法的進展,咱們對圖進行搜索的時候會對圖中的頂點進行塗色,圖初始化是頂點所有爲白色
,當第一次發現某個節點時,咱們將他塗爲灰色
,當對某個節點訪問完成後,咱們將它塗爲黑色
。在這裏咱們看到每一個節點都有 五個 屬性,color
表示節點的顏色,pi
表示前驅結點,d
表示廣度優先搜索中從源節點到當前節點的距離,edges
表示從當前節點發出的全部邊,value
表示節點存放的數據數據結構
function Edge() { if (!(this instanceof Edge)) return new Edge(); this.index = null; //邊所依附的節點的位置 this.sibling = null; }
能夠看到,邊包含兩個兩個屬性,index
,和sibling
,index
表示這條邊鏈接的節點在頂點數組中的位置,sibling
只想下一個鏈接兄弟節點的邊。優化
function Graph() { if (!(this instanceof Graph)) return new Graph(); this.graph = []; //存放頂點的數組 } Graph.prototype = { constructor: Graph, addNode: function (node) { this.graph.push(node); }, getNode: function (index) { return this.graph[index]; } }
//建立 頂點 var vA = Vertex(); var vB = Vertex(); var vC = Vertex(); var vD = Vertex(); var vE = Vertex(); var vF = Vertex(); vA.value = 'A'; vB.value = 'B'; vC.value = 'C'; vD.value = 'D'; vE.value = 'E'; vF.value = 'F'; //構建由 A 節點發出的邊集 var eA1 = Edge(); var eA2 = Edge(); eA1.index = 1; eA2.index = 3; eA1.sibling = eA2; vA.edges = eA1; //構建有 B 節點發出的邊集 var eB1 = Edge(); var eB2 = Edge(); var eB3 = Edge(); eB1.index = 0; eB2.index = 4; eB3.index = 2; eB1.sibling = eB2; eB2.sibling = eB3; vB.edges = eB1; //構建由 C 節點發出的邊 var eC1 = Edge(); var eC2 = Edge(); var eC3 = Edge(); eC1.index = 1; eC2.index = 4; eC3.index = 5; eC1.sibling = eC2; eC2.sibling = eC3; vC.edges = eC1; //構建由 D 節點發出的邊 var eD1 = Edge(); eD1.index = 0; vD.edges = eD1; //構建由 E 節點發出的邊 var eE1 = Edge(); var eE2 = Edge(); var eE3 = Edge(); eE1.index = 1; eE2.index = 2; eE3.index = 5; eE1.sibling = eE2; eE2.sibling = eE3; vE.edges = eE1; //構建由 F 節點發出的邊 var eF1 = Edge(); var eF2 = Edge(); eF1.index = 2; eF2.index = 4; eF1.sibling = eF2; vF.edges = eF1; //構建圖 var g = Graph(); g.addNode(vA); g.addNode(vB); g.addNode(vC); g.addNode(vD); g.addNode(vE); g.addNode(vF);
//廣度優先搜索 function BFS(g, s) { let queue = []; //輔助隊列 Q s.color = s.GRAY; //首次發現s塗爲灰色 s.d = 0; //距離爲0 queue.push(s); //將s放入隊列 Q while (queue.length > 0) { //當隊列Q中有頂點時執行搜索 let u = queue.shift(); //將Q中的第一個元素移出 if (u.edges == null) continue; //若是從當前頂點沒有發出邊 let sibling = u.edges; //獲取表示鄰接邊的鏈表的頭節點 while (sibling != null) { //當鏈表不爲空 let index = sibling.index; //當前邊所鏈接的頂點在隊列中的位置 let n = g.getNode(index); //獲取頂點 if (n.color == n.WHITE) { //若是沒有被訪問過 n.color = n.GRAY; //塗爲灰色 n.d = u.d + 1; //距離加1 n.pi = u; //設置前驅節點 queue.push(n); //將 n 放入隊列 Q } sibling = sibling.sibling; //下一條邊 } u.color = u.BLACK; //當前頂點訪問結束 塗爲黑色 } }
//數據結構 鄰接鏈表-頂點 function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.color = this.WHITE; //初始爲 白色 this.pi = null; //初始爲 無前驅 this.d = this.INFINITY; //初始爲 無窮大 this.edges = null; //由頂點發出的全部邊 this.value = null; //節點的值 默認爲空 } Vertex.prototype = { constructor: Vertex, WHITE: 'white', //白色 GRAY: 'gray', //灰色 BLACK: 'black', //黑色 INFINITY: null, //d 爲 null 時表示無窮大 } //數據結構 鄰接鏈表-邊 function Edge() { if (!(this instanceof Edge)) return new Edge(); this.index = null; //邊所依附的節點的位置 this.sibling = null; } //數據結構 圖-G function Graph() { if (!(this instanceof Graph)) return new Graph(); this.graph = []; } Graph.prototype = { constructor: Graph, //這裏加進來的已經具有了邊的關係 addNode: function (node) { this.graph.push(node); }, getNode: function (index) { return this.graph[index]; } } //廣度優先搜索 function BFS(g, s) { let queue = []; s.color = s.GRAY; s.d = 0; queue.push(s); while (queue.length > 0) { let u = queue.shift(); if (u.edges == null) continue; let sibling = u.edges; while (sibling != null) { let index = sibling.index; let n = g.getNode(index); if (n.color == n.WHITE) { n.color = n.GRAY; n.d = u.d + 1; n.pi = u; queue.push(n); } sibling = sibling.sibling; } u.color = u.BLACK; console.log(u); } } //建立 頂點 var vA = Vertex(); var vB = Vertex(); var vC = Vertex(); var vD = Vertex(); var vE = Vertex(); var vF = Vertex(); vA.value = 'A'; vB.value = 'B'; vC.value = 'C'; vD.value = 'D'; vE.value = 'E'; vF.value = 'F'; //構建由 A 節點發出的邊集 var eA1 = Edge(); var eA2 = Edge(); eA1.index = 1; eA2.index = 3; eA1.sibling = eA2; vA.edges = eA1; //構建有 B 節點發出的邊集 var eB1 = Edge(); var eB2 = Edge(); var eB3 = Edge(); eB1.index = 0; eB2.index = 4; eB3.index = 2; eB1.sibling = eB2; eB2.sibling = eB3; vB.edges = eB1; //構建由 C 節點發出的邊 var eC1 = Edge(); var eC2 = Edge(); var eC3 = Edge(); eC1.index = 1; eC2.index = 4; eC3.index = 5; eC1.sibling = eC2; eC2.sibling = eC3; vC.edges = eC1; //構建由 D 節點發出的邊 var eD1 = Edge(); eD1.index = 0; vD.edges = eD1; //構建由 E 節點發出的邊 var eE1 = Edge(); var eE2 = Edge(); var eE3 = Edge(); eE1.index = 1; eE2.index = 2; eE3.index = 5; eE1.sibling = eE2; eE2.sibling = eE3; vE.edges = eE1; //構建由 F 節點發出的邊 var eF1 = Edge(); var eF2 = Edge(); eF1.index = 2; eF2.index = 4; eF1.sibling = eF2; vF.edges = eF1; //構建圖 var g = Graph(); g.addNode(vA); g.addNode(vB); g.addNode(vC); g.addNode(vD); g.addNode(vE); g.addNode(vF); BFS(g, vB);
頂點的訪問順序爲 B->A->E->C->D->Fthis
d-f
之間是灰色,在f
以後是黑色,時間戳爲1
到2*|v|
之間的整數深度優先搜索的數據結構只有在表示頂點時稍有不一樣,其它的都相同,這裏給出表示頂點的數據結構spa
function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.color = this.WHITE; //初始爲 白色 this.pi = null; //初始爲 無前驅 this.d = null; //時間戳 發現時 this.f = null; //時間戳 鄰接鏈表掃描完成時 this.edges = null; //由頂點發出的全部邊 this.value = null; //節點的值 默認爲空 } Vertex.prototype = { constructor: Vertex, WHITE: 'white', //白色 GRAY: 'gray', //灰色 BLACK: 'black', //黑色 }
能夠看到頂點數據結構中的多了一個f
,同時d
的含義也發生了變化d
和f
做爲發現和訪問完成的時間戳,取值爲從1
到2*|v|
prototype
function DFS(g) { let t = 0; //時間戳 for (let v of g.vertexs) { //讓每一個節點都做爲一次源節點 if (v.color == v.WHITE) DFSVisit(g, v); } function DFSVisit(g, v) { t = t + 1; //時間戳加一 v.d = t; v.color = v.GRAY; let sibling = v.edges; while (sibling != null) { let index = sibling.index; let n = g.getNode(index); if (n.color == n.WHITE) { n.pi = v; DFSVisit(g, n); //先縱向找 } sibling = sibling.sibling; //利用遞歸的特性來回溯 } v.color = v.BLACK; t = t + 1; //時間戳加一 v.f = t; } }
function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.color = this.WHITE; //初始爲 白色 this.pi = null; //初始爲 無前驅 this.d = null; //時間戳 發現時 this.f = null; //時間戳 鄰接鏈表掃描完成 this.edges = null; //由頂點發出的全部邊 this.value = null; //節點的值 默認爲空 } Vertex.prototype = { constructor: Vertex, WHITE: 'white', //白色 GRAY: 'gray', //灰色 BLACK: 'black', //黑色 } //數據結構 圖-G function Graph() { if (!(this instanceof Graph)) return new Graph(); this.vertexs = []; } Graph.prototype = { constructor: Graph, addNode: function (node) { this.vertexs.push(node); }, getNode: function (index) { return this.vertexs[index]; } } //這裏 t 做爲全局變量和參數時結果不同 由於 js 對於基本類型的參數採用的是值捕獲,對於對象類型的參數採用的是引用捕獲 function DFS(g) { let t = 0; for (let v of g.vertexs) { if (v.color == v.WHITE) DFSVisit(g, v); } function DFSVisit(g, v) { t = t + 1; v.d = t; v.color = v.GRAY; let sibling = v.edges; while (sibling != null) { let index = sibling.index; let n = g.getNode(index); if (n.color == n.WHITE) { n.pi = v; DFSVisit(g, n); //先縱向找 } sibling = sibling.sibling; //利用遞歸的特性來回溯 } v.color = v.BLACK; t = t + 1; v.f = t; console.log(v); } } //數據結構 鄰接鏈表-邊 function Edge() { if (!(this instanceof Edge)) return new Edge(); this.index = null; //邊所依附的節點的位置 this.sibling = null; } //建立 頂點 var vA = Vertex(); var vB = Vertex(); var vC = Vertex(); var vD = Vertex(); var vE = Vertex(); var vF = Vertex(); vA.value = 'A'; vB.value = 'B'; vC.value = 'C'; vD.value = 'D'; vE.value = 'E'; vF.value = 'F'; //構建由 A 節點發出的邊集 var eA1 = Edge(); var eA2 = Edge(); eA1.index = 1; eA2.index = 3; eA1.sibling = eA2; vA.edges = eA1; //構建有 B 節點發出的邊集 var eB1 = Edge(); var eB2 = Edge(); var eB3 = Edge(); eB1.index = 0; eB2.index = 4; eB3.index = 2; eB1.sibling = eB2; eB2.sibling = eB3; vB.edges = eB1; //構建由 C 節點發出的邊 var eC1 = Edge(); var eC2 = Edge(); var eC3 = Edge(); eC1.index = 1; eC2.index = 4; eC3.index = 5; eC1.sibling = eC2; eC2.sibling = eC3; vC.edges = eC1; //構建由 D 節點發出的邊 var eD1 = Edge(); eD1.index = 0; vD.edges = eD1; //構建由 E 節點發出的邊 var eE1 = Edge(); var eE2 = Edge(); var eE3 = Edge(); eE1.index = 1; eE2.index = 2; eE3.index = 5; eE1.sibling = eE2; eE2.sibling = eE3; vE.edges = eE1; //構建由 F 節點發出的邊 var eF1 = Edge(); var eF2 = Edge(); eF1.index = 2; eF2.index = 4; eF1.sibling = eF2; vF.edges = eF1; //構建圖 var g = Graph(); g.addNode(vA); g.addNode(vB); g.addNode(vC); g.addNode(vD); g.addNode(vE); g.addNode(vF); DFS(g);
節點訪問順序爲 F->C->E->B->D->Acode
咱們發現構建圖的操做過於繁瑣,因而想簡化圖的構建方式,簡化後以下
var vertexs = ['A', 'B', 'C', 'D', 'E', 'F']; var edges = { A: [{ id: 'B', w: 1 }, { id: 'D', w: 2 }], B: [{ id: 'A', w: 3 }, { id: 'E', w: 3 }, { id: 'C', w: 7 }], C: [{ id: 'B', w: 5 }, { id: 'E', w: 3 }, { id: 'F', w: 4 }], D: [{ id: 'A', w: 2 }], E: [{ id: 'B', w: 3 }, { id: 'C', w: 7 }, { id: 'F', w: 3 }], F: [{ id: 'C', w: 6 }, { id: 'E', w: 9 }] } var g = Graph(); g.initVertex(vertexs); g.initEdge(edges);
咱們想用這種方式初始化一個圖,w爲邊的權值
這裏的改進只是針對圖的構建,全部不管時BFS,仍是DFS,表示頂點和邊的數據結構都沒有變,只有對錶示圖的數據結構 Graph進行改進
//數據結構 圖-G
//數據結構 圖-G function Graph() { if (!(this instanceof Graph)) return new Graph(); this.graph = []; this.refer = new Map(); //字典 用來映射標節點的識符和數組中的位置 } Graph.prototype = { constructor: Graph, //這裏加進來的已經具有了邊的關係 addNode: function(node) { this.graph.push(node); }, getNode: function(index) { return this.graph[index]; }, //建立圖的 節點 initVertex: function(vertexs) { //建立節點並初始化節點屬性 value for (let value of vertexs) { let vertex = Vertex(); vertex.value = value; this.graph.push(vertex); } //初始化 字典 for (let i in this.graph) { this.refer.set(this.graph[i].value,i); } }, //創建圖中 邊 的關係 initEdge: (function(){ //建立鏈表,返回鏈表的第一個節點 function createLink(index, len, edges, refer) { if (index >= len) return null; let edgeNode = Edge(); edgeNode.index = refer.get(edges[index].id); //邊鏈接的節點 用在數組中的位置表示 參照字典 edgeNode.w = edges[index].w; //邊的權值 edgeNode.sibling = createLink(++index, len, edges, refer); //經過遞歸實現 回溯 return edgeNode; } return function(edges) { for (let field in edges) { let index = this.refer.get(field); //從字典表中找出節點在 graph 中的位置 let vertex = this.graph[index]; //獲取節點 vertex.edges = createLink(0, edges[field].length, edges[field], this.refer); } } }()) }
DFS相同
function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.color = this.WHITE; //初始爲 白色 this.pi = null; //初始爲 無前驅 this.d = this.INFINITY; //初始爲 無窮大 this.edges = null; //由頂點發出的全部邊 this.value = null; //節點的值 默認爲空 } Vertex.prototype = { constructor: Vertex, WHITE: 'white', //白色 GRAY: 'gray', //灰色 BLACK: 'black', //黑色 INFINITY: null, //d 爲 null 時表示無窮大 } //數據結構 鄰接鏈表-邊 function Edge() { if (!(this instanceof Edge)) return new Edge(); this.index = null; //邊所依附的節點的位置 this.sibling = null; this.w = null; //保存邊的權值 } //數據結構 圖-G function Graph() { if (!(this instanceof Graph)) return new Graph(); this.graph = []; this.refer = new Map(); //字典 用來映射標節點的識符和數組中的位置 } Graph.prototype = { constructor: Graph, //這裏加進來的已經具有了邊的關係 addNode: function(node) { this.graph.push(node); }, getNode: function(index) { return this.graph[index]; }, //建立圖的 節點 initVertex: function(vertexs) { //建立節點並初始化節點屬性 value for (let value of vertexs) { let vertex = Vertex(); vertex.value = value; this.graph.push(vertex); } //初始化 字典 for (let i in this.graph) { this.refer.set(this.graph[i].value,i); } }, //創建圖中 邊 的關係 initEdge: (function(){ //建立鏈表,返回鏈表的第一個節點 function createLink(index, len, edges, refer) { if (index >= len) return null; let edgeNode = Edge(); edgeNode.index = refer.get(edges[index].id); //邊鏈接的節點 用在數組中的位置表示 參照字典 edgeNode.w = edges[index].w; //邊的權值 edgeNode.sibling = createLink(++index, len, edges, refer); //經過遞歸實現 回溯 return edgeNode; } return function(edges) { for (let field in edges) { let index = this.refer.get(field); //從字典表中找出節點在 graph 中的位置 let vertex = this.graph[index]; //獲取節點 vertex.edges = createLink(0, edges[field].length, edges[field], this.refer); } } }()) } //廣度優先搜索 function BFS(g, s) { let queue = []; s.color = s.GRAY; s.d = 0; queue.push(s); while (queue.length > 0) { let u = queue.shift(); if (u.edges == null) continue; let sibling = u.edges; while (sibling != null) { let index = sibling.index; let n = g.getNode(index); if (n.color == n.WHITE) { n.color = n.GRAY; n.d = u.d + 1; n.pi = u; queue.push(n); } sibling = sibling.sibling; } u.color = u.BLACK; console.log(u) } } var vertexs = ['A', 'B', 'C', 'D', 'E', 'F']; var edges = { A: [{ id: 'B', w: 1 }, { id: 'D', w: 2 }], B: [{ id: 'A', w: 3 }, { id: 'E', w: 3 }, { id: 'C', w: 7 }], C: [{ id: 'B', w: 5 }, { id: 'E', w: 3 }, { id: 'F', w: 4 }], D: [{ id: 'A', w: 2 }], E: [{ id: 'B', w: 3 }, { id: 'C', w: 7 }, { id: 'F', w: 3 }], F: [{ id: 'C', w: 6 }, { id: 'E', w: 9 }] } //構建圖 var g = Graph(); g.initVertex(vertexs); g.initEdge(edges); //調用BFS BFS(g, g.graph[1]);
着重體會