图的存储结构

  • 前言

       图的存储结构相比线性表与树来说就复杂很多,对于线性表来说,是一对一的关系,所以用数组和链表均可以存放。树的结构是一对多的关系,所以我们将数组和链表的特性结合在一起才能更好的存放。而我们的图,是多对多的情况,另外图上的任何一个顶点都可以被看作为第一个顶点,任意点的邻接点也不存在次序关系。下图中各个图是同一个图,只是改变了一下位置而已,从性质上依然是同一个图。


  • 图的存储结构

       因为任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系,(内存物理位置是线性的,图的元素关系是平面的)。如果用多重链表来描述是可以做到的,但纯粹用多重链表导致浪费的是非常大的,(如果顶点的读书相差太大,就会造成巨大的浪费)。前辈们就想出了五种方法:

一、邻接矩阵:

       1、无向图:

          考虑到图是由顶点和边或弧组成,合在一起比较困难,就很自然二点分开来存储。顶点因为不区分大小主次,所以用一个一维数组来存储,而边或弧是顶点与顶点之间的关系,很自然的想到了二维数组:

       上图中,二维数组可以用bool类型,只是涉及到0和1两个值,a[i][j]==1,表示i和j之间有一条边,没有方向的边,因为表示的是无向图,所以整个矩阵是一个对称矩阵,中间的对角线表示顶点不能与自己有一条边。

       我们可以设置两个数组,顶点数组为vertex[4]={V0, V1, V2, V3}, 边的数组为arc[4][4], a[i][j]==0,表示没有从i到j的边,a[i][j]==1,表示有。有了二维对称矩阵就很容易的知道了图中的信息了,要判定任意两顶点之间是否有边就非常容易了。要计算某个顶点的也非常容易,只需计算某个顶点所在的二维矩阵的某一行或某一列中有多少个1就行,因为是对称矩阵,所以顶点的行和列是相等的。求某个顶点的邻接点只需将这个顶点所在的行或者列扫描一遍,是1的话就是邻接点。

       2、有向图:

          上面所说的都是无向图,无向图的邻接矩阵浪费了一半的空间,而如果是有向图就会把资源利用的好好了,我们可以用arc[i][j]表示有一条弧是从i指向j的,但并不能表示j指向i,下图中很直观的表示出来了:

 

       可见顶点数组 vertex[4]={V0, V1, V2, V3},孤数组arc[4][4]也是一个矩阵,但因为是有向图,所以这个矩阵并不对称,例如由V1到V0有弧,得到arc[1][0]=1,而V0到1没有孤,因此arc[0][1]=0。另外有向图是有讲究的,要考虑入度和出度,顶点1的入度为1,正好是第1列的各数之和,顶点V1的出度为2,正好是第V1行的各数之和。

       3、网:

          在之前的博客中提到了网的概念,事实上也就是每条边上带有权的的图就叫做网,我们可以这样表示:

用无穷大表示两个顶点之间没有边或弧,有的话就存放具体权值。

 

二、邻接表

          之前的邻接矩阵看上去不错,索引和排版都很方便,但是相对于顶点多二边数少的图无疑对空间造成了巨大浪费,所以邻接表就结合了数组与链表一起来存储,这种方式在图结构中也适用,我们称之为邻接表:

         a、图中的顶点用一维数组来存储,因为数组可以很轻易的读取到顶点的信息

         b、图中的每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不确定,所以用单链表存储

       上图很直观的表现了邻接顶点的存储,每一个顶点与之相邻的顶点都存放到以这个顶点为首的单链表中,由于邻接点的个数不确定,所以可以每次都分配一个节点,一直到最后一个节点,我们用空指针来表示这是最后一个顶点。

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

出度可以计算每个链表的长度就行,但是这样很难得到入度,我们可以弄一个逆邻接表,所有的弧的方向都在链表中相反的表示。对于带权值的网图,我们可以在节点定义中在增加一个数据域就可以了。

 

三、十字链表

          邻接表固然优秀,但还是存在许多不足,例如对有向图的处理上,有时候还需要建立一个逆邻接表,那我们有没有可能把邻接表和逆邻接表结合起来呢?

为此我们用十字链表重新定义顶点表的结构:

                       data                              firstin               firstout

                                                                 data表示顶点的数据信息,firstin是这个数据第一个入边表的指针,firstout是第一个出边表的指针

重新定义边表节点结构:

               tailVex              headVex                headLink                 tailLink 

tail是尾巴,head是头,Vex是指针,Vex是顶点,tailVex表示的是弧顶点的起点的下标,headVex表示的弧的终点的顶点的下标,所以整个节点表示的是一个弧而不是顶点,十字链表同时存放的邻接表和逆邻接表,

                                                                                               红色的表示入度的弧,蓝色的表示出度的弧,没有入度或出度用空指针表示。

 

        十字链表的好处就是因为把邻接表和逆邻接表整合在了一起,这样既容易找到以i为尾的弧,也容易找到以Vi为头的弧,因而容易求得顶黑的出度和入度。十字链表除了结构复杂一点外,其实创建图算法的时间复杂度是和邻接表相同的,因此,在有向图的应用中,十字链表也是非常好的数据结构模型。

 

四、邻接多重表

        如果我们在无向图的应用中,关注的顶点的话,那么邻接表是不错的选择,但如果我们关注的是边的操作,比如对已经访问的边进行标记,或者删除某一条边等等,邻接表就不那么方便了

因此,我们也仿照十字链表的方式,对边表结构进行改装,重新定义的边表结构如下

iVexiLinkjVexjLink

        其中iVex与jVex是与某条边依附的两个顶点表中的下表,iLink是指向依附于顶点iVex的吓一条边,jLink是指向依附于顶点jVex的下一条边,也就是说在邻接多重表里面,边表存放的是一条边,而不是一个顶点。

老师讲的时候也是一笔略过,现在我还有点搞不懂,先记录下来准备考试,等以后回来再补充一些把。

 

五、边集数组

        边集数组是由两个一维数组组成,一个存储顶点的信息,另一个存储边的信息,这个边集数组每个数据元素都由一条边的起点下表标,终点下标和权组成。

        把边的起点终点和权值存起来,然后以起点从小到大或者从大到小排序,记录每个顶点在数组中的起始位置和长度.。适用于点多边少的稀疏图,或两点之间有多条弧的时候。我们可以用一个结构体数组存储,这个结构用的比较多,尤其再ACM竞赛中,其实结构也是比上两种也简单一些,建议多刷一些这方面的题。


  • 后记

        我们学计算机的,一直很难坚持下来,不知道别人是什么样,但在我们学校,很少人愿意努力,不是不愿意,是真的没有办法,当学到什么东西不会的时候,也没人可以请教,于是乎就放弃了,我们之所以把这些东西记下来,就是给自己的脑子一个清晰的概念,而不是看看视频,看看书就过的,自己整理的东西一定会有很清晰的思路,而且或许也可以帮助了别人。在这里提醒自己也建议看到这篇blog的人,学到东西一定要整理出来,再去实践,再去复习,哪些课程对自己很重要就一定不要抱着仅仅是应付考试的心态,认真去学,老师讲课真的学不到什么,只是带你入门,当然不排除那些尽职尽责的老师们,多跟老师交朋友,一定会学到不少东西,像我们老师讲课真的不敢恭维,我们老师上的课再我们学院都出了名的烂,所以我上课就没怎么听过,都是快考试的时候去网上学,班上还有好多同学苦苦找不到出路,诶,不知道该做些什么,尽力就好,保持着强烈求知欲的心态,不忘初心,方得始终,与君共勉。

 

 

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值