javascript数据结构与算法:图的遍历、广度|深度优先搜索

2018-08-3014:22:19数据结构与算法Comments2,858 views字数 8508阅读模式

2.1 图的相关概念

由一条边连接在一起的顶点称为相邻顶点。一个顶点的度是其相邻顶点的数量。如果图中不存在环,则称该图是无环的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

如果图中每两个顶点间都存在路径,则该图是连通的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

图可以是无向的(边没有方向)或是有向的(有向图)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

图还可以是未加权的或是加权的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

图最常见的实现是邻接矩阵。每个节点都和一个整数相关联,该整数将作为数组的索引。我 们用一个二维数组来表示顶点之间的连接。如果索引为i的节点和索引为j的节点相邻,则array[i][j] === 1,否则array[i][j] === 0,邻接矩阵如下图所示:
javascript数据结构与算法:图的遍历、广度|深度优先搜索文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

我们也可以使用一种叫作邻接表的动态数据结构来表示图。邻接表由图中每个顶点的相邻顶点列表所组成。存在好几种方式来表示这种数据结构。我们可以用列表(数组)、链表,甚至是 散列表或是字典来表示相邻顶点列表。下面的示意图展示了邻接表数据结构。
javascript数据结构与算法:图的遍历、广度|深度优先搜索文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

我们还可以用关联矩阵来表示图。在关联矩阵中,矩阵的行表示顶点,列表示边。如下图所示,我们使用二维数组来表示两者之间的连通性,如果顶点v是边e的入射点,则array[v][e] === 1; 否则,array[v][e]===0。关联矩阵通常用于边的数量比顶点多的情况下,以节省空间和内存。
javascript数据结构与算法:图的遍历、广度|深度优先搜索文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

尽管邻接表可能对大多数问题来说都是更好的选择,但以上两种表示法都很有用,且它们有 着不同的性质(例如,要找出顶点v和w是否相邻,使用邻接矩阵会比较快)。在后面示例中, 我们将会使用邻接表表示法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

2.2 图的遍历

和树数据结构类似,我们可以访问图的所有节点。有两种算法可以对图进行遍历:广度优先 搜索(Breadth-First Search,BFS)和深度优先搜索(Depth-First Search,DFS)。图遍历可以用来寻找特定的顶点或寻找两个顶点之间的路径,检查图是否连通,检查图是否含有环等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

图遍历算法的思想是必须追踪每个第一次访问的节点,并且追踪有哪些节点还没有被完全探索。对于两种图遍历算法,都需要明确指出第一个被访问的顶点。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

完全探索一个顶点要求我们查看该顶点的每一条边。对于每一条边所连接的没有被访问过的顶点,将其标注为被发现的,并将其加进待访问顶点列表中。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

为了保证算法的效率,务必访问每个顶点至多两次。连通图中每条边和顶点都会被访问到。广度优先搜索算法和深度优先搜索算法基本上是相同的,只有一点不同,那就是待访问顶点 列表的数据结构。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

javascript数据结构与算法:图的遍历、广度|深度优先搜索文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

2.3 广度优先搜索

广度优先搜索算法会从指定的第一个顶点开始遍历图,先访问其所有的相邻点,就像一次访问图的一层。换句话说,就是先宽后深地访问顶点。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

广度优先搜索和深度优先搜索都需要标注被访问过的顶点。为此,我们将使用一个辅助数组color。由于当算法开始执行时,所有的顶点颜色都是白色(行{1}),所以我们可以创建一个辅 助函数initializeColor,为这两个算法执行此初始化操作。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

我们会用到一个队列结构。队列的实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

2.3.1广度优先搜索的基本实现

