静态链表(链式前向星)- 表示图的另一种方法
静态链表是一种用数组静态储存的数据结构。它通常用来表示图。它还有个非常有趣的名字叫“链式前向星”。你可以通过以下两种方式来了解它:
但是我还是建议两种都了解一下啦。如果你知道其中一种或两种的吧,点这里跳过。点一下,玩一年。
在本文中,让u
表示边从该节点出发,v
表示边到达该节点。
前向星
前向星也叫做领接数组,边集数组。我们先把所有的边按照出发节点排序,到达节点的顺序不重要。接着分别用两个数组,一个存节点的在边数组的下标,一个存改节点的出度(有多少条边从该节点出发)。
范例
我们的图包含如下这些边(出,入):
|
|
我们首先以出发节点进行排序然后填入数组:
es[]
: 所有的到达节点head[]
: 以u
为起点的第一条边的位置len[]
:u
的出度(从该节点出发的边的数量)
|
|
Array | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
es | 2 | 3 | 4 | 4 | 2 | 4 | 3 |
head | 1 | 4 | 5 | 7 | |||
len | 3 | 1 | 2 | 1 |
下面我们来看如何取得所有从节点1
出发的所有边。我们可以用head[1] == 1
来找到第一条边在es[]
中的位置是1
。然后从head[1]
到head[1]+len[1]
遍历es[]
,得到es[1+0] == 2
, es[1+1] == 3
, es[1+2] == 4
。这样我们就得到了边(1,2), (1,3), (1,4)。
|
|
因为前向星需要进行一次排序操作,时间复杂度提升到了$ O(E \times log(E)) $, 所以前向星并不是非常常用。然而链式前向星避免了排序,就解决了这个问题。
领接表
领接表在C++中通常用vector<int> G[MAXN_V]
来实现。G[u]
指向一个包含所有从u
出发的边的vector。
范例
最后的结果如下图所示,左边的数组表示G[]
。当我们读入一条从u
出发的边,就推入G[u]
的vector.
|
|
链式前向星
链式前向星是前向星的改进版本,也可以说是领接表的静态版本。它不用像前向星那样进行排序。它是怎么做到的?不停地戳自己!:)
范例
我们和上文使用相同的范例。
有如下边(出发,到达):
|
|
我们开始构造数组:
es[]
: 所有的到达节点v
,出发节点的信息会在head[]
中储存。head[]
: 以u
为起点的第一条边的位置。next[]
: 下一条以u
为起点的边在es[]
中的位置。用0
来代表已经是最后一条了。
可以得到
Array | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
es | 2 | 4 | 4 | 3 | 3 | 2 | 4 |
head | 1 | 2 | 3 | 5 | |||
next | 4 | 0 | 6 | 7 | 0 | 0 | 0 |
我们需要另一个数组储存每个节点最后一条边的位置来构造next[]
数组。当然我们也可以反向构造,不断更新head[]
来储存。
下面我们来看如何得到从节点1出发的所有边:
用
head[1] == 1
来找到第一条边的位置,设i = head[1]
,es[i] == 2
输出了(1, 2)这条边,更新i = next[i]
,现在i == 4
。es[i] == 3
输出边(1, 3),更新i = next[i]
,现在i == 7
。es[i] == 4
输出边(1, 4),更新i = next[i]
, 现在i == 0
。因为
i == 0
,结束。
|
|
发现了吗?链式前向星除了第一次使用head[]
,其他时候都在es[]
和next[]
之间左右横跳来得到下一数据。想法和链表很像,不过用的是数组实现的。
比较
前向星$O(E \times log(E))$的复杂度让人望而却步,有时候可能会变得更糟。
领接表易于理解,用C++ STL vector也易于实践。vector动态分配空间所以我们不用知道边的数量。但是,有时候可能会浪费空间因为vector在需要的时候会加倍分配空间。
链式前向星不改变数组的顺序,它只是建立起了边之间的关系。它比使用STL更快而且在理解之后易于实现。
但是,这些数据结构都在寻找一条特定的边的时候不太优秀,在最坏情况下需要遍历到链表结束。