不就几种数据类型么?06-图论

临近年关,有点倦怠,本来计划周末完成图论,但是赶到过年回家,感冒不舒服,不想动~。

这期主要是讲图论,虽然说我们开发中对这块理论不常见,但是也是比较重要的一种数据结构~,咬咬牙,学了就是,对吧,拉屎的时间就够你把我这篇看完了,0.0

下面步入正题:

1.图论背景引入

学前小故事~,开心一晚上。

一笔画问题

问题描述:

18世纪著名古典数学问题之一。在哥尼斯堡的一个公园里,有七座桥将普雷格尔河中两个岛及岛与河岸连接起来(如图)。问是否可能从这四块陆地中任一块出发,恰好通过每座桥一次,再回到起点?欧拉于1736年研究并解决了此问题,他把问题归结为如右图的“一笔画”问题,证明上述走法是不可能的。他不仅解决了此问题,且给出了连通图可以一笔画的充要条件是:奇点的数目不是0 个就是2 个(连到一点的数目如是奇数条,就称为奇点,如果是偶数条就称为偶点,要想一笔画成,必须中间点均是偶点,也就是有来路必有另一条去路,奇点只可能在两端,因此任何图能一笔画成,奇点要么没有要么在两端)

2、关于图的概念:

在图中,最基本的单元是顶点(vertex),相当于树中的节点。顶点之间的关联关系,被称为边(edge)

在有些图中,每一条边并不是完全等同的。比如刚才地铁线路的例子,从A站到B站的距离是3公里,从B站到C站的距离是5公里......这样就引入一个新概念:边的权重(Weight)。涉及到权重的图,被称为带权图(Weighted Graph)

还有一种图,顶点之间的关联并不是完全对称的。还拿微信来举例,你的好友列表里有我,但我的好友列表里未必有你。

相应的,在QQ当中,只要我把你从好友里删除,你在自己的好友列表里也就看不到我了。因此,QQ的好友关系可以认为是一个没有方向区分的图,这种图被称为无向图

3、图的表示

图的概念基本了解了,那么图的数据结构如何存储呢?

图的存储方式有很多种,比如的邻接矩阵,邻接表,逆邻接表,十字链表等。下面我们来一一学习下:

3.1、邻接矩阵

拥有n个顶点的图,它所包含的连接数量最多是n(n-1)个。因此,要表达各个顶点之间的关联关系,最清晰易懂的方式是使用二维数组(矩阵)。具体如何表示呢?我们首先来看看无向图的矩阵表示:

 

如图所示,顶点0和顶点1之间有边关联,那么矩阵中的元素A[0][1]与A[1][0]的值就是1;顶点1和顶点2之间没有边关联,那么矩阵中的元素A[1][2]与A[2][1]的值就是0。像这样表达图中顶点关联关系的矩阵,就叫做邻接矩阵。需要注意的是:矩阵从左上到右下的一条对角线,其上的元素值必然是0。这样很容易想明白:任何一个顶点与它自身是没有连接的

同时,无向图对应的矩阵是一个对称矩阵,V0和V1有关联,那么V1和V0也必定有关联,因此A[0][1]和A[1][0]的值一定相等。因为是无向图嘛。

那么,有向图的邻接矩阵又是什么样子呢?如下图:

 

从图中可以看出,有向图不再是一个对称矩阵。从V0可以到达V1,从V1却未必能到达V0,因此A[0][1]和A[1][0]的值不一定相等。看图,V0可以到达V1,但是V1不能达到V0,所以A[0][1]=1,A[1][0]=0;

  • 优点:简单直观,可以快速查到一个顶点和另一顶点之间的关联关系。
  • 缺点:占用了太多的空间。试想,如果一个图有1000个顶点,其中只有10个顶点之间有关联(这种情况叫做稀疏图),却不得不建立一个1000X1000的二维数组,实在太浪费了。

3.2、邻接表和逆邻接表

为了解决邻接矩阵占用空间的问题,人们想到了另一种图的表示方法:邻接表。

