拓扑排序,其实就是根据一定的优先级信息,将信息在图这种抽象出来的数据结构中集成,最后生成的顺序。
下面来看一道模板题,来自洛谷的B3644。
拓扑排序是dfs和bfs在图上的经典应用,本文以笔记的形式,通过bfs来实现拓扑排序。
在实现拓扑排序之前,我们需要知道几个概念,即图节点的入度和出度。
如上图,节点1的入度为3,出度为0,节点2的入度为0,出度为2,,很显然,在有向图中,入度为零的节点优先级最高,排在最首,出度为0的节点优先级最低,排在最尾(2>1),方向是从优先级高指向优先级低(或者反过来也可以),当然,如果将所给的信息放在有向图中后,发现出现了环,那么不存在优先级最高(最低)的节点,也就不能进行排序,也就是说,拓扑排序只能在有向无环图中进行。(如下图)
使用bfs实现拓扑排序,可以从入度考虑,也可以从出度考虑,分别对应两种原理:
1、无前驱的节点优先。入度为零的节点优先级最高,记录,删除其与相邻节点的边,再找入度为零的节点(它在剩下的节点中优先级最高),记录,直到找完所有节点,如果遇到多个入度为零的节点,则它们之间的顺序不确定,造成了拓扑排序的不确定性(多解)。
2、无后继的节点优先。按出度思考同理,每次找优先级最低的节点。
有没有感觉到这其实类似于选择排序(堆排序)的思想?
下面是C/C++代码实现:
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10;
//#define int long long
//基于广度优先搜索实现的拓扑排序
int n,h[N],ans[N],k;
queue<int> q;
struct edge
{
int from,to;
edge(int a,int b){from=a;to=b;}
};
vector<edge> a[N]; // 邻接表存图
void init()
{
for(int i=1;i<=n;i++) a[i].clear();
return;
}
signed main()
{
scanf("%d",&n);
init();
for(int i=1;i<=n;i++)
{
int re;
while((scanf("%d",&re),re))
{
a[i].push_back(edge(i,re));
h[re]++; // 保存入度
}
} // 读入图
for(int i=1;i<=n;i++) if(!h[i]) q.push(i);
while(!q.empty())
{
int b=q.front();
q.pop();
ans[k++]=b;
for(int i=0;i<(int)a[b].size();i++) //遍历所有邻居
{
h[a[b][i].to]--; //邻居入度减一
if(!h[a[b][i].to]) q.push(a[b][i].to); // 入度减为零的入队
}
}
for(int i=0;i<k;i++) printf("%d ",ans[i]);
return 0;
}