求详解 pascal 拓扑排序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了求详解 pascal 拓扑排序相关的知识,希望对你有一定的参考价值。

拓扑排序
有向无回路图又称为dag。对这种有向无回路图的拓扑排序的结果为该图所有顶点的一个线性序列,满足如果G包含(u,v),则在序列中u出现在v之前(如果图是有回路的就不可能存在这样的线性序列)。一个图的拓扑排序可以看成是图的所有顶点沿水平线排成的一个序列,使得所有的有向边均从左指向右。因此,拓扑排序不同于通常意义上对于线性表的排序。

有向无回路图经常用于说明事件发生的先后次序,图1给出一个实例说明早晨穿衣的过程。必须先穿某一衣物才能再穿其他衣物(如先穿袜子后穿鞋),也有一些衣物可以按任意次序穿戴(如袜子和短裤)。图1(a)所示的图中的有向边(u,v)表明衣服u必须先于衣服v穿戴。因此该图的拓扑排序给出了一个穿衣的顺序。每个顶点旁标的是发现时刻与完成时刻。图1(b)说明对该图进行拓扑排序后将沿水平线方向形成一个顶点序列,使得图中所有有向边均从左指向右。

下列简单算法可以对一个有向无回路图进行拓扑排序。

procedure Topological_Sort(G);
begin
1.调用DFS(G)计算每个顶点的完成时间f[v];
2.当每个顶点完成后,把它插入链表前端;
3.返回由顶点组成的链表;
end;
图1(b)说明经拓扑排序的结点以与其完成时刻相反的顺序出现。因为深度优先搜索的运行时间为θ(V+E),每一个v中结点插入链表需占用的时间为θ(1),因此进行拓扑排序的运行时间θ(V+E)。

图1 早晨穿衣的过程

为了证明算法的正确性,我们运用了下面有关有向无回路图的重要引理。

引理1

有向图G无回路当且仅当对G进行深度优先搜索没有得到反向边。

证明:

→:假设有一条反向边(u,v),那么在深度优先森林中结点v必为结点u的祖先,因此G中从v到u必存在一通路,这一通路和边(u,v)构成一个回路。

←:假设G中包含一回路C,我们证明对G的深度优先搜索将产生一条反向边。设v是回路C中第一个被发现的结点且边(u,v)是C中的优先边,在时刻d[v]从v到u存在一条由白色结点组成的通路,根据白色路径定理可知在深度优先森林中结点u必是结点v的后裔,因而(u,v)是一条反向边。(证毕)

定理1

Topological_Sort(G)算法可产生有向无回路图G的拓扑排序。

证明:

假设对一已知有问无回路图G=(V,E)运行过程DFS以确定其结点的完成时刻。那么只要证明对任一对不同结点u,v∈V,若G中存在一条从u到v的有向边,则f[v]<f[u]即可。考虑过程DFS(G)所探寻的任何边(u,v),当探寻到该边时,结点v不可能为灰色,否则v将成为u的祖先,(u,v)将是一条反向边,和引理1矛盾。因此,v必定是白色或黑色结点。若v是白色,它就成为u的后裔,因此f[v]<f[u]。若v是黑色,同样f[v]<f[u]。这样一来对于图中任意边(u,v),都有f[v]<f[u],从而定理得证。(证毕)

另一种拓扑排序的算法基于以下思想:首先选择一个无前驱的顶点(即入度为0的顶点,图中至少应有一个这样的顶点,否则肯定存在回路),然后从图中移去该顶点以及由他发出的所有有向边,如果图中还存在无前驱的顶点,则重复上述操作,直到操作无法进行。如果图不为空,说明图中存在回路,无法进行拓扑排序;否则移出的顶点的顺序就是对该图的一个拓扑排序。

下面是该算法的具体实现:

procedure Topological_Sort_II(G);
begin
1 for 每个顶点u∈V[G] do d[u]←0; //初始化d[u],d[u]用来记录顶点u的入度

2 for 每个顶点u∈V[G] do
3 for 每个顶点v∈Adj[u] do d[v]←d[v]+1; //统计每个顶点的入度

4 CreateStack(s); //建立一个堆栈s

5 for 每个顶点u∈V[G] do
6 if d[u]=0 then push(u,s); //将度为0的顶点压入堆栈

7 count←0;

8 while (not Empty(s)) do
begin
9 u←top(s); //取出栈顶元素
10 pop(s); //弹出一个栈顶元素
11 count←count+1;
12 R[count]←u; //线性表R用来记录拓扑排序的结果

