void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
这段代码是图论中用邻接表存储图的标准实现方式,尤其常见于竞争性编程中,因为它效率高且内存占用相对紧凑。要快速记住它,你需要理解它背后的逻辑和它所代表的数据结构。
核心思想:使用数组模拟链表来构建邻接表。
变量解释:
h[N]
: 头节点数组。h[a]
存储的是以节点a
为起点的最近一次添加的边的索引(在e
和ne
数组中的位置)。初始时,所有h[a]
通常被设置为 -1 或一个特殊值表示链表为空。e[M]
: 存储边的目标节点数组。e[i]
存储的是索引为i
的边的终点。w[M]
: 存储边的权重或属性数组。w[i]
存储的是索引为i
的边的权重(如果图是带权图)。ne[M]
: 存储“下一条边”的索引数组。ne[i]
存储的是在以某个节点为起点的链表中,索引为i
的边的下一条边的索引。这模拟了链表的next
指针。idx
: 全局索引,表示下一条可用的边存储位置。每次添加一条新边,idx
就递增。
代码逻辑分解 (记住这一步一步的操作):
函数 add(int a, int b, int c)
的作用是添加一条从节点 a
到节点 b
,权值为 c
的有向边。
e[idx] = b;
: 将这条新边的终点b
存储到e
数组的当前可用位置idx
上。w[idx] = c;
: 将这条新边的权重c
存储到w
数组的当前可用位置idx
上。ne[idx] = h[a];
: 这是最关键的一步! 将这条新边的“下一条边”指针ne[idx]
指向原来以节点a
为起点的第一条边(因为h[a]
原来存储的就是第一条边的索引)。这相当于把新边插入到了节点a
的链表的头部。h[a] = idx;
: 更新节点a
的头指针h[a]
,让它指向刚刚添加的这条新边(其索引为idx
)。现在,这条新边成为了节点a
的链表的新头部。idx ++ ;
: 准备好下一个位置,以便下次添加新边。
快速记忆方法:
- 理解数据结构: 把它想象成给节点
a
的“边列表”的头部添加一个新元素。这个列表是用h
作为头指针,ne
作为链表的next
指针,边的数据 (e
,w
) 存储在e
和w
数组中。 - 记住操作顺序 (头插法):
- 存数据: 先把新边的数据(终点
b
和权重c
)存好 (e[idx] = b, w[idx] = c;
)。 - 连旧头: 让新边指向原来的第一条边 (
ne[idx] = h[a];
)。 - 成新头: 更新头指针,让它指向新边 (
h[a] = idx;
)。 - 下个位置:
idx
准备好给下一条边用 (idx++;
)。
- 存数据: 先把新边的数据(终点
- 关键词联想:
add(a, b, c)
-> 添加一条a
到b
,权值c
的边。h[a]
-> 节点a
的边的“头”。ne[idx]
-> 当前边idx
的“下一条”。e[idx]
,w[idx]
-> 当前边idx
的“数据”。- 操作顺序:
存数据 -> 指向旧头 -> 更新头 -> idx++
。
通过理解它代表的是数组模拟链表的“头插法”操作,并将代码分解为“存数据”、“链旧头”、“成新头”、“准备下一个”这几个步骤,就可以相对快速地记住这段代码。多看几次,或者自己手动模拟几次添加边的过程,加深理解。