算法与数据结构 --- 图 --- 图的存储结构


第一部分 --- 邻接矩阵

 

1.用数组来存储图的时候需要两个数组:

第一个是一维数组(顶点表),这个一维数组用来存储点集中每个点存储的数据信息

第二个是二维数组(邻接矩阵),a[ i ][ j ],i表示顶点表中的第i个结点,j则是第j个结点,在有向图中,如果i j结点之间存在弧关系的话,则a[ i ] [ j ] = 1,如果不存在的话则等于0(对于无向图则是是否存在边关系,若有则1,无则0)

2.这个邻接矩阵是一个 n * n的方阵,其中 n = 图中的结点数(只有这样才能够描述图中每个结点之间的关系)

 

1.对于分析1:在无向图中,如果结点A到结点B之间有边的关系的话,那么结点B到结点A之间也会有边关系,反映到邻接矩阵上的效果就是会使得矩阵呈现出对称的姿态

2.关于最后一个特别:完全图的定义是:每对不同的顶点之间恰有一条边相连

所以根据定义我们就可以直到在完全图中顶点和顶点本身之间没有边相连(对角元素为0),然后顶点和所有不同的顶点之间有且只有一条边相连(其余为1)

1.为尾可以理解为 “为起点” ; 为头可以理解为 “为终点 ” 

 

网(有权图)的存储和普通图的存储之间的唯一区别就是网的邻接矩阵中,如果两个顶点之间有边的关系的话则在邻接矩阵中的对应位置存放的不是1而是两个顶点之间的边的权值,如果两顶点之间没有关系的话存放的也不是0,而是无穷大

1.有权图(网)的邻接矩阵中两结点间没有边关系时在对应位置填的无穷大值需要我们自己去定义 ---  #define MaxInt  我们想定义为无穷大的数值 

1.我们在算法中进行的查找顶点位置的操作中,查找到的位置其实是顶点在顶点表中对应位置的下标

 

 

 

1.如果是用邻接矩阵去存储网 / 图的话,当我们向网 / 图中增加一个结点的时候,我们就要在邻接矩阵中增加一行和一列,这就很麻烦(在网 / 图中删除一个结点也同理,删除一行和一列也很麻烦)

 2.第三点的浪费时间是因为我们会遍历很多无效元素


第二部分 --- 邻接表

1.在使用邻接表存储图的时候,我们首先需要创建其一个表头数组(一维数组),这个数组中存放的是每个顶点对应的链表的头结点(以结构元素的方式存储,这个结构元素中对于无权图来说由两个域组成:一个用来存储顶点数据的数据域和一个指向存储顶点的邻接顶点的链表结点的指针域 --- 所谓顶点的邻接顶点就是和顶点具有边 / 弦关系的顶点)

2.有了表头数组之后,我们就要给表头数组中的每一个元素生成链表了。

如果存储的是无向图的话,则链表中的结点有一个存储表头顶点的邻接顶点在表头数组中的对应位置下标的数据域和一个指向存储表头顶点的下一个邻接顶点的链表结点的指针域(如果没有邻接结点了就将指针域置为空)

如果存储的是有权图的话,我们还需要在链表的结点中添加一个存储表头顶点和邻接顶点之间形成的边的权重值的数据域

3.对于无向图而言,表头顶点和邻接顶点之间形成的是边,边是没有指向;但是对于有向图而言,表头顶点和表头顶点的邻接顶点之间形成的是弦,弦是有指向,这个指向是表头顶点指向表头顶点的邻接结点

4.最后,表头数组和通过表头数组给每一个表头生成的链表这两部分组合在一起就是用来存储图的邻接表了

5.表头数组中的顶点又称为邻接表的头结点,通过头结点生成的链表中的结点又称为邻接表的表结点

1.关于第一点的邻接表不唯一:这是因为通过每一个头结点生成的链表中表结点中存放的是头结点的邻接结点的在表头数组中的下标,而头结点的邻接结点之间的顺序可以随意调换,也就意味着同一个链表中的表结点的顺序可以随意调换,这也就意味着一个图的邻接表可能有多种形态,则邻接表不唯一

2.关于第二点:在无向图中,如果顶点A和顶点B之间有边的话,则两个结点之间互为邻接结点

在有向图中只有在两个结点之间有相互指向对方的弦的时候,两个结点之间才互为邻接结点1.在邻接表中存储有向图的时候,表头顶点和其邻接顶点之间形成的弦是有指向的,这个指向是表头顶点指向邻接顶点 

 1.邻接表存储有向图的特点就是找出度容易,找入度难。比如找结点A的入度的时候我们需要遍历整个邻接表,统计数据域中存的是结点A在表头结点中的下标的表结点个数,这个个数就是结点A的入度(遍历整个邻接表,非常麻烦)

2.为了解决1中出现的问题,人们给出的方案是再创建一个有向图的逆邻接表,这个逆邻接表和邻接表的唯一区别就是:表头顶点和它的邻接顶点之间形成的弦的指向变了,变成邻接结点指向表头结点了。通过逆邻接表我们就可以很方便的计算出每一个表头顶点的入度了

3.实际存储时到底用邻接表还是用逆邻接表呢? --- 根据实际情况来判断,如果用出度多就用邻接表,用入度多就用逆邻接表

