Last updated on

链式前向星存图原理


作者:千秋 Lead | 原文:知乎专栏 现在有很多教程难以看懂的原因是因为用数组下标(数字)来同时给节点和边编号,又用做边权的类型,由于一个数字没有类型,所以读者经常容易混淆编号与对象的对应,所以本文用字母给节点编号,数字给边编号

首先,我们要明确一点,链式前向星本质是一个存了一组边的数组,**基本等同于邻接表,**但是不同之处是:

邻接表存起点,终点指针(即数组索引),和可选的权重共三个值。

链式前向星只存终点指针,相同

\color{red}{起点}

的边归一类,同一类在一个链表内,后添加的边指向前添加的边,最先添加的指向空(这是其名中「前向」的由来),和可选的权重共两个值。

例如,这三条边在同一链表内

所以链式前向星有个优点是,可以方便地遍历一个点的出边,这在写BFS,DFS等算法时很方便。

那如何给节点编号呢?只需要添加几个就编几号好了,或者从0号开始也行。

以此图为例,我们先看一下存完后的样子。

next数组就是链表的实现,next[当前点]=指向点。每一个边的编号都指向着共起点的另一边的编号,最先加入或无共起点的边指向-1,所以最开始初始化为-1。

to 数组是此边指向的点编码。

lesfh (last edge starting from here)在大部分代码中又名head ,我认为我的名字更直观,作用是用来保存最后一个从某点开始的边的编号,输入点编号,输出边编号。它是用来让当前边查询上一个从同样点开始的边,以指向的。所以next会在每条边加入时,指向最后一个从现在起点开始的边,因此要初始为-1。听不明白?看个例子吧。

我们按照图中顺序存边。看表顺序:先上到下,后左到右,lesfh行单独看

next0 (A->B)1 (B->C)2 (A->C)3 (A->D)-1 (最开始没有从A开始的上一条边,故指-1)-10 (lesfh[A]=0,故指向0.)2 (lesfh[A]=2,故指向0.)to0 (A->B)1 (B->C)2 (A->C)3 (A->D)B (0号边指向B)CCDlesfhABCD-1-1-1-10 (最后的从A开始的边是0号)12 (因为2从A开始,改为2)2 (因为3从A开始,改为3)

相信现在看代码不难。

// 给顶点起别名方便观察

using

VIndex

=

size_t
;

using

EIndex

=

size_t
;

struct

Edge

{


// 指向的同起点的边


EIndex

next
;


// 指向的顶点


VIndex

to
;


// 权


int

weight
;

}

edges
[
N
];

EIndex

lesfh
[
N
];

// 自行初始为 -1

size_t

edge_now

=

-
1
;

// VIndex 代表顶点索引, EIndex 代表边索引

void

add_edge
(
VIndex

from
,

VIndex

to
,

int

weight
)

{


edge_now

+=

1
;


edges
[
edge_count
].
to

=

to
;


edges
[
edge_count
].
next

=

lesfh
[
from
];


lesfh
[
from
]

=

edge_count
;

}

若欲查两点A,B之间距离,遍历的A的出边,找到终点为B的边即可。


int

get_weight
(
VIndex

from
,

VIndex

to
)

{


// 遍历from的出边


for

(


EIndex

edge_index

=

lesfh
[
from
];

// 最后以from点作起点的边


edge_index

!=

-
1

;

// 后面没边了


edge_index

=

edges
[
edge_index
].
next

// 链表链接到的下一个同始边


)

{


if

(
edges
[
edge_index
].
to

==

to
)

{

// 此边终点是查询终点


return

edges
[
edge_index
].
weight
;


}


}


return

INFINITY
;


}

还不懂问我。