拓扑排序基本概念
在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
- 每个顶点出现且只出现一次。
- 若存在一条从顶点 A A A 到顶点 B B B 的路径,那么在序列中顶点 A A A 出现在顶点 B B B 的前面。
有向无环图(DAG)才有拓扑排序,非 DAG 图没有拓扑排序一说。
例如,下面这个图,
它是一个 DAG 图,那么如何写出它的拓扑排序呢?这里说一种比较常用的方法:
- 从 DAG 图中选择一个 没有前驱(即入度为 0 0 0)的顶点并输出。
- 从图中删除该顶点和所有以它为起点的有向边。
重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
于是,得到拓扑排序后的结果是
{
a
,
c
,
b
,
f
,
d
,
e
}
\{a, c, b, f, d,e\}
{a,c,b,f,d,e}。
通常,一个有向无环图可以有一个或多个拓扑排序序列。
拓扑排序实现
关键是要维护一个入度为
0
0
0 的顶点的集合。
Step 1:将所有入度为
0
0
0 的顶点入队。
Step 2:遍历队列,获取队头的顶点
u
u
u,打印输出
u
u
u。
Step 3:枚举
u
u
u 的每一条出边
v
v
v,删除
u
→
v
u \to v
u→v 的边,就是将 in[v] 减
1
1
1。
Step 4:如果当前
v
v
v 的入度为
0
0
0,将
v
v
v 加入队列。
Step 5:重复 Step 2,3,4,直到队列为空为止。
数据结构
稠密图可以使用邻接矩阵。稀疏图可以使用邻接表。
大部分情况下,我们使用邻接表。
const int N=1e5+10;
LL h[N];
LL in[N];//顶点入度
const int M=1e6+10;
LL e[M], ne[M], idx;
//队列相关
LL que[M];
LL hh=0;
LL tt=-1;
LL n;//保存顶点数量
初始化
memset(h, -1, sizeof h);
插入边
void add(LL a, LL b) {
//a -> b
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
//入度加
in[b]++;
}
拓扑排序
bool top_sort() {
//初始化队列
hh=0; tt=-1;
//将所有入度为零的加入队列
for (LL i=1; i<=n; i++) {
if (in[i]==0) {
que[++tt]=i;//入队
}
}
while (hh<=tt) {
LL u=que[hh];//获取队头
hh++;//出队
//遍历u的所有出边
for (LL i=h[u]; i!=-1; i=ne[i]) {
LL v=e[i];
in[v]--;//出度减
if (in[v]==0) {
que[++tt]=v;
}
}
}
//判断是会否是 DAG,
return tt==n-1;
}
注意,出队的顺序就是一个拓扑序列。也就是说 que 里面的数据就是一个拓扑排序。如果需要输出对应的拓扑排序,从
0
0
0 到
n
−
1
n-1
n−1 就是一个拓扑序。
另外要注意,拓扑序不是唯一的。