JavaScript数据结构之图
图的相关术语
图是网络结构的抽象模型。图是一组由边连接的节点(或顶点)。学习图是重要的,因为任何二元关系都可以用图来表示。
任何社交网络,例如Facebook、Twitter和Google plus,都可以用图来表示。
解一下图的一些术语:
由一条边连接在一起的顶点称为相邻顶点。比如,A和B是相邻的,A和D是相邻的,A和C是相邻的,A和E不是相邻的。
一个顶点的度是其相邻顶点的数量。比如,A和其他三个顶点相连接,因此,A的度为3;E和其他两个顶点相连,因此,E的度为2。
路径是顶点v 1 , v 2 ,…,v k 的一个连续序列,其中v i 和v i+1 是相邻的。以上一示意图中的图为例,其中包含路径A B E I和A C D G。
简单路径要求不包含重复的顶点。举个例子,A D G是一条简单路径。除去最后一个顶点(因为它和第一个顶点是同一个顶点),环也是一个简单路径,比如A D C A(最后一个顶点重新回到A)。
如果图中不存在环,则称该图是无环的。如果图中每两个顶点间都存在路径,则该图是连通的。
有向图和无向图
图可以是无向的(边没有方向)或是有向的(有向图)。如下图所示,有向图的边有一个方向:
如果图中每两个顶点间在双向上都存在路径,则该图是强连通的。例如,C和D是强连通的,而A和B不是强连通的。
图还可以是未加权的(目前为止我们看到的图都是未加权的)或是加权的。如下图所示,加权图的边被赋予了权值:
可以使用图来解决计算机科学世界中的很多问题,比如搜索图中的一个特定顶点或搜索一条特定边,寻找图中的一条路径(从一个顶点到另一个顶点),寻找两个顶点之间的最短路径,以及环检测。
图的表示
从数据结构的角度来说,有多种方式来表示图。在所有的表示法中,不存在绝对正确的方式。图的正确表示法取决于待解决的问题和图的类型。
邻接矩阵
图最常见的实现是邻接矩阵。每个节点都和一个整数相关联,该整数将作为数组的索引。用一个二维数组来表示顶点之间的连接。如果索引为i的节点和索引为j的节点相邻,则array[i][j]=== 1,否则array[i][j] === 0,如下图所示:
不是强连通的图(稀疏图)如果用邻接矩阵来表示,则矩阵中将会有很多0,这意味着浪费了计算机存储空间来表示根本不存在的边。
例如,找给定顶点的相邻顶点,即使该顶点只有一个相邻顶点,我们也不得不迭代一整行。邻接矩阵表示法不够好的另一个理由是,图中顶点的数量可能会改变,而2维数组不太灵活。
邻接表
邻接表的动态数据结构也可以用来表示图。邻接表由图中每个顶点的相邻顶
点列表所组成。存在好几种方式来表示这种数据结构。可以用列表(数组)、链表,甚至是散列表或是字典来表示相邻顶点列表。下面的示意图展示了邻接表数据结构。
尽管邻接表可能对大多数问题来说都是更好的选择,但以上两种表示法都很有用,且它们有着不同的性质(例如,要找出顶点v和w是否相邻,使用邻接矩阵会比较快)。
关联矩阵
用关联矩阵来表示图。在关联矩阵中,矩阵的行表示顶点,列表示边。如下图所
示,使用二维数组来表示两者之间的连通性,如果顶点v是边e的入射点,则array[v][e] === 1;否则,array[v][e] === 0。
关联矩阵通常用于边的数量比顶点多的情况下,以节省空间和内存。
创建图类
声明类的骨架:
function Graph() {
var vertices = []; //{1}
var adjList = new Dictionary(); //{2}
}
使用一个数组来存储图中所有顶点的名字(行 {1} ),以及一个字典来存储邻接表(行 {2} )。字典将会使用顶点的名字作为键,邻接顶点列表作为值。 vertices
数组和 adjList 字典两者都是 Graph 类的私有属性。
实现两个方法:一个用来向图中添加一个新的顶点(因为图实例化后是空的),另外一个方法用来添加顶点之间的边。
实现 addVertex
方法:
this.addVertex = function(v){
vertices.push(v); //{3}
adjList.set(v, []); //{4}
};
这个方法接受顶点 v 作为参数。我们将该顶点添加到顶点列表中(行 {3} ),并且在邻接表中,设置顶点 v 作为键对应的字典值为一个空数组(行 {4} )。
实现 addEdge
方法:
this.addEdge = function(v, w){
adjList.get(v).push(w); //{5}
adjList.get(w).push(v); //{6}
};
这个方法接受两个顶点作为参数。首先,通过将 w 加入到 v 的邻接表中,我们添加了一条自顶点 v 到顶点 w 的边。如果想实现一个有向图,则行 {5} 就足够了。
如果是无向图,需要添加一条自 w 向 v 的边(行 {6} )。
注意:只是往数组里新增元素,因为数组已经在行 {4} 被初始化了。
测试这段代码:
var graph = new Graph();
var myVertices = ['A','B','C','D','E','F','G','H','I']; //{7}
for (var i=0; i<myVertices.length; i++){
//{8}
graph.addVertex(myVertices[i]);
}
graph.addEdge('A', 'B'); //{9}
graph.addEdge('A', 'C');
graph.addEdge('A', 'D')