基本概念

顶点(vertex)边(edge)

图中的元素我们就叫作顶点(vertex)。从下图可以看出,图中的一个顶点可以与任意其他顶点建立连接关系。我们把这种建立的关系叫作边(edge)

类比:类似于微信中的好友,如果两个人是好友,那么两个顶点有一条边连接
image.png

顶点的度(degree)

就是跟顶点相连接的边的条数。

有向图、无向图

把边有方向的图叫作 “有向图”。把边没有方向的图就叫作 “无向图”。

类比:微博中如果用户 A 关注了用户 B,我们就在图中画一条从 A 到 B 的带箭头的边,来表示边的方向。如果用户 A 和用户 B 互相关注了,那我们就画一条从 A 指向 B 的边,再画一条从 B 指向 A 的边。

图(Graph) - 图2

入度、出度

有向图中,我们把度分为入度(In-degree)和出度(Out-degree)。

顶点的入度,表示有多少条边指向这个顶点;顶点的出度,表示有多少条边是以这个顶点为起点指向其他顶点。对应到微博的例子,入度就表示有多少粉丝,出度就表示关注了多少人。

带权图(weighted graph)

类比:QQ中的亲密度

在带权图中,每条边都有一个权重(weight)

图(Graph) - 图3

图的存储方式


邻接矩阵(Adjacency Matrix)

图最直观的一种存储方法就是,邻接矩阵(Adjacency Matrix)

邻接矩阵的底层依赖一个二维数组。

  • 对于无向图来说,如果顶点 i 与顶点 j 之间有边,我们就将 A [i][j] 和 A [j][i] 标记为 1;(该矩阵是对称阵
  • 对于有向图来说,如果顶点 i 到顶点 j 之间,有一条箭头从顶点 i 指向顶点 j 的边,那我们就将 A [i][j] 标记为 1。同理,如果有一条箭头从顶点 j 指向顶点 i 的边,我们就将 A [j][i] 标记为 1。
  • 对于带权图,数组中就存储相应的权重。

图(Graph) - 图4

用邻接矩阵来表示一个图,虽然简单、直观,但是比较浪费存储空间。为什么这么说呢?

对于无向图来说,如果 A [i][j] 等于 1,那 A [j][i] 也肯定等于 1。实际上,我们只需要存储一个就可以了。也就是说,无向图的二维数组中,如果我们将其用对角线划分为上下两部分,那我们只需要利用上面或者下面这样一半的空间就足够了,另外一半白白浪费掉了。

还有,如果我们存储的是稀疏图(Sparse Matrix),也就是说,顶点很多,但每个顶点的边并不多,那邻接矩阵的存储方法就更加浪费空间了。比如微信有好几亿的用户,对应到图上就是好几亿的顶点。但是每个用户的好友并不会很多,一般也就三五百个而已。如果我们用邻接矩阵来存储,那绝大部分的存储空间都被浪费了。

但这也并不是说,邻接矩阵的存储方法就完全没有优点。首先,邻接矩阵的存储方式简单、直接,因为基于数组,所以在获取两个顶点的关系时,就非常高效。其次,用邻接矩阵存储图的另外一个好处是方便计算。这是因为,用邻接矩阵的方式存储图,可以将很多图的运算转换成矩阵之间的运算。比如求解最短路径问题时会提到一个 Floyd-Warshall 算法,就是利用矩阵循环相乘若干次得到结果。

邻接表存储方法

邻接表(Adjacency List)。

图(Graph) - 图5

总结

邻接矩阵存储方法的缺点是比较浪费空间,但是优点是查询效率高,而且方便矩阵运算。邻接表存储方法中每个顶点都对应一个链表,存储与其相连接的其他顶点。尽管邻接表的存储方式比较节省存储空间,但链表不方便查找,所以查询效率没有邻接矩阵存储方式高。针对这个问题,邻接表还有改进升级版,即将链表换成更加高效的动态数据结构,比如平衡二叉查找树、跳表、散列表等。