了解数据结构图 (graph)

半城伤御伤魂 提交于 2019-12-05 12:34:38

下面是本文对图操作的小结:

  邻接表 邻接矩阵
空间复杂度 O(|V|+ |E|) O(|V|²)
添加顶点 O(1) O(|V|²)
移除顶点 O(|V| + |E|) O(|V|)²
添加 O(1) O(1)
移除边 (基于 Array 实现) O(|E|) O(1)
移除边 (基于 HashSet 实现) O(1) O(1
获取相邻的顶点 O(|E|) O(|V|)
判断是否相邻 (基于 Array 实现) O(|E|) O(1)
判断是否相邻 (基于 HashSet 实现) O(1) O(1)

图的基础

图是一种(包含若干个节点),每个节点可以连接 0 个或多个元素

两个节点相连的部分称为边(edge)。节点也被称作顶点(vertice)

一个顶点的 度(degree)是指与该顶点相连的边的条数。比如上图中,紫色顶点的度是 3,蓝色顶点的度是 1。

如果所有的边都是双向(译者注:或者理解为没有方向)的,那我们就有了一个无向图(undirected graph)。反之如果边是有向的,我们得到的就是有向图(directed graph)。你可以将有向图和无向图想象为单行道或双行道组成的交通网。

 

此外,无环无向图也被称为树(tree)

在图中,从一个顶点出发,并非所有顶点都是可到达的。可能会存在孤立的顶点或者是相分离的子图。如果一个图所有顶点都至少有一条边(译者注:原文表述有点奇怪,个人认为不应该是至少有一条边,而是从任一节点出发,沿着各条边可以访问图中任意节点),这样的图被称为连通图(connected graph)。而当一个图中两两不同的顶点之间都恰有一条边相连,这样的图就是完全图(complete graph)

对于完全图而言,每个顶点都有 图的顶点数 - 1 条边。在上面完全图的例子中,一共有7个顶点,因此每个顶点有6条边。

图的应用

当图的每条边都被分配了权重时,我们就有了一个加权图(weighted graph)。如果边的权重被忽略,那么可以将(每条边的)权重都视为 1(译者注:权重都是一样,也就是无权重)。

 

加权图应用的场景很多,根据待解决问题主体的不同,有不同的展现。一起来看一些具体的场景吧:

  • 航空线路图 (如上图所示)

    • 顶点 = 机场
    • 边 = 两个机场间的飞行线路
    • 权重 = 两个机场间的距离
  • GPS 导航

    • 顶点 = 交叉路口
    • 边 = 道路
    • 权重 = 从一个路口到另一个路口所花的时间
  • 网络

    • 顶点 = 服务器
    • 边 = 数据链路
    • 权重 = 连接速度

一般而言, 图在现实世界中的应用有:

  • 电子电路
  • 航空控制
  • 行车导航
  • 电信设施: 基站建设规划
  • 社交网络: Facebook 利用图来推荐(你可能认识的)朋友
  • 推荐系统: Amazon/Netflix 利用图来推荐产品与电影
  • 利用图来规划物流线路

 

我们学习了图的基础以及它的一些应用场景。接下来一起学习怎么使用代码来表示图。

图的表示

图的表示有两种主要方式:

  1. 邻接表
  2. 邻接矩阵

让我们以有向图为例子,阐述这两种表示方式:

这是一个拥有四个顶点的图。当一个顶点有一条边指向它自身时(译者注:即闭合的路径),称之为自环(self-loop)

邻接矩阵

邻接矩阵使用二维数组(N x N)来表示图。如若不同顶点存在连接的边,就赋值两顶点交汇处为1(也可以是这条边的权重),反之赋值为 0 或者 -。

我们可以通过建立以下的邻接矩阵,来表示上面的图:

  a b c d e
a 1 1 - - -
b - - 1 - -
c - - - 1 -
d - 1 1 - -

如你所见,矩阵水平与垂直两个方向都列出了所有的顶点。如果图中只有很少顶点互相连接,那么这个图就是稀疏图(sparse graph)。如果图相连的顶点很多(接近两两顶点都相连)的话,我们称这种图为稠密图(dense graph)。而如果图的每个顶点都直接连接到除此之外的所有顶点,那就是一个完全图(complete graph)

注意,你必须意识到对于无向图而言,邻接矩阵始终是对角线对称的。然而,对于有向图而言,并非总是如此(反例如上面的有向图)。

那查询两个顶点是否相邻的时间复杂度是什么呢?

在邻接矩阵中,查询两个顶点是否相邻的时间复杂度是 O(1)。

那空间复杂度呢?

利用邻接矩阵存储一个图,空间复杂度是 O(n²),n 为顶点的数量,因此也可以表示为 O(|V|²)

添加一个顶点的时间复杂度呢?

邻接矩阵根据顶点的数量存储为 V x V 的矩阵。因此每增加一个顶点,矩阵需要重建为 V+1 x V+1 的新矩阵。

(因此,)在邻接矩阵中添加一个顶点的时间复杂度是 O(|V|²)

如何获取相邻的顶点?

由于邻接矩阵是一个 V x V 的矩阵,为了获取所有相邻的顶点,我们必须去到该顶点所在的行中,查询它与其他顶点是否有边。

以上面的邻接矩阵为例,假设我们想知道与顶点 b 相邻的顶点有哪些,就需要到达记录 b 与其他节点关系的那一行中进行查询。

  a b c d e
b - - 1 - -

访问它与其他所有顶点的关系,因此:

在邻接矩阵中,查询相邻顶点的时间复杂度是 O(|V|)

想象一下,如果你需要将 FaceBook 中人们的关系网表示为一个图。你必须建立一个 20亿 x 20亿 的邻接矩阵,而该矩阵中很多位置都是空的。没有任何人可能认识其他所有人,最多也就认识几千个人。

通常,我们使用邻接矩阵处理稀疏图时,会浪费很多空间。这就是大多时候使用邻接表而不是邻接矩阵去表示一个图的原因(译者注:邻接矩阵也有优势的,尤其是表示有向稠密图时,比邻接表要方便得多)。

邻接表

表示一个图,最常用的方式是邻接表。每个顶点都有一个记录着与它所相邻顶点的表。

可以使用一个数组或者 HashMap 来建立一个邻接表,它存储这所有的顶点。每个顶点都有一个列表(可以是数组、链表、集合等数据结构),存放着与其相邻的顶点。

例如上面的图,对于顶点 a,与之相邻的有顶点 b,同时也是自环;而顶点 b 则有指向顶点 c 的边,如此类推:

a -> { a b }
b -> { c }
c -> { d }
d -> { b c }

和想象中的一样,如果想知道一个顶点是否连接着其他顶点,就必须遍历(顶点的)整个列表。

在邻接表中查询两个顶点是否相连的时间复杂度是 O(n),n 为顶点的数量,因此也可以表示为 O(|V|)

那空间复杂度呢?

利用邻接表存储一个图的空间复杂度是 O(n),n 为顶点数量与边数量之和,因此也可以表示为 O(|V| + |E|)

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!