C++(数据结构与算法):72---无权图与有权图的描述(邻接矩阵、邻接链表、邻接数组)
分类:
文章
•
2024-01-06 14:46:22
一、无权图描述之邻接矩阵
邻接矩阵的定义
- 一个n顶点图G=(V,E)的邻接矩阵是一个n*n的矩阵(假设是A),其中每个元素都是0或1.假设V={1,2,3,...,n}
-
如果G是一个无向图,则元素的定义如下:
将邻接矩阵映射到数组
-
方法一:利用A(i,j)=1,当且仅当a[i][j]是true,1<=i<=n,1<=j<=n,因此n*n的邻接矩阵需要映射到一个(n+1)*(n+1)的布尔型数组中,此时需要字节
-
方法二:利用A(i,j)=1,当且仅当a[i-1]a[j-1]是true,1<=i<=n,1<=j<=n,因此n*n的邻接矩阵需要映射到一个n*n的布尔型数组中,此时需要字节,比方法一减少了2n+1字节
-
方法三:
- 根据定义我们知道,所有对侥幸元素都是0而不需要存储,因此可以对角线元素去掉,进一步减少n字节的存储空间,压缩之后可以得到一个上(或下)三角矩阵
-
缺陷:压缩之后虽然减少了存储空间,但是代价不小,因为定点的外部索引和在图中的内部描述不匹配。这样一来,不仅代码容易出错,而且访问边的时间也会增加。因此我们不建议这种做法
- 例如把上面的三个邻接矩阵去掉对角线元素之后,如下图所示(阴影部分是原邻接矩阵的下三角部分)
复杂度分析
- 使用邻接矩阵时,要确定邻接至或邻接于一个给定节点的集合需要用时Θ(n)。然而,增加或删除一条边仅需用时Θ(1)
二、无权图描述之邻接链表
- 一个顶点i的邻接表是一个线性表,它包含所有邻接于顶点i的顶点。在一个图的邻接表描述中,图的每一个顶点都有一个邻接表。当邻接表用链表表示时,就是邻接链表
邻接链表的描述
- 我们假设使用数组aList来描述所有的邻接表
- aList[i].firstNode指向顶点i的邻接表的第一个顶点
- 如果x指向链表aList[i]的一个顶点,那么(i,x->element)是图的一条边,其中element的数据类型是整型int
- 下图是一些无向图与有向图以及它们对应的邻接链表的描述:
复杂度分析
- 一个指针和一个整数各需要4字节的存储空间,因此用邻接链表描述一个n顶点的图需要8(n+1)字节存储n+1个firstNode指针和aList链表的listSize域,需要4*2*m字节存储m个链表节点,每个链表节点的两个域next和element各需要4字节,其中对于无向图m=2e,对于无向图m=3(其中e是边数)
- 当e远远小于时,邻接链表比邻接矩阵需要更少的空间。例如,一个e=n的有向图,用邻接链表描述需要16n+8字节,用压缩的邻接链表矩阵描述需要字节。因此,当e=n>=17时,邻接链表描述所需空间更少
- 在邻接链描述中,确定邻接于顶点i的顶点需要用时Θ(邻接于顶点i的顶点数)。插入或删除一条边(i,j)的用时,对无向图是,对无向图是
三、无权图描述之邻接数组
- 一个顶点i的邻接表是一个线性表,它包含所有邻接于顶点i的顶点。在一个图的邻接表描述中,图的每一个顶点都有一个邻接表。当邻接表用数组表示时,就是邻接数组
邻接数组的描述
-
方法一:用下图所示的邻接数组来表示,其中每一个索引处都是一个数组
-
方法二:使用二维数组来表示,假设是aList[][],其中aList[i]容量等于顶点i的邻接表长度
复杂度分析
- 邻接数组比邻接链表少用4m字节,因为不需要next指针域(这样的指针域有m个)
- 而大部分的图操作,无论是邻接链表还是用邻接数组,其渐近时间复杂性是相同的。但是根据以往的实验,我们认为,对大部分的图操作,邻接数组的用时要少于邻接链表
- 注意:对邻接矩阵和邻接表所做的空间需求分析是渐近分析,而实际的实现所需空间可能要多一些,因为实际的代码可能要存储诸如顶点和边的个数,这些量在我们的分析中没有考虑
四、有权图的描述
邻接矩阵描述
- 用成本邻接矩阵C描述加权图,如果C(i,j)的权,那么它的使用方法和邻接矩阵的使用方法一样
- 在这种描述中,因为矩阵中的值代表的是权值,因此不能单单使用0来描述不存在的边,在实际编程中可以给不存在的边指定一个很大的值(这个很大的值可以用变量noEdge来命名)
- 下图是三个加权图以及邻接矩阵的描述:
邻接链表描述
- 因为有了权值,所以链表的元素有两个域vertex和weight,分别表示节点的编号与节点的权值
- 下图是对于一个无向加权图的描述
邻接数组描述
- 原理与邻接链表相似,只是元素使用数对(vertex,weight)表示。不再讲述