//广度优先搜索算法 v表示初始节点,callback表示回调。
Graph.prototype.bfs = function(v, callback){
    var color = initializeColor(this.vertices);
    var queue = new Queue();//存储待访问和待探索的节点
    queue.enqueue(v);
    while(!queue.isEmpty()){
        var u = queue.dequeue();
        //获取u的相邻节点列表
        var neighbors = this.adjList.get(u);
        color[u] = 'grey';
        for(var i = 0; i < neighbors.length; i++){
            var w = neighbors[i];
            //如果从没有标记过,则标记为grey,加入队列
            if (color[w] === 'white') {
                color[w] = 'grey';
                queue.enqueue(w);
            }
        }
        //所有相邻节点都被标记了,所以改为黑色
        color[u] = 'black';
        //如果对于标记过得节点有操作,通过callback操作
        if (callback) {
            callback(u);
        }
    }
};

2.3.2 广度优先实现最短路径查找

给定一个图G和源顶点v,找出对每个顶点u,u和v之间最短路径的距离。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

	//用BFS实现最短路径
	Graph.prototype.BFS = function(v, callback) {
	    var color = initializeColor(this.vertices);
	    var queue = new Queue(); //存储待访问和待探索的节点
	    var d = [];
	    var pred = [];
	    queue.enqueue(v);
	    for (var i = 0; i < this.vertices.length; i++) {
	        d[this.vertices[i]] = 0;
	        pred[this.vertices[i]] = null;
	    }
	    while (!queue.isEmpty()) {
	        var u = queue.dequeue();
	        //获取u的相邻节点列表
	        var neighbors = this.adjList.get(u);
	        color[u] = 'grey';
	        for (var i = 0; i < neighbors.length; i++) {
	            var w = neighbors[i];
	            //如果从没有标记过,则标记为grey,加入队列
	            if (color[w] === 'white') {
	                color[w] = 'grey';
	                d[w] = d[u] + 1;
	                pred[w] = u;
	                queue.enqueue(w);
	            }
	        }
	        //所有相邻节点都被标记了,所以改为黑色
	        color[u] = 'black';
	        //如果对于标记过得节点有操作,通过callback操作
	        if (callback) {
	            callback(u);
	        }
	    }
	    return {
	        distances: d,
	        predecessors: pred
	    }
	};

2.3.3 深度优先搜索基本实现

	//深度优先基本实现
	Graph.prototype.dfs = function(callback) {
	    var self = this;
	    function dfsVisit(u, color, callback) {
	        color[u] = 'grey';
	        if (callback) {
	            callback(u);
	        }
	        var neighbors = self.adjList.get(u);
	        for (var i = 0; i < neighbors.length; i++) {
	            var w = neighbors[i];
	            if (color[w] === 'white') {
	                dfsVisit(w, color, callback);
	            }
	        }
	        color[u] = 'black';
	    }
	    var color = initializeColor(this.vertices);
	    for (var i = 0; i < this.vertices.length; i++) {
	        if (color[this.vertices[i]] === 'white') {
	            dfsVisit(this.vertices[i], color, callback);
	        }
	    }
	};

2.3.4 深度优先搜索实现拓扑排序

当我们需要编排一些任务或步骤的执行顺序时,这称为拓扑排序。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

	//DFS可以实现输出被访问顶点的顺序,可以用于拓扑排序实现。
	Graph.prototype.DFS = function(){
	    var time = 0;
	    var self = this;
	    function DFSVisit(u,color,d,f,p){
	        //console.log('discovered ' + u);
	        color[u] = 'grey';
	        d[u] = ++time;
	        var neighbors = self.adjList.get(u);
	        for(var i = 0; i < neighbors.length; i++){
	            var w = neighbors[i];
	            if (color[w] === 'white') {
	                p[w] = u;
	                DFSVisit(w,color,d,f,p);
	            }
	        }
	        color[u] = 'black';
	        f[u] = ++time;
	        //console.log('explored ' + u);
	    }
	    var color = initializeColor(this.vertices);
	    var d = [];
	    var f = [];
	    var p = [];
	    var time = 0;
	    for(var i = 0; i < this.vertices.length; i++){
	        f[this.vertices[i]] = 0;
	        d[this.vertices[i]] = 0;
	        p[this.vertices[i]] = null;
	    }
	    for(var i = 0; i< this.vertices.length; i++){
	        if (color[this.vertices[i]] === 'white') {
	            DFSVisit(this.vertices[i], color, d, f, p);
	        }
	    }
	    return {
	        discovery:d,
	        finished:f,
	        predecessors:p
	    }
	};

