定义 链接到标题
拓扑排序(Topological sorting)要解决的问题是给一个有向图的所有节点排序。
这里直接使用OI-Wiki中举的例子来说明:
我们可以拿大学选课的例子来描述这个过程,比如学习大学课程中有:单变量微积分,线性代数,离散数学概述,概率论与统计学概述,语言基础,算法导论,机器学习。当我们想要学习 算法导论 的时候,就必须先学会 离散数学概述 和 概率论与统计学概述,不然在课堂就会听的一脸懵逼。当然还有一个更加前的课程 单变量微积分。
这些课程就相当于几个顶点$u$, 顶点之间的有向边$(u,v)$就相当于学习课程的顺序。显然拓扑排序不是那么的麻烦,不然你是如何选出合适的学习顺序。下面将介绍如何将这个过程抽象出来,用算法来实现。
但是如果某一天排课的老师打瞌睡了,说想要学习 算法导论,还得先学 机器学习,而 机器学习 的前置课程又是 算法导论,然后你就一万脸懵逼了,我到底应该先学哪一个?当然我们在这里不考虑什么同时学几个课程的情况。在这里,算法导论 和 机器学习 间就出现了一个环,显然你现在没办法弄清楚你需要学什么了,于是你也没办法进行拓扑排序了。因而如果有向图中存在环路,那么我们就没办法进行拓扑排序了。
因此我们可以说在一个[[DAG(有向无环图)]]中,我们将图中顶点以线性方式排序,使得对于任意顶点$u$到$v$的有向边$(u, v)$,都有$u$在$v$的前面。
或者说给定一个DAG,如果$i$到$j$有边,则认为$j$依赖于$i$,如果$i$到$j$有路径,则称$j$间接依赖于$i$; 拓扑排序的目标是将所有节点排序,使得在前面的节点不能依赖于排在后面的节点。
bfs 链接到标题
拓扑排序有广度优先搜索(bfs)和深度优先搜索(dfs)两种实现方式,这里我们先讨论bfs。
利用bfs实现拓扑排序需要根据节点的入度:
入度:有多少条边直接指向该节点
思路 链接到标题
- 起始时,将所有入度为$0$的点放入队列
q_in0
; - 将队首元素出队,出队序列就是我们要求的拓扑序,对当前弹出的节点
u
,res.push_back(u)
,遍历u
的所有出度,即遍历所有由u
直接指向的节点v
,递减节点v
的入度; - 如果节点
v
的入度变为0,将节点v
入队; - 循环2、3流程直到队列为空;
如果res
最后恰好有$n$个节点,说明原图为DAG,res
中的节点序列即要求的拓扑序;否则说明图中存在环。
代码实现 链接到标题
vector<vector<int>> graph;
int n = graph.size();
int in[n]; // 存储每个节点的入度
bool toposrot() {
vector<int> res;
queue<int> q_in0;
for (int i = 0; i < n; ++i) {
if (in[i] == 0) {
q_in0.push(i);
}
}
while (!q_in0.empty()) {
int u = q_in0.front();
q_in0.pop();
res.push_back(u);
for (auto v : graph[u]) {
if (--in[v] == 0) {
q_in0.push(v);
}
}
}
if (res.size() == n) {
for (auto i : res) {
cout << i << " ";
}
return true;
}
return false;
}
dfs 链接到标题
todo