什么是拓扑排序
拓扑排序是一种针对“有向无环图”的算法,用于解决一些有“依赖关系”的问题,比如说如下问题:
一场跑步比赛结束了,每个人都有一个编号,都还不知道自己的排名,但是每个人都拿到了一张卡片,上面写着自己前面一名参赛者的编号,现在小明想要收集这些卡片然后得出完整的比赛排名。
其实思路就是看看每个数有不有人排在他前面,如果没有那么就是当下的第一名,在拓扑排序中呢就是叫做一个点的入度。
拓扑排序保证了当处理到某个点时,其所有的入点都已经处理过了,这就像找跑步的排名,一旦排在某个人前面的人都已经排好了,那么此时他本人就是当下第一,直接加入排好序的序列中,那么排在他后面一名的人的入度就减一。
例如下面这个图
拓扑排序可以保证:
处理点2之前,点4和6都处理过了
处理点3之前,点2和6都处理过了
对应到上面跑步比赛排名的例子里,当2号选手加入排序中时,4号和6号选手已经排号排名了
此外,拓扑排序并不是“唯一”的,比如图中的点1和点7入度都为0,都可以作为起点,就像跑步比赛中两个人同时到达终点,谁排在前面都可以,都满足拓扑关系
比如上面的图就有以下几种可能的拓扑排序:
{1,4,6,2,5,7,3}
{7,1,4,6,2,3,5}
{7,1,6,4,2,5,3}
......
从上面几种可能的排序中我们可以看出每个点的左侧必定包含它的所有入点(提供入度的点)
拓扑排序算法
拓扑排序一般借助queue(队列),使用类似于BFS的思想来实现,先处理出每个点的入度,这个要在读入边的时候处理。
图一般用邻接表建立,比较容易操作,指针动态分配空间太麻烦了,虽然可以节省不少空间
void topo()
{
queue<int> q;//这个是c++的模板,如果不会需要提前去学习
for(int i=1;i<=n;i++) if(!ind[i])q.push(i);//将所有入度为0的点加入队列
while(q.size())//当队列不为空,说明当下有可以作为开头的点,就继续循环
{
int x=q.front();//用一个临时变量来存放队列头元素,即一个一个处理入度为0的点
q.pop();//这个操作千万不能忘记,因为如果不把已经处理好的点弹出会一直处理这个点从而陷入死循环导致超时
for(const auto &y:g[x])//此处循环遍历于x相邻的所有点构成的边
{
//处理边x->y
ind[y]--;//处理之后y的入度减一
if(!ind[y])q.push(y);//如果入度处理之后y的入度也变成了0,那么点y也可以作为一个开头,那就入队列
}
}
}
而对于主函数建立树结构来说
while(m--)
{
int u,v;;cin >> u >> v;//新增一条u->v的边
ind[v]++;//v的入度+1
}