数据结构 - 图 - 存储结构

本文详细介绍了图的几种存储结构,包括邻接矩阵(无向图、有向图、网)、邻接表、十字链表和邻接多重表。邻接矩阵适用于稠密图,邻接表和十字链表适合稀疏图,邻接多重表则方便边的操作。文章通过实例展示了各种结构的优缺点和适用场景。
摘要由CSDN通过智能技术生成

图的存储结构

  • 线性表:一对一:数组或者链表
  • 树:一对多:数组和链表结合
  • 图:多对多:?

因为任意两个顶点都可能存在联系, 因此无法以数据元素在内存中的物理位置来表示元素之间的关系(内存物理位置是线性的,图的元素关系是平面的)。

⭐️ 各种存贮结构的适用类型:
数组(邻接矩阵):有向图和无向图 ——— 稠密图
邻接表(逆邻接表):有向图和无向图 —— 稀疏图
十字链表:有向图
邻接多重表:无向图

邻接矩阵

邻接矩阵(无向图)

考虑到图是由顶点边或弧两部分组成,合在一起比较困难,那就很自然地考虑到分为两个结构来存储。

  • 顶点因为不区分大小,主次,所以用一个一维数组是很不错的选择
  • 而边(或者弧)由于是顶点与顶点之间的关系,需要用二维数组来实现。

💡邻接矩阵
图的邻接矩阵的存储方式是用两个数组来表示图。一个一位数组存储图种顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
在这里插入图片描述
如图,我们可以设置两个数组,顶点数组为 Vertex[4] = {V0, V1, V2, V3},边数组 arc[4][4]为对称矩阵(0表示不存在顶点的边,1表示顶点存在边)

对称矩阵:N阶矩阵的元满足a[i][j] = a[j][i](0<=i, j <=n)。即 从矩阵的做对角线到右下角的主对角线为轴。除轴外的右上角元素与左下角的元素相等。

有了这个二维数组组成的对称矩阵,可以轻易的得出图中的信息:

  • 任意两顶点是否有边,如Vi和Vj ->arc[i][j]/arc[j[i]
  • 要知道某个顶点的(关联边之和),其实就是这个顶点Vi在邻接矩阵中第i行或者第i列元素之和

邻接矩阵(有向图)

在这里插入图片描述
可见顶点数组 Vertext[4] = {V0, V1, V2, V4},弧数组arc[4][4]也是一个矩阵,但因为是有向图,所以这个矩阵并不对称。例如由V1到V0有弧,得到arc[1][0]=1,而V0到V1没有弧,因此 arc[0][1] = 0

关于顶点度的计算。例如顶点Vi,出度就是i行的所有元素之和,而入度就是i列所有元素之和。

例如V1的出度为1+1=2,入度为1。

邻接矩阵(网)

每条边上带有权值就叫做
在这里插入图片描述

这里代表一个计算机允许的,大于所有边上权值的值

对于网的邻接矩阵,有以下定义:

arc[i][j] = {
	W(i,j)  ,  若 <Vi, Vj>或(Vi,Vj)∈E
	∞      , 边或者弧不存在
}

💻编程使用邻接矩阵实现以下图的边和顶点关系的演示:
在这里插入图片描述

#include <stdio.h>
/**
 * 打印二维数组
 * @return
 */
void PrintArray(int a[4][4], char v[]) {
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            if (a[i][j] > 0) //当存在此方向的弧时输出起始地和权值
                printf("%c -> %c : %d\n", v[i], v[j], a[i][j]);
        }
    }

}

int main() {
    char Vertex[4] = {'A', 'B', 'C', 'D'};
    int Arc[4][4] = {{0, -1, -1, 18},
                     {8, -1, 2,  -1},
                     {4, -1,  0,  -1},
                     {-1, -1,  -1,  0}};
    printf("Adjacent Matrix:\n");
    PrintArray(Arc, Vertex);
    return 0;
}

输出结果:

Adjacent Matrix:
A -> D : 18
B -> A : 8
B -> C : 2
C -> A : 4

邻接表

