拓扑排序
拓扑排序的概念
拓扑排序(Topological sorting)要解决的问题是如何给一个有向无环图的所有节点排序。因此我们可以说 在一个 DAG
(有向无环图) 中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 \(u\) 到 \(v\) 的有向边 \((u,v)\), 都有 \(u\) 在 \(v\) 的前面。给定一个 DAG
,如果从 \(i\) 到 \(j\) 有边,则认为 \(j\) 依赖于 \(i\)。如果 \(i\) 到 \(j\) 有路径(\(i\) 可达 \(j\)),则称 \(j\) 间接依赖于 \(i\)。
\({ \color{red}\text{拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。}}\)
拓扑排序的 dfs
实现
DFS 总是沿着一条路搜索到无出度的节点,然后逐层回退,因此 DFS 适合拓扑排序,并且我们可以在回退的过程中保存下拓扑排序的逆序。
从所有零入度的节点开始,就能到达所有点;也可以从未被遍历到的节点开始。
\({\color{red}\text{注意:要仔细看定义。}}\)
cpp
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
vector< int > G[N];
// 1代表正在访问,-1代表访问结束,0代表未访问
int vis[N];
int ans[N], tot;
int innode[N];
bool dfs_topsort(int u) // 从节点u出发
{
vis[u] = 1; // 正在访问
for (int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
// 如果后继比前驱先访问,说明存在有向环
if (vis[v] == 1)
{
return false;
}
// 如果后继未被访问
if (!vis[v])
{
// 访问后继返回假,也是失败
if (!dfs_topsort(v))
{
return false;
}
}
}
vis[u] = -1;
// 在递归结束才加入拓扑序列数组中,
// 最深层次先返回
ans[tot++] = u;
return true;
}
int main()
{
int n, m, a, b;
cin >> n >> m;
while (m--)
{
cin >> a >> b;
G[a].push_back(b); // 邻接数组存图
innode[b]++; // 纪录入度
}
for (int i = 1; i <= n; i++)
{
if (innode[i] == 0)
{
dfs_topsort(i); // 拓扑排序
}
}
for (int i = tot - 1; i >= 0; i--)
{
cout << ans[i] << " "; // 输出答案
}
return 0;
}
拓扑排序的 bfs
实现
-
在有向图中选一个无前驱的顶点并且输出。
-
从图中删除所有和它有关的边。
-
重复上述两步,直至所有顶点输出。如果还有顶点未输出,则说明有环。
cpp
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
queue< int > q;
vector< int > G[N];
vector< int > ans; // 存放拓扑序列
int in[N];
bool bfs_topsort(int n) // n为节点个数
{
for (int i = 1; i <= n; i++)
{
if (in[i] == 0)
{
q.push(i); // 将入度为0的点入队列
}
}
while (!q.empty())
{
int u = q.front();
q.pop(); // 选一个入度为 0 的点,出队
ans.push_back(u);
for (int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
in[v]--;
if (in[v] == 0)
{
// 当某一条边的入度为0,入队
q.push(v);
}
}
}
if (ans.size() == n)
{
for (int i = 0; i < ans.size(); i++)
{
// 输出答案
cout << ans[i] << " ";
}
return true;
}
return false; // 图内还有剩余节点,存在环路
}
int main()
{
int n;
int m;
int a;
int b;
cin >> n >> m;
while (m--)
{
cin >> a >> b;
G[a].push_back(b); // 邻接数组存图
in[b]++; // 纪录入度
}
if (!bfs_topsort(n))
{
cout << "No answer";
}
cout << endl;
return 0;
}
注意:如果需要输出和字典序相关的拓扑序列,那么只要把队列替换为优先队列即可。