存储及基本操作
图的存储必须要完整、准确的反映顶点集和边集的信息。主要的存储方式有两种: 邻接矩阵 和 邻接表。前者属于图的顺序存储结构,后者属于图的链式存储结构。
邻接矩阵
设图的顶点数量为 ,邻接矩阵(adjacency matrix)使用一个 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 1 或 0 表示两个顶点之间是否存在边。
如下图所示,设邻接矩阵为 、顶点列表为 ,那么矩阵元素 表示顶点 到顶点 之间存在边,反之 表示两顶点之间无边。

邻接矩阵具有以下特性:
顶点不能与自身相连,因此邻接矩阵主对角线元素没有意义。
无向图的邻接矩一定是对称矩阵并且唯一。
邻接矩阵的第i行(或第i列) 非零元素(或非∞元素)的个数正好是第 i 个顶点的度。
将邻接矩阵的元素从 1 和 0 替换为权重,则可表示有权图。
使用邻接矩阵表示图时,我们可以直接访问矩阵元素以获取边,因此增删查改操作的效率很高,时间复杂度均为 。然而,矩阵的空间复杂度为 ,内存占用较多。
邻接矩阵的基本操作:
添加或删除边:直接在邻接矩阵中修改指定的边即可,使用 时间。而由于是无向图,因此需要同时更新两个方向的边。
添加顶点:在邻接矩阵的尾部添加一行一列,并全部填 0 即可,使用 时间。
删除顶点:在邻接矩阵中删除一行一列。当删除首行首列时达到最差情况,需要将 个元素“向左上移动”,从而使用 时间。
初始化:传入 个顶点,初始化长度为 的顶点列表
vertices,使用 时间;初始化 大小的邻接矩阵adjMat,使用 时间。





邻接表
邻接表(adjacency list)使用 个链表来表示图,链表节点表示顶点。第 个链表对应顶点 ,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)。下图展示了一个使用邻接表存储的图的示例。

邻接表仅存储实际存在的边,而边的总数通常远小于 ,因此它更加节省空间。然而,在邻接表中需要通过遍历链表来查找边,因此其时间效率不如邻接矩阵。
观察上图 ,邻接表结构与哈希表中的“链式地址”非常相似,因此我们也可以采用类似的方法来优化效率。比如当链表较长时,可以将链表转化为 AVL 树或红黑树,从而将时间效率从 优化至 ;还可以把链表转换为哈希表,从而将时间复杂度降至 。
邻接表的基本操作:
添加边:在顶点对应链表的末尾添加边即可,使用 时间。因为是无向图,所以需要同时添加两个方向的边。
删除边:在顶点对应链表中查找并删除指定边,使用 时间。在无向图中,需要同时删除两个方向的边。
添加顶点:在邻接表中添加一个链表,并将新增顶点作为链表头节点,使用 时间。
删除顶点:需遍历整个邻接表,删除包含指定顶点的所有边,使用 时间。
初始化:在邻接表中创建 个顶点和 条边,使用 时间。





以下是邻接表的代码实现。对比上图 ,实际代码有以下不同。
为了方便添加与删除顶点,以及简化代码,我们使用列表(动态数组)来代替链表。
使用哈希表来存储邻接表,
key为顶点实例,value为该顶点的邻接顶点列表(链表)。另外,我们在邻接表中使用
Vertex类来表示顶点,这样做的原因是:如果与邻接矩阵一样,用列表索引来区分不同顶点,那么假设要删除索引为 的顶点,则需遍历整个邻接表,将所有大于 的索引全部减 1 ,效率很低。而如果每个顶点都是唯一的Vertex实例,删除某一顶点之后就无须改动其他顶点了。
效率对比

图的常见应用
社交网络
用户
好友关系
潜在好友推荐
地铁线路
站点
站点间的连通性
最短路线推荐
太阳系
星体
星体间的万有引力作用
行星轨道计算
Citing
Last updated
Was this helpful?