栗子
先来看一个实际案例来引出拓扑排序的概念。大学四年有很多课程,假设这些课程表示为C1,C2,…,C12,一共12门课程如下表:
而这些课程的学习顺序是有限制的,比如在学习《数据结构》C3之前必须先学习《程序设计基础》C1和《离散数学》C2,学习《操作系统》C8前必须先学习《数据结构》C3和《计算机组成原理》C6。而学习《程序设计基础课》C1,和《高等数学》C9不需要其他课程作为基础。
这样我们可以用顶点表示课程,有向边表示先决条件。若课程i是课程j的先决条件则图中有弧<i,j>,如下图
这种顶点表示活动,弧表示活动间优先关系的有向图称为顶点表示活动的网,简称 AOV-网(Activity On Vertex network)。AOV网中不应该出现有向环,因为存在意味着某项顶点活动应该以自己作为先决条件。显然这是荒谬的!在上面给定课程以及课程的先选条件后,一个问题是,我们应该如何将所有课程进行排序,使得在学习一门课程时,它的先选课必然已经学过,这就是拓扑排序的内容!
————————————————————————————————————————————
拓扑排序
设有向图G=(V,{A}),G 的拓扑序列是指V中所有顶点的线性序列,该序列满足如下条件:若G中从顶点Vi到顶点Vj有一条路径,则序列中Vi必在Vj之前。构造有向图的拓扑序列的过程称作拓扑排序。通过上面的例子来说就是《数据结构》C3有先选课《程序设计基础》C1和《离散数学》C2,那么拓扑排序后的线性序列中必有C1,C2在C3之前!
拓扑排序的方法:
1.从有向图中选取一个没有前驱的顶点(入度为0),并输出之;
2. 从有向图中删去此顶点以及所有以它为尾的弧;
3. 重复上述两步,直至全部顶点均已输出,或者图中找不到无前驱的顶点为止。
————————————————————————————————————————
对于任意的有向图,拓扑排序不一定成功,如果有向图含有环,则不能得到其拓扑序列。
例如,对于下列有向图,如果对其进行拓扑排序,在输出A之后,会因为图中没有入度为0的点而结束,此时拓扑排序只有1个点,所以对于一个图进行拓扑排序,如果拓扑排序结果中的点小于图中点的个数,那么这个图就是有环图!所以拓扑排序可以用来检测一个有向图中是否有环。
不能求得它的拓扑序列。因为图中存在回路 ( B, C, D )
拓扑排序过程:
假设有如下的有向图,可以看到点a,b没有前驱(入度为0),假设我们选择a作为第一个点(此时选择点b作为第一个点也是可行的,所以拓扑排序不唯一)
选择a后,删除以a为尾的弧(出去的边),此时点b,c入度为0,假设下一步选择点b
删除以b为尾的弧,此时点c,g,h入度为0,假设下一步选择点h
删除以h为尾的弧,此时点c,g入度为0,假设下一个点选择c
删除以c为尾的弧,此时只有点d的入度为0,下一步选择点d
删除以d为尾的弧,此时只有点g的入度为0,下一步选择点g
删除以g为尾的弧,此时只有f的入度为0,下一步选择f
删除以f为顶点的弧,此时只有e的入度为0,下一步选择e,最后得到了拓扑排序a,b,h,c,d,g,f,e
以上就是拓扑排序的过程
代码
vector<int> topologicla_sort(vector<vector<int> > &map){//map为邻接矩阵存储的图
priority_queue<int, vector<int>, greater<int> > q;//大顶堆,实现从小到大排序(拓扑排序不唯一,使用优先队列保证在有多个可以选择的节点时优先选择较小的节点)
vector<int> result;//保存拓扑排序的结果
for (int i = 1; i <= N;i++){//将所有入度为0的顶点入队
if(degree[i]==0){
q.push(i);
}
}
while(!q.empty()){
int point = q.top();
q.pop();
result.push_back(point);
for (int i = 1; i <= N;i++){//删除以point出去的边
if(map[point][i]){
if(--degree[i] == 0){//如果有弧<point, i>则将i的入度减少1,并且如果入度为0,加入队列
q.push(i);
}
map[point][i] = 0;//删除该条边
}
}
}
return result;
}