本文全部的算法严格按照《算法导论》,本文将详细的对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]);
着重体会