2.4 图的实现

我们会实现一个邻接表的图结构。我们使用一个数组来存储图中所有顶点的名字,以及一个字典 字典实现来存储邻接表字典将会使用顶点的名字作为键,邻接顶点列表作为值。vertices数组和adjList字典两者都是我们Graph类的私有属性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html

	function Graph() {
	    this.vertices = []; //点数组
	    this.adjList = new Dictionary();
	    if ((typeof this.addVertex !== 'function') && (typeof this.addVertex !== 'string')) {
	        //私有方法,标记节点颜色 未被访问过是white 被发现是grey 已被探索black。
	        function initializeColor(vertices) {
	            var color = [];
	            for (var i = 0; i < vertices.length; i++) {
	                color[vertices[i]] = 'white';
	            }
	            return color;
	        }
	        //添加节点
	        Graph.prototype.addVertex = function(v) {
	            this.vertices.push(v);
	            this.adjList.set(v, []); //给节点v设置一个空数组作为值。
	        };
	        //添加边
	        Graph.prototype.addEdge = function(v, w) {
	            this.adjList.get(v).push(w); //先获取v节点对应的数组,然后把w推入数组中,这样就表示一条v到w的线
	            this.adjList.get(w).push(v);
	        };
	        //广度优先d
	        //搜索算法 v表示初始节点,callback表示回调。
	        Graph.prototype.bfs = function(v, callback) {
	            var color = initializeColor(this.vertices);
	            var queue = new Queue(); //存储待访问和待探索的节点
	            queue.enqueue(v);
	            while (!queue.isEmpty()) {
	                var u = queue.dequeue();
	                //获取u的相邻节点列表
	                var neighbors = this.adjList.get(u);
	                color[u] = 'grey';
	                for (var i = 0; i < neighbors.length; i++) {
	                    var w = neighbors[i];
	                    //如果从没有标记过,则标记为grey,加入队列
	                    if (color[w] === 'white') {
	                        color[w] = 'grey';
	                        queue.enqueue(w);
	                    }
	                }
	                //所有相邻节点都被标记了,所以改为黑色
	                color[u] = 'black';
	                //如果对于标记过得节点有操作,通过callback操作
	                if (callback) {
	                    callback(u);
	                }
	            }
	        };
	        //用BFS实现最短路径
	        Graph.prototype.BFS = function(v, callback) {
	            var color = initializeColor(this.vertices);
	            var queue = new Queue(); //存储待访问和待探索的节点
	            var d = [];
	            var pred = [];
	            queue.enqueue(v);
	            for (var i = 0; i < this.vertices.length; i++) {
	                d[this.vertices[i]] = 0;
	                pred[this.vertices[i]] = null;
	            }
	            while (!queue.isEmpty()) {
	                var u = queue.dequeue();
	                //获取u的相邻节点列表
	                var neighbors = this.adjList.get(u);
	                color[u] = 'grey';
	                for (var i = 0; i < neighbors.length; i++) {
	                    var w = neighbors[i];
	                    //如果从没有标记过,则标记为grey,加入队列
	                    if (color[w] === 'white') {
	                        color[w] = 'grey';
	                        d[w] = d[u] + 1;
	                        pred[w] = u;
	                        queue.enqueue(w);
	                    }
	                }
	                //所有相邻节点都被标记了,所以改为黑色
	                color[u] = 'black';
	                //如果对于标记过得节点有操作,通过callback操作
	                if (callback) {
	                    callback(u);
	                }
	            }
	            return {
	                distances: d,
	                predecessors: pred
	            }
	        };
	        //深度优先基本实现
	        Graph.prototype.dfs = function(callback) {
	            var self = this;
	            function dfsVisit(u, color, callback) {
	                color[u] = 'grey';
	                if (callback) {
	                    callback(u);
	                }
	                var neighbors = self.adjList.get(u);
	                for (var i = 0; i < neighbors.length; i++) {
	                    var w = neighbors[i];
	                    if (color[w] === 'white') {
	                        dfsVisit(w, color, callback);
	                    }
	                }
	                color[u] = 'black';
	            }
	            var color = initializeColor(this.vertices);
	            for (var i = 0; i < this.vertices.length; i++) {
	                if (color[this.vertices[i]] === 'white') {
	                    dfsVisit(this.vertices[i], color, callback);
	                }
	            }
	        };
	        //DFS可以实现输出被访问顶点的顺序
	        Graph.prototype.DFS = function(){
	            var time = 0;
	            var self = this;
	            function DFSVisit(u,color,d,f,p){
	                //console.log('discovered ' + u);
	                color[u] = 'grey';
	                d[u] = ++time;
	                var neighbors = self.adjList.get(u);
	                for(var i = 0; i < neighbors.length; i++){
	                    var w = neighbors[i];
	                    if (color[w] === 'white') {
	                        p[w] = u;
	                        DFSVisit(w,color,d,f,p);
	                    }
	                }
	                color[u] = 'black';
	                f[u] = ++time;
	                //console.log('explored ' + u);
	            }
	            var color = initializeColor(this.vertices);
	            var d = [];
	            var f = [];
	            var p = [];
	            var time = 0;
	            for(var i = 0; i < this.vertices.length; i++){
	                f[this.vertices[i]] = 0;
	                d[this.vertices[i]] = 0;
	                p[this.vertices[i]] = null;
	            }
	            for(var i = 0; i< this.vertices.length; i++){
	                if (color[this.vertices[i]] === 'white') {
	                    DFSVisit(this.vertices[i], color, d, f, p);
	                }
	            }
	            return {
	                discovery:d,
	                finished:f,
	                predecessors:p
	            }
	        };
	        Graph.prototype.toString = function() {
	            var s = '';
	            for (var i = 0; i < this.vertices.length; i++) {
	                s += this.vertices[i] + ' -> ';
	                var neighbors = this.adjList.get(this.vertices[i]);
	                for (var j = 0; j < neighbors.length; j++) {
	                    s += neighbors[j] + ' ';
	                }
	                s += ',';
	            }
	            return s;
	        };
	    }
	}