13 for 每个顶点v∈Adj[u] do //对于每个和u相邻的节点v
begin
14 d[v]←d[v]-1;
15 if d[v]=0 then push(v,s); //如果出现入度为0的顶点将其压入栈
end;
end;

16 if count<>G.size then writeln(\'Error! The graph has cycle.\')
17 else 按次序输出R;
end;
上面的算法中利用d[u]来记录顶点u的入度,第2-3行用来统计所有顶点的入度,第5-6行将入度为0的顶点压入堆栈,第8-15行不断地从栈顶取出顶点,将该顶点输出到拓扑序列中,并将所有与该顶点相邻的顶点的入度减1,如果某个顶点的入度减至0,则压入堆栈,重复该过程直到堆栈空了为止。显而易见该算法的复杂度为O(VE),因为第2-3行的复杂性就是O(VE),后面8-15行的复杂性也是O(VE)。这个算法虽然简单,但是没有前面一个算法的效率高。
参考技术A 第六节 图的拓扑排序有向无回路图是用来描述一项工程或任务进行过程的有效工具,一项大的工程都可划分成若干个子工程。在这些子工程中,有一些必须按照某种先后顺序进行,而另一些则不必考虑其先后次序,只有当各子工程顺利完成之后,整个工程方可竣工。这里所指的工程是广义的。 例如,计算机科学技术学院的学生,只有当他学完教学计划中所规定的全部课程,并取得成绩合格之后才能毕业。在这种情况下,工程就是给定的学习计划,而子工程就是学习某门课程。在这些课程中,有一些课程可以单独研读,与其他课程无关,而另外一些课程则有先决条件,就是说,只有当先期的课程学完之后才能研读这些课程。以《数据结构》为例,只有在学完《高等数学》、《程序设计》和《离散数学》之后才开始学习《数据结构》,否则无法进行。于是,先决条件就规定了课程之间的先后关系。若用有向图7.27就可把先决条件所规定的各种关系较为清楚地表示出来。图中的各个顶点代表课程,而有向边则代表课程间的先决条件。 课程代号 课程名称 先行课程 C1 高等数学 无 C2 程序设计基础 无 C3 普通物理 C1 C4 离散数学 C1 C5 数据结构 C1,C2,C4 C6 计算机原理 C3 C7 编译原理 C2,C5 C8 操作系统 C5,C6 图7.27表示课程之间先后关系的有向图 如果一个有向图G中各顶点代表任务或活动,而各有向边表示任务之间的先后关系,则该有向图称为顶点表示活动的网(Activity on Vertex network),简称AOV网。对于上面所列举的课程之间的先后关系,构造出了如图7.27所示的计算机科学技术学院教学计划的AOV网。 在AOV网中,若从顶点vi到顶点vj有一条有向路径,则vi是vj的前驱,vj是vi的后继,当且仅当<vi,vj>是网中的有向边时,则vi是vj的直接前驱,vj是vi的直接后继。 设G=(V,E)是一个AOV网,将它所有顶点排列成一个满足下述关系的线性序列:vi1,vi2,…, vin,若从顶点vi到vj有一条路径,则在该线性序列中顶点vi必在顶点vj之前;而且使原来没有路径的顶点之间也建立先后次序。我们把满足这种关系的线性序列称为拓扑序列,并把构造AOV网的拓扑序列的操作称为拓扑排序。若G是一个无回路的有向图,则G中至少有一个顶点没有前驱,即没有直接前驱,它的入度为0。于是可把入度为0的顶点以及由该顶点出发的所有弧都去掉。这时所得到的子图仍然是一个无回路的有向图。显然,该子图至少存在一个入度为0的顶点,再把入度为0的顶点以及由它出发的所有弧去掉,……。如此继续下去,便可得到一个顶点序列:vi1,vi2,…, vin顶点的这种线性序列就是它的一个拓扑次序。下面以图7.27为例,说明拓扑排序的方法。由于顶点C1和C2的入度均为0,所以取其中一个顶点输出即可。令C1为线性序列中的第一个顶点,并去掉以C1为尾的所有弧,此时,顶点C3和C4的入度均为0,取C2,C3,C4中任意一个作为线性序列中的第二个顶点,如C2,去掉以C2为尾的所有弧;再取一个入度为0的顶点,如C3,作为线性序列中的第三个顶点去掉以C3为尾的所有弧,此时顶点C6的入度变成了0,取C4或C6作为线性序列中的第四个顶点,如C4,…,如此下去,直到所有顶点出现在线性序列中为止。对于图7.27的拓扑线性序列为:C1,C2,C3,C4,C5,C6,C7,C8由上面的例子可以看出,一个有向无回路图的顶点的拓扑线性序列不一定是唯一的。根据以上分析,对AOV网进行拓扑排序的方法和步骤归结为:(1)从网中选择一个入度为0的顶点且将它输出;(2)从网中删除该顶点和以它为尾的所有有向边。(3)重复上述两步,直至全部顶点均被输出,或者网中剩余的顶点均有前驱。前一种情况得到完全的拓扑排列序列,说明网中不存在有向回路;后一种情况得到部分顶点的拓扑排序序列,说明网中存在有向回路。 为了有效地在计算机中实现拓扑排序算法,假定采用邻接表作为AOV网的存储结构。根据拓扑排序是在网中选取一个入度为0的顶点进行输出的思想,为此,在邻接表中增加一个存放顶点入度的整数域,这样一来,表头结点和链表结点的结构形式便完全一致了。 下面是上图AOV网的邻接表vertexcountlink v10-->3-->4-->5^v20-->5-->7^v31-->6^v41-->5^v53-->7-->8^v61-->8^v72^v82^在拓扑排序过程中,当某个顶点的入度为零时就将它输出,并同时将该顶点的所有直接后继顶点的入度减1。为了避免重复检测入度为零的顶点,需设立一个栈,用来存放入度为零的顶点。这样,拓扑排序算法可形式地描述如下: (1)将所有初始入度为零的顶点进栈; (2)当栈不空时,做: ①将栈顶顶点vj退栈并输出; ②对vj的每个直接后继vk做: a 顶点vk的入度减1; b 若vk的入度为零,则将vk入栈; (3)当栈空时输出的顶点数小于n时,说明图中存在有向回路;否则,拓扑排序正常结束。 下面给出按深度优先搜索的拓扑排序的详细算法描述:procedure toposort(ad:adjlist);ad是AOV网的邻接表begintop:=0; 栈指针初始化for i:=1 to n do 建立入度为0的顶点链栈if ad[i].count=0 then顶点vi入栈begintop:=top+1;stack[top]:=iend;c:=0;c为计数器,计输出顶点数while top<>0 do当栈不空时beginj:=stack[top];top:=top-1; 顶点vj退栈write(ad[j].vertex); 输出退栈的顶点vjc:=c+1;q:=ad[j].link; q指向v<sub>j</sub>为弧尾的第一条弧结点while q<>nil do 删去所有以vj为弧尾的边begink:=q^.edgver;顶点vk为vj 的直接后继ad[k].count:=ad[k].count-1; 入度减1if ad[k].count=0 then 新的入度为0 的顶点进栈begintop:=top+1;stack[top]:=k;end;q:=q^.link;end;end;if c<n n为网中顶点的个数then writeln('there is a cycle in the AOV!')else writeln('topological sorting completed!')end; 程序的时间复杂度为O(n+e)参考程序 采用邻接距阵作为有向图存储结构算法描述,对AOV网进行拓扑排序的方法和步骤归结为: (1)从网中选择一个入度为0的顶点且将它输出;(2)从网中删除该顶点和以它为尾的所有有向边。 重复上述两步,直至全部顶点均被输出,或者网中剩余的顶点均有前驱。前一种情况得到完全的拓扑排列序列,说明网中不存在有向回路;后一种情况得到部分顶点的拓扑排序序列,说明网中存在有向回路。procedure toposort;采用邻接距阵作为图的存储结构的拓扑排序算法
begin
b:=[1..n];未排序的顶点集合
repeat拓扑n个顶点
p:=0;
repeat
inc(p);
j:=0;
for i:=1 to n do 寻找入度为0的顶点
j:=j+a[i,p];统计顶点vp的入度
until (p>n) or ((p in b) and (j=0));
if p>n then 没有入度为0的顶点
begin
writeln('The cycle in the AOV');图中存在回路
exit;
end;
write(p,' ')最后一个顶点
b:=b-[p];顶点vp已经排序
for i:=1 to n do a[p,i]:=0;删除以顶点vp为弧尾的边
until b=[];
end; 算法的时间复杂度为O(n2)

以上是关于求详解 pascal 拓扑排序的主要内容,如果未能解决你的问题,请参考以下文章

拓扑排序

5.6 拓扑排序

拓扑排序 编程

图论-拓扑排序详解

拓扑排序详解

拓扑排序详解与实现