在邻接表中,图的每一个顶点都是一个链表的头节点,其后连接着该顶点能够直接达到的相邻顶点。看起来是不是有点像前面我们学到过的hash的链式存储。其实很多数据结构的思想都都是想通的,就看你如何灵活运用。、

很明显,这种邻接表的存储方式,占用的空间比邻接矩阵要小得多。比如:

  • 要想查出从顶点0能否到达顶点1,该怎么做呢?很简单,我们从顶点0开始,顺着链表的头节点向后遍历,看看后继的节点中是否存在顶点1。
  • 要想查出顶点0能够到达的所有相邻节点,也很简单,从顶点0向后的所有链表节点,就是顶点0能到达的相邻节点。
  • 那么,要想查出有哪些节点能一步到达顶点1,又该怎么做呢?这样就麻烦一些了,我们要遍历每一个顶点所在的链表,看看链表节点中是否包含节点1,最后发现顶点0和顶点3可以到达顶点1。

像这种逆向查找的麻烦,该如何解决呢?我们可以是用逆邻接表来解决。其实就是讲邻接表反过来。存到达节点。如下图:

看图思意:讲到达节点做为头结点,那么他的后续节点就是可以到达这个节点的节点。这样一来,要想查出有哪些节点能一步到达顶点1就容易了,从顶点1向后的所有链表节点,就是能一步到达顶点1的节点。因此,我们可以根据实际需求,选择使用邻接表还是逆邻接表。

但是有一个问题:这个正反是不是太麻烦了些。如何一个图两种情况都存在呢?难道还要做两次数据存储么?下面我们开始学习十字链表,所谓十字链表就是讲邻接表和逆邻接表整合在一起。如图:

 

如图所示,十字链表的每一个顶点,都是两个链表的根节点,其中一个链表存储着该顶点能到达的相邻顶点,另一个链表存储着能到达该顶点的相邻节点。不过,上图只是一个便于理解的示意图,我们没有必要把链表的节点都重复存储两次。在优化之后的十字链表中,链表的每一个节点不再是顶点,而是一条边,里面包含起止顶点的下标。因此我们优化后:

为了更好的理解,我们看一下十字链表的数据结构:

  • firstin表示入边表头指针,指向该顶点的入边表中第一个结点;
  • firstout表示出边表头指针,指向该顶点的出边表中第一个结点;
  • data表示每一个节点数据;

重新定义了边表结点结构:

  • tailvex是指边起点在顶点的下标,
  • headvex是指边终点在顶点表中的下标,
  • headlink是指入边表指针域,指向终点相同的一下条边
  • taillink是指出边表指针域,指向起点相同的下一条边。

如图对于这样的一个有向图,构建如下的十字链表

举例:以数组下标为0,其存储顶点我V1。对于V1来说:

  • 出边:E[0][1](即V1与V2组成的有向边),E[0][2]
  • 入边:E[2][0],E[3][0]

对于数据下标为0记为:A0

A0节点的firstout指向如上图,是不是就是表示为这个顶点有两条出边(以该节点为起始点的边);

A0节点的firstin指向如上图,是不是就是就是表示这个节点有两条入边(就是以该节点为终点的边);

其中E[2][0]的headlink指向终点相同(都是指向A0)的下一条边,没问题吧,好理解,再看E[3][0]的taillink是指向E[3][1]表示E[3][0]与E[3][1]都是从V4为起始点的边。初学十字链表的时候,可能会觉得有些乱。哈哈~苗某私下在好好看看。

  1. 我们这一次介绍了图的定义和分类。根据图的边是否有方向,可分为有向图无向图。根据图的边是否有权重,可分为带权无权图。当然,也可以把两个维度结合起来描述,比如有向带权图,无向无权图等等。
  2. 图的表示方法有很多种。包括邻接矩阵、邻接表、逆邻接表、十字链表。(还有一种邻接多重表,有兴趣的小伙伴可以自学下)。

好的关于图的知识就介绍到这里,下期我们开始学习,位运算~。

 

PS:我开通了微信公众号,里面有关于数据结构全部资料,每天多学一点点比什么都实在。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值