24张图,九大数据结构安排得明明白白( 五 )


而根据实际情况可以分析得 , 图中的顶点并不是任意两个顶点间都会相连 , 不是都需要对其边上权重进行存储 。那么存储的邻接矩阵实际上会存在大量的0 。虽然可以通过稀疏表示等方式对稀疏性高的矩阵进行关键信息的存储 , 但是却增加了图存储的复杂性 。
因此 , 为了解决上述问题 , 一种可以只存储相连顶点关系的邻接表应运而生 。
邻接表在邻接表中 , 图的每一个顶点都是一个链表的头节点 , 其后连接着该顶点能够直接达到的相邻顶点 。相较于无向图 , 有向图的情况更为复杂 , 因此这里采用有向图进行实例分析 。

24张图,九大数据结构安排得明明白白

文章插图
 
在邻接表中 , 每一个顶点都对应着一条链表 , 链表中存储的是顶点能够达到的相邻顶点 。存储的顺序可以按照顶点的编号顺序进行 。比如上图中对于顶点B来说 , 其通过有向边可以到达顶点A和顶点E , 那么其对应的邻接表中的顺序即B->A->E , 其它顶点亦如此 。
通过邻接表可以获得从某个顶点出发能够到达的顶点 , 从而省去了对不相连顶点的存储空间 。然而 , 这还不够 。对于有向图而言 , 图中有效信息除了从顶点“指出去”的信息 , 还包括从别的顶点“指进来”的信息 。这里的“指出去”和“指进来”可以用出度和入度来表示 。
入度:有向图的某个顶点作为终点的次数和 。
出度:有向图的某个顶点作为起点的次数和 。
由此看出 , 在对有向图进行表示时 , 邻接表只能求出图的出度 , 而无法求出入度 。这个问题很好解决 , 那就是增加一个表用来存储能够到达某个顶点的相邻顶点 。这个表称作逆邻接表 。
逆邻接表逆邻接表与邻接表结构类似 , 只不过图的顶点链接着能够到达该顶点的相邻顶点 。也就是说 , 邻接表时顺着图中的箭头寻找相邻顶点 , 而逆邻接表时逆着图中的箭头寻找相邻顶点 。
24张图,九大数据结构安排得明明白白

文章插图
 
邻接表和逆邻接表的共同使用下 , 就能够把一个完整的有向图结构进行表示 。可以发现 , 邻接表和逆邻接表实际上有一部分数据时重合的 , 因此可以将两个表合二为一 , 从而得到了所谓的十字链表 。
十字链表十字链表似乎很简单 , 只需要通过相同的顶点分别链向以该顶点为终点和起点的相邻顶点即可 。
24张图,九大数据结构安排得明明白白

文章插图
 
但这并不是最优的表示方式 。虽然这样的方式共用了中间的顶点存储空间 , 但是邻接表和逆邻接表的链表节点中重复出现的顶点并没有得到重复利用 , 反而是进行了再次存储 。因此 , 上图的表示方式还可以进行进一步优化 。
十字链表优化后 , 可通过扩展的顶点结构和边结构来进行正逆邻接表的存储:(下面的弧头可看作是边的箭头那端 , 弧尾可看作是边的圆点那端)
data:用于存储该顶点中的数据;
firstin指针:用于连接以当前顶点为弧头的其他顶点构成的链表 , 即从别的顶点指进来的顶点;
firstout指针:用于连接以当前顶点为弧尾的其他顶点构成的链表 , 即从该顶点指出去的顶点;
边结构通过存储两个顶点来确定一条边 , 同时通过分别代表这两个顶点的指针来与相邻顶点进行链接:
tailvex:用于存储作为弧尾的顶点的编号;
headvex:用于存储作为弧头的顶点的编号;
headlink 指针:用于链接下一个存储作为弧头的顶点的节点;
taillink 指针:用于链接下一个存储作为弧尾的顶点的节点;

24张图,九大数据结构安排得明明白白

文章插图
 
以上图为例子 , 对于顶点A而言 , 其作为起点能够到达顶点E 。因此在邻接表中顶点A要通过边AE(即边04)指向顶点E , 顶点A的firstout指针需要指向边04的tailvex 。同时 , 从B出发能够到达A , 所以在逆邻接表中顶点A要通过边AB(即边10)指向B , 顶点A的firstin指针需要指向边10的弧头 , 即headlink指针 。依次类推 。


推荐阅读