拓扑排序详解-----看这一篇就够了
拓扑排序(Topological Sorting)是针对有向无环图(DAG, Directed Acyclic Graph)的一种排序方法。其目的是将图中的所有顶点排成一个线性序列,使得对于图中的每一条有向边u->v,顶点u在v之前。拓扑排序常用于解决任务调度、编译顺序确定、依赖关系解析等问题。其本质是寻找一种可行的线性排序,使得满足依赖关系的任务得以按顺序执行。拓扑排序的前提是图必须是有向无环图
1. 定义
拓扑排序(Topological Sorting)是针对有向无环图(DAG, Directed Acyclic Graph)的一种排序方法。其目的是将图中的所有顶点排成一个线性序列,使得对于图中的每一条有向边 u->v
,顶点 u
在 v
之前。
2. 概述
拓扑排序常用于解决任务调度、编译顺序确定、依赖关系解析等问题。其本质是寻找一种可行的线性排序,使得满足依赖关系的任务得以按顺序执行。
拓扑排序的前提是图必须是 有向无环图(DAG),否则无法进行拓扑排序。常见的拓扑排序方法包括 入度法(Kahn算法) 和 深度优先搜索(DFS)法。
3. 算法思想
3.1 入度法(Kahn算法)
-
统计所有顶点的入度。
-
将所有入度为
0
的顶点加入队列。 -
依次从队列中取出顶点
u
,将其加入拓扑序列,并移除u
指向的所有边。 -
若某个顶点的入度变为
0
,将其加入队列。 -
重复上述过程,直到队列为空。
-
若最终拓扑序列中的顶点数等于图中顶点数,则排序成功,否则说明存在环。
3.2 深度优先搜索(DFS)法
-
维护一个访问标记数组
vis
,其中0
表示未访问,1
表示正在访问,2
表示已访问。 -
遍历所有未访问的顶点,执行 DFS 过程。
-
在 DFS 过程中,若访问到正在访问的顶点,则说明存在环。
-
若 DFS 递归回溯,则将当前顶点加入拓扑序列。
-
逆序输出拓扑序列。
4. 举个栗子🌰
假设我们有一个更复杂的有向无环图,包含 7 个任务(顶点)和如下的依赖关系:
1 -> 2
1 -> 3
2 -> 4
2 -> 5
3 -> 5
3 -> 6
4 -> 7
5 -> 7
6 -> 7
其对应的图结构如下:
1
/ \
2 3
/ \ / \
4 5 6
\ | /
7
4.1模拟(kahn)
初始化 in
数组(入度):
in: [0,0,1,1,2,1,2,3]
处理步骤:
-
取出入度为
0
的节点1
,移除其边1->2
和1->3
。-
in
:[0,0,0,1,2,1,2,3]
-
队列:
[1]
-
结果:
1
-
-
取出
2
,移除2->4
和2->5
。-
in
:[0,0,0,0,1,0,2,3]
-
队列:
[1,2]
-
结果:
1 2
-
-
取出
3
,移除3->5
和3->6
。-
in
:[0,0,0,0,1,0,1,3]
-
队列:
[1,2,3]
-
结果:
1 2 3
-
-
取出
4
,移除4->7
。-
in
:[0,0,0,0,0,0,1,2]
-
队列:
[1,2,3,4]
-
结果:
1 2 3 4
-
-
取出
5
,移除5->7
。-
in
:[0,0,0,0,0,0,1,1]
-
队列:
[1,2,3,4,5]
-
结果:
1 2 3 4 5
-
-
取出
6
,移除6->7
。-
in
:[0,0,0,0,0,0,0,0]
-
队列:
[1,2,3,4,5,6]
-
结果:
1 2 3 4 5 6
-
-
取出
7
。-
队列:
[1,2,3,4,5,6,7]
-
结果:
1 2 3 4 5 6 7
-
最终拓扑序列:1 2 3 4 5 6 7
5. 代码实现
5.1 入度法(Kahn算法)
#include<iostream>
#include<queue>
using namespace std;
int n=7,m=9,in[10],tot,a[10][10];
queue<int>q;
void topo_sort()
{
for(int i=1;i<=n;i++)if(!in[i])q.push(i);
while(!q.empty())
{
int u=q.front();q.pop();
cout<<u<<" ";
for(int v=1;v<=n;v++)
{
if(a[u][v])
{
in[v]--;
if(!in[v])q.push(v);
}
}
}
}
int main()
{
int edges[9][2]={{1,2},{1,3},{2,4},{2,5},{3,5},{3,6},{4,7},{5,7},{6,7}};
for(int i=0;i<m;i++)
{
int u=edges[i][0],v=edges[i][1];
a[u][v]=1;
in[v]++;
}
topo_sort();
return 0;
}
5.2 深度优先搜索(DFS)法
#include<iostream>
using namespace std;
int n=7,m=9,a[10][10],vis[10],order[10],tot;
void dfs(int u)
{
vis[u]=1;
for(int v=1;v<=n;v++)
{
if(a[u][v]&&!vis[v])dfs(v);
}
order[++tot]=u;
}
void topo_sort()
{
for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
for(int i=tot;i>=1;i--)cout<<order[i]<<" ";
}
int main()
{
int edges[9][2]={{1,2},{1,3},{2,4},{2,5},{3,5},{3,6},{4,7},{5,7},{6,7}};
for(int i=0;i<m;i++)
{
int u=edges[i][0],v=edges[i][1];
a[u][v]=1;
}
topo_sort();
return 0;
}
6. 注意事项
-
DAG 限制:拓扑排序仅适用于 有向无环图(DAG),如果图中存在环,拓扑排序无法进行。
-
唯一性:若某个图存在多个拓扑序列,则说明拓扑排序结果可能不唯一。
-
检测环:在
DFS
版本中,若访问到已经在递归栈中的节点,则说明存在环。 -
入度法 vs DFS:
-
入度法适合 宽度优先遍历(BFS),较为直观,适用于大多数场景。
-
DFS 方法适合 深度优先遍历(DFS),可用于检测环,但实现较为复杂。
-
7. 结论
拓扑排序是一种重要的图算法,在解决任务调度、依赖管理等问题中非常有用。掌握 入度法(Kahn算法) 和 深度优先搜索(DFS)法 这两种方式,能更好地应对不同的实际应用场景。
更多推荐
所有评论(0)