【算法设计与数据结构】拓扑排序算法的实现——Kahn算法及基于dfs的算法

    拓扑排序的定义和原理等我不再赘述,各种教材和网络上都有详细解释,今天我主要谈一谈两种实现拓扑排序的算法。

Kahn算法:

<span style="white-space:pre">	</span>将所有入度为0的顶点加入队列q;
	while(!q.empty() )
	{
		u = q.front();
		q.pop();
		list.push(u);
		for (u的每个邻接点v)
		{
			删除边(u, v);
			if (indegree(v) == 0)
				q.push(v);
		}
	}
	if (图G还有边存在)
		return 存在环
	else
		return list;


    以上这种是比较典型的求拓扑排序的算法,算法复杂度为O(v+e),常可用来判断该图是否是DAG(有向无环图)
    在算法导论上,介绍的则是另外一种算法,它是基于DFS的,实现十分简单,仅需要在DFS中多加一个语句即可。
    基于DFS的拓扑排序算法(前提:图是DAG):

L ← 用于存放排序结果的数组
S ← 出度为0的顶点的集合
for (S中的每个顶点)
    dfs(n) 
void dfs(node n)
{
	if (!vis[n])
	{
		vis[n] = true;
		for (每一个顶点m,满足m->n)
			dfs(m);
	}
        L.push(n);
}

    可以看到,我们只是在dfs函数快退出时将结点加入到L中而已。

    (注意,for中的顶点m,满足的是m->n而不是n->m)

    下面简单证明一下它的正确性:
    对任意的边m->n,当调用dfs(n)的时候,有如下两种情况:
    1) dfs(m)还没有被调用,此时会调用dfs(m),只有dfs(m)返回之后,dfs(n)才会返回

    2) dfs(m)已经被调用过并返回了

    (由于本图是DAG,所以不存在dfs(m)已经被调用,但是在dfs(n)在被调用时还未返回的情况)

    无论是以上哪一种情况,m都会先于n被添加到L中。所以对于任意边m->n,在L中,m总会在n前面。

    本算法的复杂度为O(v+e),需要注意的一些点是,本算法是建立在图为DAG的基础上的,当然,可以进行一些修改来做环路检测,另外, 本算法的起点是对每个出度为0的顶点进行dfs,而Kahn算法则是从每个入度为0的顶点出发。为何本算法需要从出度为0的顶点出发呢?因为出度为0的顶点必然排在最后面,而最先调用dfs的顶点最后才加入L中。

    对比两种算法,有着异曲同工之妙,一个从入度为0的顶点出发,一个从出度为0的顶点出发;一个对m->n中的每个n进行操作,一个对m->n中的每个m进行操作。

    如果需要判断该图是否为DAG,那么第一种算法是不错的选择,如果已经知道该图为DAG,则第二种算法更加简洁!

    原文作者:排序算法
    原文地址: https://blog.csdn.net/jiange_zh/article/details/48183267
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