图的存储结构比线性表和树就要复杂多了,图的顶点没有顺序的概念,任何一个顶点都可以是起始,下面四张图其实是同一个图形结构.
真是的场景下会有复杂的多的情况出现,顺序存储完全无法实现,即使是多重链表,由于顶点的度可能差别很大,就得设置很多个指针域,效率很低.
一:邻接矩阵
先用一个一位数组来存储顶点数据,然后用一个二维数组来存储连通关系,二维数组在这里就是一个矩阵.
图G(V,{E})有n个顶点,一个二维数组arc(nxn),如果v1v2有边,即v1v2 ∈ E,则arc[1][1] = 1,否则为0.
1.无向图
看一个示例,下图左是一个图,右边是数据的数组和边的数组;
对角线在图的概念中是无意义,设置为0,
例如第二行,提现了v1和v0,v2,v3的连通关系,也能得到v1的度是2;
能看的v1和v3不连通,那么因为是无向图,同样的v3和v1也不连通,因此无向图的矩阵是对称的.对称矩阵的Aij = Aji.
想知道vi的度和邻接点,遍历第i行就可以了.
2.有向图
有向图无非就是不对称了,存在弧的是1,不存在是0.
对于网的情况,也就是带权的有向图,矩阵需要修改,因为权可能为0,可能是负数,因此我们可以根据具体情况设置,比如swift中设置一个Float的.infinity,这里用∞表示.
二:邻接表
通过邻接矩阵,我们可以发现,对于稀疏图,也就是边很少顶点很多的图,矩阵有点太浪费了.
在树的存储结构中,有一种孩子表示法,这里也可以使用,于是就有了一种数组和链表结合(或者两个链表)的存储方法,叫做邻接表.
首先用一个数组存储顶点,每个元素分为数据域和指针域,指针域连接一个单链表,可以叫做边表,对于下面的无向图,
v0连通三个节点,因此v0的边表有三个节点.边表的节点也分为数据域和指针域,数据域存储顶点的下标,指针指向下个节点.
对于这个结构,想知道某个顶点的度,和那些顶点连通就很容易找到.
对于有向图,可以选择以弧尾来存储边表,也可以用弧头来存储边表,叫做逆邻接表.
对于带权的网图,在边表增加一个权的数据域即可,这样就可以描述边的权值.
三:十字链表
对于有向图,邻接表只体现了出度,逆邻接表只体现了入度,而十字链表就是把邻接表和逆邻接表结合起来.
首先定义顶点的结构:data是数据,firstin是一条进入的弧,firstout是一条出去的弧.
然后将顶点存储在一个数组A中
然后定义弧:tailvex是这条弧的终点,也就是弧头,存的是数组A中顶点的下标,headvex是这条弧的起点,也就是弧尾,存的也是数组A中顶点的下标;
headlink是指针,指向另一条终点相同的弧,同样的taillink也是指针,指向起点相同的另一条弧.
需要注意这里有先后顺序,不会互相指来指去.
下面这张图是十字链表的结构.这张图看起来复杂,其实从它的定义这个角度来看就清晰了,十字链表是把邻接表和逆邻接表结合起来,而下面这图的实线就是邻接表,虚线就是逆邻接表.
四:邻接多重表
前面说到无向图的邻接表,它是针对顶点的,对于边其实是重复定义的,如果要删除某一条边,需要删除两个链表里的两个节点.
比如下面这图要删除v0v2这条边,需要把打勾的节点删掉.
现在仿照十字链表的方式来改造一下;
首先顶点的结构分为数据域和指针域,然后存储在数组中;
然后边表节点结构为两个数据域ivex和jvex,存储的是一条边的两个顶点,还有指针ilink指向另一个有顶点ivex的边,指针jlink指向另一个含有顶点jvex的边.
对于下面的这个无向图,一共四个顶点,五条边,首先把顶点和边都创建出来:
现在顶点和边已经有了,再来连线,,虽然无向图不存在方向,但是再连线的时候考虑方向可以实现单循环,这样连线全都是唯一的,既不会漏,也不会存在重复.
首先以四个顶点为起点,先连接4条边,也就是①到④;
然后找边01的ilink,它应该是以0为终点的,那么只有一个就是02,也就是⑤,而01的jlink,应该指向是以1终点的,发现没有,于是是空;
接着边12的ilink是以1位终点的,则是01,也就是⑦,jlink是以2位终点的,则是02,也就是⑨;
五:边集数组
对于非常关注边的情况下,边集数组可以很好的发挥作用;由两个数组组成,一个存储顶点,另一个存储起点和终点,当然还可以再加上权,访问信息等等.
对于边集数组,之后在图的应用中还会提到.