前面介绍了图论算法基础,下面在图论基础上介绍图论算法中的拓扑排序。
简介
拓扑排序是对有向无环图DAG的顶点的一种排序算法,如果在图中存在一条路径从顶点v到顶点w,则在排序中,顶点v在w前面。拓扑排序对带环图无意义,因为无法确定环中顶点的先后顺序。拓扑排序在一些场景下很有用,比如学校排课时,必须保证先修完课程A然后才能修课程B,此时就可以用拓扑排序。
算法实现
顶点v的入度(indegree):指向顶点v的边的条数。
上图中v1的入度为2,v2的入度为0,v3的入度为1,……
1、简单实现
拓扑排序的简单实现:先找出图中任何一个入度为0的顶点,打印该顶点,并将该顶点及其相关的边从图中删除,然后对图的剩下部分重复此操作。最终打印顺序就是拓扑排序结果。
对应伪代码如下:
void topuSort(){
for(int i=0; i<num_vertex; i++){
//寻找图中剩余部分入度为0的顶点
Vertex v = findNewVertexWithZeroIndegree()
//如果没有找到入度为0的顶点,说明图中有环
if(v == null){
throw new Exception("Graph has cycle !")
}
//给顶点设置拓扑排序编号,最后此编码顺序就是拓扑排序结果
v.topuNo = i;
//将与v相邻的所有顶点的入度减1(从图中去掉与v相关的边)
for each Vertex w adjacent to v
w.indegree--;
}
}
findNewVertexWithZeroIndegree()方法对图进行简单遍历寻找入度为0的顶点,每次调用花费O(n)时间(n为图的顶点个数),一共对该方法调用n次,所以上述代码的时间复杂度为O( n 2 n^2 n2),该代码时间复杂度较差,下面我们对其进行优化。
2、算法优化
上面的代码运行时间较差的原因在于,每个顶点的迭代都对图进行一次遍历寻找入度为0的顶点。如果是稀疏图,每次迭代只有少量顶点的入度发生变化,但是查找新的入度为0的顶点时,还是遍历了所有顶点,造成很多无效的重复操作。
可以用一个队列存放所有的入度为0的顶点,每次从队列中取一个顶点进行处理,并从队列中删除该顶点,然后更新该顶点所有相邻顶点的入度,如果更新后入度为0,则将其放入队列。
伪代码如下:
void topuSort() {
//将入度为0的顶点存在该队列中
Queue<Vertex> zeroVertexQueue = new Queue<Vertex>();
//遍历图,将顶点为0的顶点存到队列中
for each Vertex v in Graph {
if(v.indegree == 0){
//入队
zeroVertexQueue.enqueue();
}
}
int num = 0;
while(!zeroVertexQueue.isEmpty()){
//出队,从队列中获取一个顶点,并从队列中删除
Vertex v = zeroVertexQueue.dequeue();
//设置拓扑排序编号
v.topuNo = num;
num++;
处理图中与v相邻的顶点
for each Vertex w adjacent to v {
w.indegree--;
//如果入度更新后值为0,将该顶点入队
if(w.indegree == 0){
zeroVertexQueue.enqueue();
}
}
}
if(num != num_vertex){
throw new Exception("Graph has cycle !")
}
}
上面代码中,对于一个有n个顶点,m条边的图,while循环的时间复杂度为O(n);每次for循环处理邻接顶点的入度,一条边对应一个邻接顶点,故此部分时间复杂度为O(m),所以上述优化后的代码总的时间复杂度为O(m+n)。
以上就是拓扑排序的简单介绍,欢迎交流,共同学习!