JavaScript 数据结构与算法——图

209 阅读3分钟

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, 类当中声明了三个公共属性verticesadjListisDirected,分别表示图的顶点列表,图的邻接表,是否是有向图。这里邻接表我们使用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);
    }
  }