Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 图 #30

Open
Checkson opened this issue Apr 29, 2019 · 0 comments
Open

JavaScript 图 #30

Checkson opened this issue Apr 29, 2019 · 0 comments

Comments

@Checkson
Copy link
Owner

Checkson commented Apr 29, 2019

背景

我们可以将复杂的网络看做一张图,数据在网络中以 点对点 形式传输。图通常用来表示和存储具有“多对多”关系的数据结构,是数据结构中非常重要的一种结构。图是众多数据中比较复杂的一种,本章,我们将介绍,如何用 JavaScript 表示图,如何实现重要的图算法。

定义

图由 的集合和 顶点 的集合组成。我们在中国地图上看到各个省道、国道、高速、铁路等连接两个地方的道路,在图里面都可以看做 ,各个交通枢纽,例如北京、上海、深圳、广州等城市,在图中可以看做是 顶点

边由顶点 对 (v1,v2) 定义,v1 和 v2 分别是图中的两个顶点。顶点也有权重,也称为成本。如果一个 图的顶点对是有序的,则可以称之为有向图。在对有向图中的顶点对排序后,便可以在两个顶点之间绘制一个箭头。有向图表明了顶点的流向。计算机程序中用来表明计算方向的 流程图就是一个有向图的例子。下面展示了一个有向图。

有向图

如果图是无序的,则称之为无序图,或无向图。下面展示了一个无序图。

无向图

图中的一系列顶点构成路径,路径中所有的顶点都由边连接。路径的长度用路径中第一个顶点到最后一个顶点之间边的数量表示。由指向自身的顶点组成的路径称为环,环的长度为 0。

圈是至少有一条边的路径,且路径的第一个顶点和最后一个顶点相同。无论是有向图还是无向图,只要是没有重复边或重复顶点的圈,就是一个简单圈。除了第一个和最后一个顶点以外,路径的其他顶点有重复的圈称为平凡圈。

如果两个顶点之间有路径,那么这两个顶点就是强连通的,反之亦然。如果有向图的所有的顶点都是强连通的,那么这个有向图也是强连通的。

顶点(Vertex)类实现

创建图类的第一步就是要创建一个 Vertex 类来保存顶点和边。这个类的作用与链表和二叉搜索树的 Node 类一样。Vertex 类有两个数据成员:一个用于标识顶点,另一个是表明这个顶点是否被访问过的布尔值。它们分别被命名为 label 和 hadVisited。这个类只需要一个函数,那就是为顶点的数据成员设定值的构造函数。Vertex 类的代码如下所示:

// 顶点构造函数
function Vertex (label) {
    this.label = label;
    this.hadVisited = false;
}

边表示

图的实际信息都保存在边上面,因为它们描述了图的结构。我们很容易像之前提到的那样用二叉树的方式去表示图,这是不对的。二叉树的表现形式相当固定,一个父节点只能有两个子节点,而图的结构却要灵活得多,一个顶点既可以有一条边,也可以有多条边与它相连。

我们将表示图的边的方法称为邻接表。这种方法将边存储为由顶点的相邻顶点列表构成的数组,并以此顶点作为索引。使用这种方案,当我们在程序中引用一个顶 点时,可以高效地访问与这个顶点相连的所有顶点的列表。比如,如果顶点 2 与顶点 1、 3相连,并且它存储在数组中索引为 2 的位置,那么,访问这个元素,我们可以访 问到索引为 2 的位置处由顶点 1、3组成的数组。请参考下图:

邻接表

图(Vertex)类实现

确定了如何在代码中表示图之后,构建一个表示图的类就很容易了。下面是第一个 Graph类的定义:

[完整代码地址]

// 图构造函数
function Graph (v) {
    this.vertices = v;
    this.edges = 0;
    this.adj = [];
    this.init();
}

// 数据初始化
Graph.prototype.init  = function () {
    for (var i = 0; i < this.vertices; i++) {
        this.adj[i] = [];
    }
}

// 添加边
Graph.prototype.addEdge = function (v1, v2) {
    this.adj[v1].push(v2);
    this.adj[v2].push(v1);
    this.edges++;
}

