目录
2.1 邻接矩阵(数组)表示法 (AMG — Adjacency Matrix Graph)
2.2 邻接表(链表)表示法 (ALG — Adjacency List Graph)
1. 图的逻辑结构
图的逻辑结构是多对多的关系。图没有顺序存储结构,但仍然可以用二维数组表示元素之间的关系
这就是邻接矩阵(数组)表示法
思考
如果使用链式存储结构,一个结点需要多少个指针域呢?
由于不好确定,图很难用多重链表的形式表现。一般使用邻接表,邻接多重表,十字链表来表示。
2. 图的表示方法
下面介绍邻接矩阵表示法和邻接表表示法。
2.1 邻接矩阵(数组)表示法 (AMG — Adjacency Matrix Graph)
无向图
- 存储顶点的叫顶点表,是一维数组Vexs[n]。(顶点英文为Vertex
- 存储边(顶点之间的关系)的是邻接矩阵,是一个二维数组arcs[n][n]。(弧英文为arc
- 邻接矩阵中,如果两个顶点间存在关系,则记作1,否则为0(只考虑直接连线,非路径)
无向图的邻接矩阵的特点:
1. 主对角线上都是0。因为自己对自己没有邻接关系。
2. 矩阵关于主对角线对称。因为某两点之间的关系是相互的。(有没有联想到对角矩阵(带状矩阵),如三对角,五对角与七对角及压缩存储哈哈)
3. 任一顶点的度为该行1的个数。
4. 完全图的邻接矩阵除了主对角线为0都是1。
例子:
有向图
有向图的邻接矩阵的特点:
1. 主对角线上都是0,并不一定对称。
2. 图中有多少条边,矩阵中就有多少个1,也可以逆推。
3. 某一结点 Vi 的度= 第 i 行数字和+第 i 列数字和;某一结点的出度=该横行的数字和;某一结点的入度=该纵列的数字和。因为横着看,每一行是出度边,描述该顶点能通往哪里。竖着看,每一列是入度边,描述哪些顶点能来到这里。
网
与之前不同的是,网中的每一条边都带有权。所以我们定义结点间关系不再用0和1表示,改为:
即两结点间如果有关系,则定义为权值而不是1;没关系,就定义为无穷大而不是0。
例:
创建无向网的邻接矩阵AMG的算法(伪代码)
定义部分
第一行定义是针对网的,因为没有关系时记为无穷,无法直接定义,所以用一个很大的数代替。
结构体内共四个元素,一个一维数组(顶点表),一个二维数组(邻接矩阵),两个int整形元素记录顶点数和边数。
初始化
思路(以无向网为例可以推广)
*如果要创建不是网,是图(即边没有权值),则初始化的时候不置无穷改为置0;构造邻接矩阵时不置为权改为置1;
**如果要创建的是有向网,则邻接矩阵中不加对称的那一条(更省事了)就可以了
算法部分
注意:G不是一个指针,定义时不是*AMGraph。所以用点引用成员,如G.vexs, G.arcs。(当然,在vs编译器里会自动改)
实现思路第1~3步(输入顶点数和总边数两个int,输入顶点的信息,初始化邻接矩阵为极大值):
实现思路第4步:构造邻接矩阵
注意:无向网具有对称性,记得要改对应的。
补充第4步:根据输入的顶点获取数组下标的算法Locate:
思路为用for i++ =0遍历数组,if相等则返回i
邻接矩阵表示法的优缺点
优点
注意:无向图直接看一横行就是顶点的度,有向图的话还要记得加上纵列(出度)。
缺点
- 不利于增加删除顶点:传统的数组结构经典的缺点,一旦确定大小就不能改变了。
- 空间复杂度:空间复杂度O(n²),跟边数无关(边多合适,但边少就浪费很多空间)
- 遍历时间复杂度:统计总边数的时间复杂度为O(n²),即必须遍历邻接矩阵内所有元素
2.2 邻接表(链表)表示法 (ALG — Adjacency List Graph)
- 存储顶点的仍然叫顶点表,是一维数组Vexs[n]。
- 存储边(顶点之间的关系)的是一个线性链表arcs,如果是有向图则是记录该点发出的弧。
- 表结点有2/3个域。第一个记邻接顶点在数组中的位置,如V1连接V2,V2是A[1]则为1,第二是指针域指向下一个结点,如果为网,有权值,则加第三个域info存放。
无向图的邻接表
特点:
- 邻接表不唯一 (联想,邻接矩阵是唯一的):以图中V1,表中vexs[0]为例,与V4,V2相连,则链表中1,3/3,1均可
- 无向图的邻接表的存储空间:O(n+2e),与O(n²)区别主要在边数上,利于存储稀疏图
- 邻接表中结点的度:就是链表中的结点数量
有向图的邻接表
根据用出度多还是入度多,选择使用邻接表还是逆邻接表。
例:
创建无向网的邻接表ALG的算法(伪代码)
定义部分
顶点表Vexs
记录头结点,是邻接表的表头
VerTexType取决于信息的类型,如果是简单的char,int就可以直接用。
ArcNode是指向边结点的,边结点在链表(邻接表)中。
其中,定义最后的AdjList,即说明部分可能不太好理解。AdjList可以看成是一个数组名:执行AdjList v,v不是一个常量,而是一个包含许多VNode的数组。
边结点表(邻接表)Arcs
三个组成部分:int记录指向的顶点(头结点),next指针,info记录sth like权
图的定义
共两项:一个头指针表,加两个int记录顶点数和弧数(都是附加信息视为一项)
PS:和邻接矩阵图AMG定义相比,之所以没有边的定义,是因为顶点就是边的头结点,只要知道头结点就能找到边。所以相当于两者一并包含在这个AdjList里了。加上顶点数和弧数,刚好也是三项:顶点表,边表,两个int(都是附加信息视为一项)。
例:常用操作
算法部分
思路
伪代码
*Locate函数不赘述,如果忘记烦请见上文构建无向网的代码部分
第三行稍微有点难理解,其实是用头插法。第一次执行是将p1.next=NULL, 然后插入,成为链表中除头结点外第一个也是最后一个结点;第二次执行p1.next就不是NULL了,而是上次的p1, 不懂的回去复习头插法,我把链接在下面贴出来(笑哭)
传送门:
【2月第四周学习记录】数据结构与算法王卓-第二章线性表-单链表(函数定义篇)_Finale_R的博客-CSDN博客
**见2-10 使用头插法创建链表
邻接表表示法的优缺点
优点
- 节约空间。对于n个结点的有向图:只需要n+e个结点;无向图则需要n+2e个结点。
- 在无向图中计算顶点的度很方便。
- 在无向图中方便找某一顶点的所有邻接点。
缺点
- 无向图中,增加/删除一条边要操作两次(不同的头结点处)
这也是引出邻接多重表的原因 - 有向图中计算顶点的度比较麻烦。
- 找两顶点间是否有边比较麻烦。
- ...
邻接矩阵和邻接表的联系
相似
- 同一张图/网的邻接矩阵中有几个1或权,邻接表中就有几个结点
不同
- 唯一性不同。同一张图/网的邻接矩阵只有一个,邻接表可以有很多个(arcs链表中结点顺序可以不同)
- 空间复杂度不同。邻接矩阵空间复杂度固定为O(n²),而邻接表的空间复杂度固定为为O(n+e)(有向图的空间复杂度为O(n+e),无向图的空间复杂度为O(n+2e),但2可以约掉)
- 适用性不同。邻接矩阵更适合存储稠密图,邻接表更适合存储稀疏图。
2.3 补充:邻接表的改进存储结构—十字链表与邻接多重表
来历
为了解决邻接表目前的缺点,引入了新的方法改进邻接表进行存储。
有向图:十字链表
概念
翻译:将出度入度结合
具体操作
顶点结点域从两个变为三个:增加一个入度域记录firstin第一个入度边
弧结点从两个变为四个:从a到b(两个)+下一个从a出发+下一个到b结束
例:
无向图:邻接多重表
为了解决加/减一条边要存/删两次的问题,将一条边同时关联到两个顶点。但相应的边结点的指针域就增加了。其中info可以存放权值等附加信息(对于网);mark域起到标记的效果,老师没有细讲,感兴趣可以自行深入探索。