拓扑排序
定义
拓扑排序要解决的问题是如何给一个有向无环图的所有节点排序 拓扑排序要解决的问题是如何给一个有向无环图的所有节点排序 拓扑排序要解决的问题是如何给一个有向无环图的所有节点排序
我们可以拿大学每学期排课的例子来描述这个过程
我们可以拿大学每学期排课的例子来描述这个过程
我们可以拿大学每学期排课的例子来描述这个过程
比如学习大学课程中有「程序设计」「算法语言」「高等数学」「离散数学」「编译技术」「普通物理」
比如学习大学课程中有「程序设计」「算法语言」「高等数学」「离散数学」「编译技术」「普通物理」
比如学习大学课程中有「程序设计」「算法语言」「高等数学」「离散数学」「编译技术」「普通物理」
「数据结构」
,
「数据库系统」等。按照例子中的排课,当我们想要学习「数据结构」的时候
「数据结构」,「数据库系统」等。按照例子中的排课,当我们想要学习「数据结构」的时候
「数据结构」,「数据库系统」等。按照例子中的排课,当我们想要学习「数据结构」的时候
就必须先学会「离散数学」
,
学习完这门课后就获得了学习「编译技术」的前置条件
就必须先学会「离散数学」,学习完这门课后就获得了学习「编译技术」的前置条件
就必须先学会「离散数学」,学习完这门课后就获得了学习「编译技术」的前置条件
当然,「编译技术」还有一个更加前的课程「算法语言」
当然,「编译技术」还有一个更加前的课程「算法语言」
当然,「编译技术」还有一个更加前的课程「算法语言」
这些课程就相当于几个顶点
u
,
顶点之间的有向边
(
u
,
v
)
就相当于学习课程的顺序
这些课程就相当于几个顶点u,顶点之间的有向边 (u,v) 就相当于学习课程的顺序
这些课程就相当于几个顶点u,顶点之间的有向边(u,v)就相当于学习课程的顺序
教务处安排这些课程
,
使得在逻辑关系符合的情况下排出课表
教务处安排这些课程,使得在逻辑关系符合的情况下排出课表
教务处安排这些课程,使得在逻辑关系符合的情况下排出课表
就是拓扑排序的过程
就是拓扑排序的过程
就是拓扑排序的过程
但是如果某一天排课的老师打瞌睡了,说想要学习数据结构,还得先学操作系统
但是如果某一天排课的老师打瞌睡了,说想要学习 数据结构,还得先学 操作系统
但是如果某一天排课的老师打瞌睡了,说想要学习数据结构,还得先学操作系统
而操作系统的前置课程又是数据结构,那么到底应该先学哪一个
(
不考虑同时学习的情况
)
?
而 操作系统 的前置课程又是 数据结构,那么到底应该先学哪一个(不考虑同时学习的情况)?
而操作系统的前置课程又是数据结构,那么到底应该先学哪一个(不考虑同时学习的情况)?
在这里,数据结构和操作系统间就出现了一个环
在这里,数据结构 和 操作系统 间就出现了一个环
在这里,数据结构和操作系统间就出现了一个环
显然同学们现在没办法弄清楚自己需要先学什么了
,
也就没办法进行拓扑排序了
显然同学们现在没办法弄清楚自己需要先学什么了,也就没办法进行拓扑排序了
显然同学们现在没办法弄清楚自己需要先学什么了,也就没办法进行拓扑排序了
因为如果有向图中存在环路
,
那么我们就没办法进行拓扑排序
因为如果有向图中存在环路,那么我们就没办法进行拓扑排序
因为如果有向图中存在环路,那么我们就没办法进行拓扑排序
因此我们可以说在一个
D
A
G
(
有向无环图
)
中
,
我们将图中的顶点以线性方式进行排序
因此我们可以说在一个DAG(有向无环图)中,我们将图中的顶点以线性方式进行排序
因此我们可以说在一个DAG(有向无环图)中,我们将图中的顶点以线性方式进行排序
使得对于任何的顶点
u
到
v
的有向边
(
u
,
v
)
,
都可以有
u
在
v
的前面
使得对于任何的顶点u到v的有向边(u,v),都可以有u在v的前面
使得对于任何的顶点u到v的有向边(u,v),都可以有u在v的前面
还有给定一个
D
A
G
,
如果从
i
到
j
有边
,
则认为
j
依赖于
i
还有给定一个DAG,如果从 i 到 j 有边,则认为j依赖于i
还有给定一个DAG,如果从i到j有边,则认为j依赖于i
如果
i
到
j
有路径
(
i
可达
j
)
,
则称
j
间接依赖于
i
如果i到j有路径(i可达j),则称 j 间接依赖于i
如果i到j有路径(i可达j),则称j间接依赖于i
拓扑排序的目标是将所有节点排序 , 使得排在前面的节点不能依赖于排在后面的节点 拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点 拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点
特点
拓扑排序是不唯一的 拓扑排序是不唯一的 拓扑排序是不唯一的
举例 : 如定义的图 举例:如定义的图 举例:如定义的图
入度为 0 的节点有高等数学和程序设计,我先排高等数学和先排程序设计都是等价的 入度为0的节点有高等数学和程序设计,我先排高等数学和先排程序设计都是等价的 入度为0的节点有高等数学和程序设计,我先排高等数学和先排程序设计都是等价的
算法具体过程
先建图 , 在建图的过程中统计每个点的入度 , 我们用队列去进行拓扑排序 先建图,在建图的过程中统计每个点的入度,我们用队列去进行拓扑排序 先建图,在建图的过程中统计每个点的入度,我们用队列去进行拓扑排序
先扫一遍点,找出入度为 0 的点 ( 拓扑排序必有入读为 0 的点 ) , 然后放入队列 先扫一遍点,找出入度为0的点(拓扑排序必有入读为0的点), 然后放入队列 先扫一遍点,找出入度为0的点(拓扑排序必有入读为0的点),然后放入队列
然后进行 b f s , 先取出队列首端 , 然后遍历子节点 , 把子节点的入度 − 1 , 当子节点入度为 0 的时候放入队列 然后进行bfs,先取出队列首端,然后遍历子节点,把子节点的入度-1,当子节点入度为0的时候放入队列 然后进行bfs,先取出队列首端,然后遍历子节点,把子节点的入度−1,当子节点入度为0的时候放入队列
对于统计拓扑序我们可以用队列 , 数组 , 或者 v e c t o r 在队列首端弹出的时候存起来 对于统计拓扑序我们可以用队列,数组,或者vector在队列首端弹出的时候存起来 对于统计拓扑序我们可以用队列,数组,或者vector在队列首端弹出的时候存起来
int n,m;
int d[N],len[N],f[N];
vector<int>a[N];
queue<int>q;
queue<int>p;
void topsort(){
for(int i = 1;i <= n;i++){
if(!d[i]){
q.push(i);
}
}
while(!q.empty()){
int t = q.front();
q.pop();
p.push(t);
for(auto j : a[t]){
d[j]--;
if(d[j] == 0){
q.push(j);
}
}
}
return;
}
应用
1 : 用于解决依赖性的排序问题 1: 用于解决依赖性的排序问题 1:用于解决依赖性的排序问题
2 : 拓扑排序判环 , 记录队列取出首端的次数,如果不为总节点数则存在环 2: 拓扑排序判环,记录队列取出首端的次数,如果不为总节点数则存在环 2:拓扑排序判环,记录队列取出首端的次数,如果不为总节点数则存在环
例题
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+10;
int n,m;
int d[N],len[N],f[N];
vector<int>a[N];
//如果题目有对节点大小有限制,用优先队列priority_queue<int>q或者priority_queue<int,vector<int>,greater<int>>q
queue<int>q;
queue<int>p;
void topsort(){
for(int i = 1;i <= n;i++){
if(!d[i]){
q.push(i);
}
}
while(!q.empty()){
int t = q.front();
q.pop();
p.push(t);
for(auto j : a[t]){
d[j]--;
if(d[j] == 0){
q.push(j);
}
}
}
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for(int i = 1;i <= n;i++){
int u;
while(cin >> u && u){
a[i].push_back(u);
d[u]++;
}
}
topsort();
while(!p.empty()){
cout << p.front() << " ";
p.pop();
}
return 0;
}
题单
序号 | 题号 | 标题 | 题型 | 难度评级 | 题解 |
---|---|---|---|---|---|
1 | luogu B3644 | [模板]拓扑排序 / 家谱树 | 正常拓扑排序 | ⭐⭐ | 👍 |
2 | HDU-1285 | 确定比赛名次 | 优先队列拓扑排序 | ⭐⭐ | 👍 |
3 | HDU-4857 | 逃生 | 反向建图+优先队列 | ⭐⭐⭐ | 👍 |
4 | luogu P1347 | 排序 | 拓扑排序判环 | ⭐⭐⭐ | 👍 |
5 | luogu P3243 | 菜肴制作 | 反向建图+优先队列 | ⭐⭐⭐ | 👍 |
6 | luogu P1113 | 杂物 | 拓扑思想应用 | ⭐⭐⭐ | 👍 |
7 | luogu P1685 | 游览 | 拓扑思想应用 | ⭐⭐⭐ | 👍 |
8 | luogu P1983 | 车站分级 | 拓扑思想应用 | ⭐⭐⭐ | 👍 |
9 | luogu P1038 | 神经网络 | 拓扑思想应用 | ⭐⭐⭐ | 👍 |
10 | luogu P4017 | 最大食物链计数 | 拓扑思想应用 | ⭐⭐⭐ | 👍 |
11 | Codeforces 1931F | Chat Screenshots | 拓扑排序判环 | ⭐⭐ | 👍 |
12 | luogu P1954 | 航空管制 | 拓扑思想+优先队列+反向建图 | ⭐⭐⭐ ⭐ | 👍 |