【2019/11/3】图的表示方法(邻接矩阵、邻接表、链式前向星)

一、邻接矩阵

构造一个二维矩阵M = {mi, j}。
如果图是无权图,那么仅需要表示边的邻接关系。这时每个元素mi, j的取值只能是0或1:

如果图是有权图,那么还需要表示边权。权值有时候会取0,所以表示方法改成:
【2019/11/3】图的表示方法(邻接矩阵、邻接表、链式前向星)_第1张图片
当用邻接矩阵表示无向图时,需要同时添加有向边,得到的邻接矩阵关于主对角线对称。

二、邻接表

每个节点至少包含当前节点的编号和下一个节点的指针。每个节点的指针都指向邻接节点。邻接节点的存储是无序的,要查找一个节点的某个邻接节点,就要从该节点开始一直沿下一个节点的指针访问到目标节点或下一个节点的指针为空为止(此时代表未找到该邻接节点)。
也可以用向量数组来方便地保存邻接表。
对有权边,在下一个节点添加一个元素,用于保存权。对有的题目,可以只用邻接表保存邻接关系,而用额外的数组去存储每条边的权。

三、链式前向星

这是一种比较新的图的表示方法。前向星是一种特殊的边数组。我们把边数组{n}中的每一条边先后按照起点u、终点v排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,就完成了前向星的构造。
但是这个过程需要用到快排,使得时间复杂度为O(n log n) 。为了免去排序进而减小时间复杂度,我们采用链式前向星来记录一个图。
链式前向星至少要求一个head(有的资料写作first)数组和一个边数组e。边用结构体表示,至少包含终点、下一个储存位置两个元素。对有权图,边的结构体中要添加一个元素表示权。
添加边时,需要指定在边数组中写入的位置p(p>0,一般随着边的输入而递增),然后在该位置保存边:依次记录终点v、权(对有权边)w、下一个存储位置next是当前的head[u]指向的位置。然后,令head[u] = p。
可见,head[u]储存的是边数组的某个下标,指向起点为u的最后录入的一条边。遍历以某点u为起点的全部边时,循环变量i的初值就是head[u],就能定位到起点为u的最后录入的一条边在边数组中存储的位置。每次循环都令i = e[i].next,就依次被导航到同起点的边的下一个存储位置。遍历到的最后一条边是最开始存入的边,下一个储存位置为0。
边数组e的第0个位置是空着的,这样就无需像网上的众多资料那样要先初始化head数组为-1(循环条件被设置为i≠-1)。有时候,一个std::fill或者memset就能直接让代码超时(数组较大的情况下)。

四、图的表示方法的选择

设图G(V, E),其中V和E分别是顶点数组和边数组。易证:一个具有|V|个顶点的图在无重边的情况下最多含有|V||V-1|条边(有向图,如果是无向图,无重边的情况下最多的边数少一半)。对稠密图而言,采用邻接矩阵较多;对稀疏图(常取|E|≤|V|log|V|)而言,采用邻接表和链式前向星比较多。据说链式前向星会比vector实现的邻接表快一点。不过链式前向星的head数组和边数组e一般都是定长数组,也许这就是主要原因。此外,采用何种方法存图还需要根据特定的算法来决定。例如Dijkstra算法的优先队列优化(堆优化)的时间复杂度为O(|E| log |V|)。当采用邻接表(链式前向星)+堆优化求解稠密图的单源最短路时,复杂度达到O(|V|2 log |V|),比不优化的Dijkstra(O(|V|2))具有更高阶的复杂度。所以对于稠密图,应该采用邻接矩阵存图,并且不要进行堆优化。

你可能感兴趣的:(ACM-ICPC,基础课,#,数据结构)