邻接矩阵无疑有很多优点,例如容易理解,而且索引和编排都很舒服。但是我们也发现,对于边数较少的图,这种结构造成了大量的空间浪费。

考虑将数组和链表结合起来来存储,我们称之为邻接表(Adjacency List)
⭐️处理方法如下:

  • 图中顶点用1个一维数组来存储(也可以使用单链表来存储)
  • 图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数的不确定,所以我们选择用单链表来存储。

在这里插入图片描述在这里插入图片描述
如果是有向图,邻接表结构也是类似的,我们先看下把顶点当弧尾建立的邻接表,这样很容易就可以得到每个顶点的出度:

在这里插入图片描述
但是有时候为了便于确定顶点的入度或以顶点为弧头的弧,我们可以建立一个有向图的逆邻接表
在这里插入图片描述
对于带权值的网图,可以在边表结构中再增加一个数据域来存储权值即可:
在这里插入图片描述

总结:在无向图的邻接表中,顶点vi的就是第i个链表的结点数。
而在有向图中,第i个链表中的结点个数只是顶点vi的出度或者入度

十字链表

有向图的处理,同时应用邻接表和逆邻接表显然有些复杂,我们尝试寻找一种数据结构把这两种结构结合起来。这就是我们要谈的十字链表

🍉数据结构简图
顶点结点:

datafirstInfirstOut
数据域首入弧索引首出弧索引

弧结点结构:

tailVexheadVexheadLinktailLinkinfo
弧尾顶点(j)弧头顶点(i)同顶点(i)下一条入度同顶点(j)下一条出度信息

蓝色:出度 ;红色:入度
在这里插入图片描述
💡十字链表的好处就是因为把邻接表和逆邻接表整合在了一起,这样极容易找到以Vi为尾的弧,也容易找到以Vi为头的弧,因而容易求得顶点的出度入度

十字链表的时间复杂度和邻接表相同

邻接多重表

⚠️邻接多重表的画法并不唯一,实现效果即可

对于无向图,邻接表是不错的选择,但如果我们关注边的操作,比如对已经访问过的边做标记,或者删除某一条边等一系列操作,邻接表就显得的不那么方便了。

在这里插入图片描述
因此我们仿照十字链表的方式对边表结构进行改装,重新定义的边表结构如下。

iVexiLinkjVexjLink
依附的一个顶点依附的另一个顶点依附Vi的下一条边依附Vj的下一条边

也就是说在邻接多重表中,边表存放的是一条边,而不是一个顶点

在这里插入图片描述
步骤:
(1)首先分发结点,对于边的下标顺序并不重要,因此邻接多重表的结构并不唯一;
(2)首先使得V1、V3、V5分别指向边(0,1) (2,1) (4,1);
(3)观察 (0,1),它对于V1的临边有(0,3),因此需要让(0,1)的i指针域指向(0,3)
(4)观察V2,与V2关联的边有(0,1) (2,1) (4,1),按照顺序先将V2的FristArc指向(0,1),然后将(0,1)的j指针域指向(2,1),之后让(2,1)的j指针域指向(4,1);
(5)观察边(2,1),对于V3的临边有(2,3)和(2,4),因此按照顺序,先让(2,1)i 的指针域指向 (2,3),然后让(2,3)的 i 指针域指向 ( 2, 4);
(6)观察顶点V4,与V4相关联的边有(0,3)和(2,3),此时有两种方式,先指向(0,3)和先指向(2,3),我们这里让V4的FristArc指向(2,3),然后让(2,3)的 j 指针域指向(0,3);
(7)观察(4,1)的临边对于V5的临边有(2,4),因此只需要将 (4,1)i的指针域指向(2,4)即可;
(8)将未指向任何内存空间的边左右指针域赋值为空。

🔔注意
在边的Vi或者Vj的指针域寻找指向的过程中,只要下一条边中含有Vi或者Vj(无论方向)任一,即可使得Vi或者Vj的指针域指向下一条边。

边集数组

边集数组是由两个一位数组构成,一个是存储顶点的信息,一个是存储边的信息。这个边数组的每个元素的数据结构如下:

beginendweight
起点下标终点下标

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值