三、【图算法】DFS应用-拓扑排序

深度优先搜索(DFS)算法是最重要的图遍历算法,基于DFS框架,可以导出大量的图算法,图的拓扑排序即为其中一个很典型的例子。

拓扑排序:给定一个有向图,如何在保证“每个顶点都不会通过边,指向其在此序列中的前驱顶点”这一前提下,将所有顶点排成一个线性序列。

例如

在编写教材时,由于各个知识点之间具有一定的依赖关系,如何将这些知识点串联为一份教学计划,保证在整个授课进程中,每节课的基本知识点均在之前已讲授呢?

三、【图算法】DFS应用-拓扑排序_第1张图片

这个例子就是个典型的拓扑排序实例。

特点:有向无环图的拓扑排序一定存在。

策略:在DFS中首先转换至VISITED状态的顶点是出度为0的顶点m,此顶点m对之后的搜索没有任何作用,这时就相当于拓扑排序的最后一个顶点,将顶点m剔除,之后的出度为0的顶点即为顶点m的前驱,这样将DFS遍历结束,即可将这些前后剔除的顶点连接起来构成一个拓扑排序。注意,多个连通域会执行多次DFS,这些DFS各自剔除的顶点之前其实是没任何关系的,所以可以任意排列。

实现:由于整个图可能具有多个连通域,从单个顶点开始的TSort可能不能遍历到图中的所有顶点,所以TSort函数能够遍历从顶点s开始的单个连通域,而tSort函数则对所有顶点进行检查,只要未曾被访问过,就从该点开始一次新的TSort搜索,这样就能保证所有的连通域都能够被遍历到。

本文使用的图数据结构参见之前博客https://blog.csdn.net/qq_18108083/article/details/84870399

template bool graph::TSort(int v, int& clock, stack* S)   //(基于DFS)单个连通域的拓扑排序,从连通域的任一顶点开始即可,因为会有外层tSort函数排查尚未发现的顶点
{
	status(v) = DISCOVERED;       
	dTime(v) = ++clock;
	for (int u = firstNbr(v); u > -1; u = nextNbr(v, u))
	{
		switch (status(u))
		{
		case UNDISCOVERED:   //若此顶点尚未被发现
			status(u) = DISCOVERED;  //标记为已被发现
			type(v, u) = TREE;     //标记边e(v,u)为遍历树的枝干
			parent(u) = v;
			if (!TSort(u, clock, S))  //继续深入遍历
				return false;
			break;
		case DISCOVERED:    //若此顶点已被发现但尚未完成遍历,则为回环
			type(v, u) = BACKWARD;
			return false;  //发现此连通域为有环图,不能生成拓扑序列
		default:   //VISITED  发现已经遍历完毕的顶点
			type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;  //根据顶点最开始遍历的时间标签dTime判断是前向边还是两个分支的跨边
			break;
		}
	}  
	//此顶点已经完成遍历
	status(v) = VISITED;
	S->push(vdata(v));
	return true;
}

template stack* graph::tSort(int s)
{
	reset();
	stack* S=new stack;
	int v = s;
	int clock = 0;
	do
	{
		if (status(v) == UNDISCOVERED)
		{
			if (!TSort(v, clock, S))   //如果发现是有环图不能生成拓扑序列,则返回
			{
				while (!(S->empty()))
				{
					S->pop(); 
				}
				break;
			}
		}
	} while ((v=(++v)%n)!=s);
	return S;
}

效率:若图G=(V,E)中共有n个顶点和e条边,则tSort仅需O(n+e)时间。

你可能感兴趣的:(算法)