拓扑排序

图论算法の拓扑排序

拓扑序定义

对于一个$DAG$(有向无环图)$G$,它的拓扑序是该图所有节点的一个线性序列,该序列必须满足使得图中的任意一对顶点$u$和$v$,若边$ (u,v) \in E(G) $,则$u$在线性序列中出现在$v$之前

算法流程

对于一个$DAG$:

  1. 将所有入度为0的节点加入队列中
  2. 每次将队首元素(节点)出队并将其所有的出边砍掉(即 把该出边所连节点的入度减一),这样以后如果出现了新的入度为0的节点,那么把他们加入队列,重复该步骤,直到队列为空
  3. 每次出队的队首元素的顺序就是该图的一种拓扑序(答案不唯一,因为可能会同时存在好几个入度为0的节点,他们互不影响)

举个例子,如下一张图

我们首先将入度为0的点入队(1、4),此时拓扑序还未构建

image.png

然后删去他们的出边,并判断出边所连节点是否入度为0

此时拓扑序为:1 4

image.png

发现2、5节点的入度变为0了,于是入队,重复上一步操作

image.png

此时拓扑序为:1 4 2 5

删去2 5节点的出边后,发现3 6节点入度变为0,于是入队

image.png

取出3 6之后,拓扑序为:1 4 2 5 3 6

这时发现7入度为0,于是入队

image.png

最后结束,拓扑排序最终结果为:1 4 2 5 3 6 7(答案不止这一种,可以很容易算出此题答案有8种)

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<bits/stdc++.h>
using namespace std;
struct Edge{ //边结构体
int u,v,next;
}e[100005];

int n,m,cnt,head[100005],indeg[100005];
inline void addedge(int u,int v){ //链式前向星加边
e[++cnt].u=u;
e[cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
int ans[100005],ccnt;
inline void topo_sort(){ //拓扑排序
queue<int> q;
for(int i=1;i<=n;i++){ //将所有入度为0的点入队
if(indeg[i]==0){
q.push(i);
}
}
while(!q.empty()){
int u=q.front();
q.pop();
ans[++ccnt]=u; //记录拓扑序
for(int i=head[u];i;i=e[i].next){ //遍历所有出边
int v=e[i].v;
if(indeg[v]){ //如果入度不为零
indeg[v]--; // 出边所连节点入度-1
if(indeg[v]==0){ //如果出现了入度=0
q.push(v); //加入队列
}
}
}
}
}

int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d %d",&u,&v);
addedge(u,v);
indeg[v]++; //记录所有节点的入度
}
topo_sort();
for(int i=1;i<=ccnt;i++){ //output答案
cout<<ans[i]<<" ";
}
return 0;
}

习题:Vijos P1790 拓扑编号

题意

  • 给一个DAG图重新编号并排序,使得对于该序列中任意的两点$i$$j$且$i<j$,满足新编号$a[i]<a[j]$

题解

  • 考虑反向建图做一个逆拓扑排序,并且使用优先队列维护每次出队的都是最大的编号的节点,同时逆向编号即可得出答案

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<bits/stdc++.h>
using namespace std;

struct Edge{
int u,v,next;
}e[1000005];
int n,m,cnt,head[1000005],indeg[1000005];
inline void addedge(int u,int v){
e[++cnt].u=u;
e[cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
int ans[1000005];
inline void tpsort(){
priority_queue<int> q; //维护优先队列
for(int i=1;i<=n;i++){
if(indeg[i]==0){
q.push(i);
}
}
int num=n;
while(!q.empty()){
int u=q.top();
q.pop();
ans[u]=num--; //倒着编号
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;
if(indeg[v]){
indeg[v]--;
if(indeg[v]==0){
q.push(v);
}
}
}
}

}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d %d",&u,&v);
addedge(v,u);
indeg[u]++;
}
tpsort();
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}
return 0;

} //https://vijos.org/p/1790

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!