4.邻接表的存储结构确定了之后,我们就唯一确定了一个图了

 1.上面这个结构体类型就是表头数组中的元素的类型,其中ArcNode类型是图

2.在花括号的后面第一个VNode是和typedef对应的,这是在对struct VNode进行重命名,第二个.ArjList [ MVNum ]则是在将以上面这个结构体类型来创建数组时得到的数组类型 VNode [MVNum]重命名为 AdjList , 如果我们要创建一个类型为上面这个结构体类型,长度为MVNum的数组的话,只需要直接写 : AdjList 数组名;即可

3.下面这个ArcNode类型是我们自定义的表结点的数据类型,而VNode则是我们自定义的头结点的数据类型

1.网需要这个info数据域,无权图不需要

2.adjvex数据域中存放的是邻接顶点在表头数组中的对应位置的下标

通过AdjList(头结点数组类型重命名后的结果)创建一个头结点数组(表头数组),创建这个表头数组(还未填充头结点元素和表结点元素)就相当于创建了一个空的邻接表

此时每当我们通过这个自定义的图类型创建一个空图的时候,这个图就会和一个空邻接表绑定,我们可以通过修改邻接表中的头结点和表结点来对我们创建的空图进行修改

(ps:在图自定义类型中还有两个整型变量,这两个整型变量分别是用来描述我们创建的图的顶点个数和边的个数的)

 

第三步的作用是创建边 / 弦:

首先输入我们要创建的边 / 弦的左右两个顶点 Va和 Vb 的值

然后根据这个值在表头数组中查找到两个顶点的下标

最后:

如果是无向图的话,则在 Va 和 Vb 互为邻接顶点,所以我们需要分别在代表Va和Vb顶点的头结点对应的单链表中插入一个表结点,表结点中存放的数据则分别是 Vb 在表头数组中对应位置的的下标和 Va在表头数组中对应位置的下标的下标

如果是有向图的话,则需要我们确定这两个顶点谁是弧的起点,谁是弧的终点,假设Va是弧的起点,则我们要在代表Va的头结点的链表中插入一个表结点,表结点中存放的数据是Vb顶点在表头数组中的对应位置的下标

 

上面这个算法中向链表中插入元素使用的方法是头插法

1.上面的第二点中,需要2E个表结点是针对无向图的,对于有向图而言只需要E个表结点(N是图的结点个数,E是图的边数) --- 这是因为在无向图中,存在边关系的两个结点之间互为邻接结点,而有向图中,只有两个结点之间存在弦互相指向对方的时候,这两个结点之间才互为邻接结点

2.如果用邻接矩阵来存储图的话,给我们任意两个点我们可以直接在邻接矩阵中找到对应位置来判断两个结点之间是否存在边,但是对于邻接表来说,我们则需要先遍历表头数组找到结点,然后再遍历头结点对应的链表后才能够做出判断,这就使得邻接表不方便检查任意两点之间是否存在边

一个单链表中的表结点个数等于邻接矩阵中对应行中的非零元素个数

1.邻接表中的链表中的表结点的顺序是能够改变的,所以说邻接表不唯一

2.创建邻接矩阵的时候,规定要创建n*n大小的方阵(n是图的结点个数),所以空间复杂度是O(n*n)而在创建邻接表的时候,如果是无向图的话,空间复杂度为O(n + 2e)(e是图的边数,然后由于算的是复杂度,所以可将空间复杂度简化为O(n +e)),如果是有向图的话,空间复杂度为O(n+e),综上可认为邻接表的空间复杂度为O(n+e)


第三部分 ---  十字链表与邻接多重表

十字链表和邻接多重表是对邻接表的改进

 

 

十字链表其实就是在邻接表的基础上做了一些修改:

1.给表头数组中的每个元素增加一个指针域,这个指针域指向的是存在元素位置上的头结点对应的顶点作为弦的终点的时候的邻接结点链表

2.链表中的每一个表结点都增加一个数据域和一个指针域,增加的数据域是用来存储头结点对应的顶点作为弦的终点时的邻接结点(弦的起点)在表头数组中的下标的

增加的指针域则是用来指向存储了下一个作为弦的起点的顶点的邻接结点的下标的表结点的,如果没有的话就置为空指针

对于无向图而言,在其邻接表中如果要删除图中的一条边的话,我们需要先遍历表头数组找到边的左右两个结点,然后再遍历两个结点对应的邻接结点链表,在链表中找到对应边后进行删除,这就很麻烦

1.使用邻接多重表我们就能够保证无向图中的每条边都只被存储一次

2.邻接多重表也只是对邻接表本身的一些修改:
一.表头数组不发生改变,但是链表中的表结点发生改变

二.在不考虑需要标记是否被搜索(mark标记域)和边是否具有权重值(info数据域)的前提下,我们给每个表结点添加一个数据域和一个指针域:
其中一个数据域用来存放表结点所在的链表对应的顶点在表头数组中的下标,然后另一个数据域用来存放与顶点具有邻接关系的顶点在表头数组中的下标

其中一个指针域用来指向依附于顶点的下一条边,还有一个指针域用来指向依附于顶点的邻接结点的下一条边

(在这里我需要补充一个知识点:就是邻接表中的每一个表结点都代表着一条边,所以我们也可以用边来代指表结点)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值