JavaScript 数据结构与算法——图
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 14 天,点击查看活动详情。
介绍
图是一种非线性结构,它在生活当中的应用非常广泛,比如高德地图、道路、通信、铁路等等。在数学上有一门专门的课叫做图论。
图的定义
在数学上,图的定义为 G = (V,E);V 是顶点,E 是边,顶点通过边来互相连接。这样就构成了一个图,图也分为有向图和无向图。无向图顶点之间没有方向,只有一条边,也可以说这条边是双向的,两个顶点都可以通过这条边到另外一个节点。有向图顶点之间有方向,只有一条边,这条边是单向的,只能从一个顶点到另外一个顶点,无法反向。
由一条边连接在一起的顶点叫做相邻顶点,这个边叫做边。一个顶点其相邻顶点的数量叫做度。路径是顶点 v1,v2,v3,vk 的一个连续的序列,其中 vi 和 vi+1 是相邻的。 简单路径要求不包含重复的顶点,环也是一个简单路径,如果图中不存在环,则称该图是无环的。如果图中每两个顶点间都存在路径,则该图是连通的。
图的表示
从数据结构的角度来说,我们有很多种方式来表示图。在所有的表示方法当中,不存在绝对正确的方式。图的正确表示法取决于解决问题和图的类型。
邻接矩阵
图最常见的实现方式就是邻接矩阵。每个节点都和一个整数相关联,该整数将作为数组的索引。我们用一个二维数组来表示顶点之间的链接。如果索引为 i 的节点和索引为 j 的节点相邻,则矩阵 array[i][j] = 1,否则为 0。
但是,如果不是强连通的图我,1 使用邻接矩阵来表达,这样矩阵当中会有很多 0,这样意味着我们浪费了很多计算机的内存空间来存储 0,这样对计算机的性能影响很大。并且,使用二维数组本身并不是很灵活,要是有多重遍历来访问二维数组,这样时间复杂度会很高。
邻接表
我们还可以使用一种叫做邻接表的动态数据存储结构来表示图。邻接表由图中每个顶点的相邻顶点的列表组成。存在好几种表示方法来表示这种数据结构。
图的实现
创建 Graph 类
class Graph<T> {
// 节点列表
public vertices: T[];
// 邻接表
public adjList: Map<T, T[]>;
// 是否是有向图
public isDirected: boolean;
constructor(isDirected = false) {
this.isDirected = isDirected;
//初始化邻接表
this.adjList = new Map<T, T[]>();
// 初始化节点列表
this.vertices = [];
}
}
上面我们创建了类Graph, 类当中声明了三个公共属性vertices、adjList、isDirected,分别表示图的顶点列表,图的邻接表,是否是有向图。这里邻接表我们使用Map来实现存储。在构造函数当中,我们对这三个属性进行初始化。
添加顶点
/**
* 向图当中添加节点
* @param v 添加的节点
*/
addVertex(v: T) {
//先判断当前图当中是否包含节点
if (!this.vertices.includes(v)) {
this.vertices.push(v);
this.adjList.set(v, []);
}
}
添加边
/**
*
* @param v 顶点v
* @param w 顶点w
*/
addEdge(v: T, w: T) {
//如果图当中不存在v节点
if (!this.vertices.includes(v)) {
//将v节点添加到图当中
this.addVertex(v);
}
//如果图当中不存在W节点
if (!this.vertices.includes(w)) {
//将w节点添加到图当中
this.addVertex(w);
}
//如果都存在,那么对他们进行关系添加
this.adjList.get(v).push(w);
//判断是否为有向图,如果为无向图则添加v到w的关系
if (!this.isDirected) {
this.adjList.get(w).push(v);
}
}