2.5 图的基本使用

	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 
	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

	//广度优先搜索的使用:最短路径算法
	var shortestPathA = graph.BFS(myVertices[0]);
	//console.log(shortestPathA);
	//上行输出结果:
	// { distances: [ A: 0, B: 1, C: 1, D: 1, E: 2, F: 2, G: 2, H: 2, I: 3 ],
	//   predecessors: 
	//    [ A: null,
	//      B: 'A',
	//      C: 'A',
	//      D: 'A',
	//      E: 'B',
	//      F: 'B',
	//      G: 'C',
	//      H: 'D',
	//      I: 'E' ]

	//深入优先搜索算法
	//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

	//深度优先搜索查找访问过程:
	graph = new Graph();
	myVertices = ['A','B','C','D','E','F'];
	for (i=0; i<myVertices.length; i++){
	    graph.addVertex(myVertices[i]);
	}
	graph.addEdge('A', 'C');
	graph.addEdge('A', 'D');
	graph.addEdge('B', 'D');
	graph.addEdge('B', 'E');
	graph.addEdge('C', 'F');
	graph.addEdge('F', 'E');
	var result = graph.DFS();
	// 上面运行输出:
	// discovered A
	// discovered C
	// discovered F
	// discovered E
	// discovered B
	// discovered D
	// explored D
	// explored B
	// explored E
	// explored F
	// explored C
	// explored A
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4353.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/suanfa/4353.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定