版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_18108083/article/details/84873576
深度优先搜索(DFS)算法是最重要的图遍历算法,基于DFS框架,可以导出大量的图算法,图的拓扑排序即为其中一个很典型的例子。
拓扑排序:给定一个有向图,如何在保证“每个顶点都不会通过边,指向其在此序列中的前驱顶点”这一前提下,将所有顶点排成一个线性序列。
例如:
在编写教材时,由于各个知识点之间具有一定的依赖关系,如何将这些知识点串联为一份教学计划,保证在整个授课进程中,每节课的基本知识点均在之前已讲授呢?
这个例子就是个典型的拓扑排序实例。
特点:有向无环图的拓扑排序一定存在。
策略:在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<typename Tv, typename Te> bool graph<Tv, Te>::TSort(int v, int& clock, stack<Tv>* 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<typename Tv, typename Te> stack<Tv>* graph<Tv, Te>::tSort(int s)
{
reset();
stack<Tv>* S=new stack<Tv>;
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)时间。