// 输出图信息
Graph.prototype.showGraph = function () {
    for (var i = 0; i < this.vertices; ++i) {
        var logMsg = i + ' -->';
        for (var j = 0; j < this.vertices; j++) {
            if (this.adj[i][j]) { 
                logMsg += ' ' + this.adj[i][j]
            }
        }
        console.log(logMsg);
    }
}

图(Graph)类测试

var graph = new Graph(6);

graph.addEdge(0, 4);
graph.addEdge(1, 2);
graph.addEdge(1, 4);
graph.addEdge(2, 3);
graph.addEdge(3, 4);
graph.addEdge(3, 5);

graph.showGraph();

运行结果

0 --> 4
1 --> 2 4
2 --> 1 3
3 --> 2 4 5
4 --> 0 1 3
5 --> 3

图的遍历

深度优先搜索

深度优先搜索包括从一条路径的起始顶点开始追溯,直到到达最后一个顶点,然后回溯, 继续追溯下一条路径,直到到达最后的顶点,如此往复,直到没有路径为止。这不是在搜索特定的路径,而是通过搜索来查看在图中有哪些路径可以选择。

// 深度优先搜索
Graph.prototype.dfs = function (v) {
    var st = [], top = -1;
    var visited = [];
    st[++top] = v;
    visited[v] = true;
    while (top !== -1) {
        var vertex = st[top--];
        console.log('Visited --> ' + vertex);
        for (var i = this.adj[vertex].length - 1; i >= 0; i--) {
            if (!visited[this.adj[vertex][i]]) {
                st[++top] = this.adj[vertex][i];
                visited[this.adj[vertex][i]] = true;
            }
        }
    }
}

广度优先搜索

广度优先搜索从第一个顶点开始,尝试访问尽可能靠近它的顶点。本质上,这种搜索在图上是逐层移动的,首先检查最靠近第一个顶点的层,再逐渐向下移动到离起始顶点最远的层.

Graph.prototype.bfs = function (v) {
    var rear = -1, front = -1;
    var queue = [], visited = [];
    queue[++rear] = v;
    visited[v] = true;
    while (rear != front) {
        var vertex = queue[++front];
        console.log('Visited --> ' + vertex);
        for (var i = 0; i < this.adj[vertex].length; i++) {
            var tempVertex = this.adj[vertex][i];
            if (!visited[tempVertex]) {
                queue[++rear] = tempVertex;
                visited[tempVertex] = true;
            }
        }
    }
}

测试代码

var graph = new Graph(6);

graph.addEdge(0, 4);
graph.addEdge(1, 2);
graph.addEdge(1, 4);
graph.addEdge(2, 3);
graph.addEdge(3, 4);
graph.addEdge(3, 5);

console.log('=============== dfs ===============');
graph.dfs(0);
console.log('=============== bfs ===============');
graph.bfs(0);

运行结果

=============== dfs ===============
Visited --> 0
Visited --> 4
Visited --> 1
Visited --> 2
Visited --> 3
Visited --> 5
=============== bfs ===============
Visited --> 0
Visited --> 4
Visited --> 1
Visited --> 3
Visited --> 2
Visited --> 5

查找最短路径

图最常见的操作之一就是寻找从一个顶点到另一个顶点的最短路径。考虑下面的例子:在国庆假期中,你将在两个星期的时间里游历 10 个大旅游城市,去参观各种名胜古迹。你希望通过最短路径算法,找出开车游历这 10 个城市行驶的最小里程数。另一个最短路径问题涉及创建一个计算机网络时的开销,其中包括两台电脑之间传递数据的时间,或者两台电脑建立和维护连接的成本。最短路径算法可以帮助确定构建此网络的最有效方法。

广度优先搜索对应的最短路径

在执行广度优先搜索时,会自动查找从一个顶点到另一个相连顶点的最短路径。例如,要查找从顶点 A 到顶点 D 的最短路径,我们首先会查找从 A 到 D 是否有任何一条单边路径, 接着查找两条边的路径,以此类推。这正是广度优先搜索的搜索过程,因此我们可以轻松地修改广度优先搜索算法,找出最短路径。

参考链接

最短路径—Dijkstra算法和Floyd算法
图的四种最短路径算法
拓扑排序

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant