拓扑排序详解

拓扑排序是对有向无圈图的顶点的一种排序方式,使得如果存在一条从vi到vj的路径,那么在排序中vj就在vi的之后出现。在图1中的图表示迈阿密州立大学的课程先修结构(course prerequisite structure)。有向边(v,w)表明课程v必须在课程w选修前修完。这些课程的拓扑排序是不破坏课程先修要求的任意的课程序列。

在这里插入图片描述

图1 表示课程先修结构的无圈图

显然,如果图含有圈,那么进行拓扑排序是不可能的,因为对于圈上的两个顶点v和w, v先于w同时w又先于v。此外,拓扑排序不必是唯一的,任何合理的排序都是可以的。在图2中,v1 ,v2,v5,v4,v3,v7,v6和 v1,v2,v5,v4,v7,v3, v6两个都是拓扑排序。

在这里插入图片描述
图2.一个无圈图

一个简单的求拓扑排序的算法是先找出任意一个没有入边(incoming edge)的顶点。然后我们显示出该顶点,并将它和它的边一起从图中删除。然后,我们对图的其余部分继续应用这样的方法来处理。
为了将上述方法形式化,我们把顶点v的入度(indegree)定义为边(u,v)的条数。我们计算图中所有顶点的入度。假设每一个顶点的入度均被存储,并且图被读入一个邻接表中,则此时可以应用图3中的算法生成一个拓扑排序。

 void Graph::topsort()
 {
 	 for(int counter=0;counter<NUM_VERTICES;counter++)
 	 { Vertex v = findNewVertexOfIndegreeZero(); 
 	  	if(v==NOT_A_VERTEX )
 	  		 throw CycleFoundException{ };
 	  	v.topNum = counter;
 	  	for each Vertex w adjacent to v
 	  	 	w.indegree--;
 	  	 	}  	 	
 	}3.简单拓扑排序代码

函数findNewVertexOfIndegreeZero扫描数组,寻找一个尚未被分配拓扑编号的入度为0的顶点。如果这样的顶点不存在,那么它返回NOT_A_VERTEX。这就说明该图有圈。
因为函数findNewVertexOfIndegreeZero是对顶点数组的一个简单的顺序扫描,所以每次对它的调用都花费O(|V|)时间。由于有|V|次这样的调用,因此该算法的运行时间为O(|V*V|)。
通过更仔细地关注这样的数据结构,我们可以做得更好。产生如此差的运行时间的原因在于对顶点数组的顺序扫描。如果图是稀疏的,那么我们就可以预知,在每次迭代期间只有少数顶点的入度被更新。然而,虽然只有一小部分发生变化,但在搜索入度为0的顶点时我们(潜在地)查看了所有的顶点。
我们可以通过将所有(未分配拓扑编号)的入度为0的顶点放在一个特殊的盒子中而消除这种无效的劳动。此时findNewVertexOfIndegreeZero函数返回(并删除)该盒子中的任一顶点。当我们将它的邻接顶点的入度减1时,检查每一个顶点并在它的入度降为0时把它放入盒子中。
为实现这个盒子,可以使用一个栈或一个队列。我们将使用队列。首先,对每个顶点计算它的入度。然后,将所有入度为0的顶点放入一个初始为空的队列中。当队列不空时,删除一个顶点v,并将邻接到v的所有顶点的入度均减1。只要一个顶点的入度降为0,就把该顶点放入队列中。此时,拓扑排序就是顶点出队的顺序。图4显示了每一阶段之后的状态。

在这里插入图片描述
图4对图2中的图应用拓扑排序的结果

这个算法的伪代码实现在图5中给出。和前面一样,我们将假设图已经被读到一个邻接表中,并假设入度均被算出且和顶点一起被存储。我们还假设每个顶点有一个域,叫作topNum, 其中存放的是顶点的拓扑编号。

void Graph::topsort()
{
	Queue<Vertex> q;
	int counter =0;
	
	q.makeEmpty();
	for each Vertex v
		if(v.indegree == 0)
			q.enqueue(v);

while(!q.isEmpty ( ))
{
	Vertex v = q.dequeue();
	v.topNum = ++counter;//分配下一个拓扑编号

	for each Vertex w adjacent to v
		if(--w.indegree ==0)
			q.enqueue(w);
}
	if(counter != NUM_VERTICES)
		throw CycleFoundException { };
}5.实施拓扑排序的伪代码

如果使用邻接表,那么执行这个算法所用的时间为O(|E|+|V|)。当认识到for循环体对每条边最多执行一次时,这个结果是显然的。入度的计算可以由下列代码完成。同样,计算的开销也是O(|E|+|V|),尽管这里存在一些嵌套的循环。

for each Vertex v
	v.indegree=0;
for each Vertex v
for each Vertex w adjacent to v
w.indegree++;

队列操作对每个顶点最多进行一次,而其他的初始化步骤,包括入度的计算,所花费的时间也与图的大小呈正比。

参考文献:	数据结构与算法分析--C++语言描述(第四版)(Make Allen Weiss)
  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值