200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > python 拓扑排序_拓扑排序(topsort)算法详解

python 拓扑排序_拓扑排序(topsort)算法详解

时间:2019-07-14 03:19:15

相关推荐

python 拓扑排序_拓扑排序(topsort)算法详解

在图论中,由某个集合上的偏序得到全序的策略就是拓补排序算法。拓扑排序常出现在涉及偏序关系的问题中,例如时序的先后、事物的依赖等。针对这些问题拓扑排序通常能有效地给出可行解。

为了便于理解,我们先来看一个实例,开源软件常使用GNU make工具来管理项目的构建,这里的“项目”是由若干个“对象”构成的。Makefile文件则描述了这些“对象”的构建规则,即给出一系列对象间的依赖关系。若对象A依赖于对象B,则说明对象B必须先于对象A构建,否则构建将无法进行。make的任务就是合理安排各个对象构建的先后顺序,使得过程能顺利地完成。

作为例子,一个Makefile文件的内容如下:每行描述一个规则。例如第一行指明对象foo.o和bar.o必须先于target构建。

target: foo.o bar.o

foo.o: foo.c foo.h

bar.o: bar.c bar.h

我们先对问题进行数学转化。离散数学为我们描述对象间的关系提供了有力工具——偏序。令X为所有要研究的对象的集合。集合X上的一个关系R是偏序,当且仅当R满足自反性、对称性、传递性。

定义如下关系:xRy:x必须先于y被构建,即y依赖x,因为R满足偏序的性质,xRy也记为x≼y。到此我们成功地对问题进行了建模。接下来使用DAG来表示每个对象间的关系,图的每一个顶点表示一个对象、有向线段表示关系起点≼终点。

(图一)

如何合理布局各个对象的构建顺序,使得构建过程可以顺利地进行下去呢?一种直观的想法是:先选择不被其它对象依赖的作为第1个对象;再考虑第2个对象,它除了已选的第1个对象外,不应该被其它对象依赖;选择第n个对象,它除了前面已选的第1~n-1对象外,不能再被其它对象依赖。按照这个规则依次选出对象,即可保证构建过程顺利结束。

可以认为在DAG中,这种直观想法是正确的。这种策略的结果用下图描述,但是,这并不是真正的拓扑排序:

(图二)

问题在于,得到的图并没有反映排序结果,充其量不过把图重新摆了一个形态,而图所描述的关系并没有本质的改变。如何解决这一问题?这就要引入全序。从图中直观地看出,只有部分对象之间具有偏序关系,作为反例,bar.h与bar.c之间无偏序关系,因此R不是集合X上的全序关系。试想在图中,如果为每一对不能比较的对象,强制添加一个关系u≼v(或v≼u),使得集合X中每两个对象都能建立关系,则R就成为了X上的全序关系,如图三所示。

(图三)

按照Hass图的顺序排列各个顶点得到图三,我们发现从最底部顶点bar.c出发,总有一条路径能走完所有顶点并到达最顶部顶点target,另一个重要的观察是,对于图中任意一对顶点u和v,若边∈Edges,则u在线性序列中出现在v之前,因此我们得到的结果拓扑有序。线性排列所有顶点,如下图所示:

这个结果便是原问题的拓扑排序。因为添加关系u≼v的方法不一定唯一,所以拓扑排序不一定唯一。但无论哪种情况,拓扑排序都满足一个关键的性质:没有一个节点指向它前面的节点,形式化地描述:对于图中的任意两个结点u和v,若存在一条有向边,则在拓扑排序中u一定出现在v前面。这条性质描述了拓扑排序的本质,为我们编写可行的算法提供了依据。

另外一些需要补充的定理是,有向图拓扑排序存在的充分必要条件是图为DAG(有向无环图),这个结论用于判断问题是否有解,也可用于判断一个有向图是否有环。

算法的求解过程如下:首先统计所有顶点的入度。然后:

a.

寻找所有入度为0的顶点,追加到结果序列末尾并将其从图中移除,同时将其所有邻接顶点的入度减一。

b.

重复a,直到所有顶点都从图中移除。

算法结束时,所得结果序列便是最终答案。

对于任意一个可能带环的有向图,在寻找入度为0的顶点时,如果找不到,说明图的拓扑排序是不存在的,即问题无解。

上述的“移除”是逻辑层面的概念,具体实现中,我们不需要真正地将顶点从图中移除,因为某次a.中找到的入度为0的顶点只可能出现在上一次a.中入度被减一的顶点中。当a找到入度为0的顶点时,就会把它的邻接顶点的入度减一,这时便可以顺便统计入度减为0的顶点,下次a直接从这些入度为0的顶点开始,无需再从整个图中寻找入度为0的顶点。

最后通过一道UVa的题目来说明算法的具体实现:

UVa10305(Ordering Tasks)

题目大意

给出一堆任务,其中一个任务必须在它依赖的所有任务都完成后才能执行。已知任务之间的关系,求可能的执行顺序。

分析

思路与make的例子一致。这里使用vector存储邻接表,数组deg_in维护每个顶点的入度,队列que维护每趟中入度被减为0的顶点。

参考代码

#include #include

#define N 100+2

using namespacestd;static vectorcon[N];static intdeg_in[N];int main(void) {

ios::sync_with_stdio(false);intn,m;while((cin >> n >> m) &&n) {for(int i=1;i<=n;++i) {

con[i].clear();

deg_in[i]= 0;

}for(int i=0; i

cin>> a >>j;

con[a].push_back(j);++deg_in[j];

}//

//找出第一个度为0的顶点// queueque;

vectorans;for(int i=1; i<=n; ++i) {if (!deg_in[i]) {

que.push(i);

}

}//

//求排序中其它n-1个顶点// while(!que.empty()) {int u =que.front();

que.pop();

ans.push_back(u);for(size_t i=0; i

que.push(t);

}

}

}for(size_t i=0; i

cout<< ans[i] << (i==ans.size()-1 ? "" : " ");

}

cout<

}return 0;

}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。