zoukankan      html  css  js  c++  java
  • 拓扑排序(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对象外,不能再被其它对象依赖;依次继续下去,直到留下最后一个对象

      按照这个规则依次选出对象,将最先选出的放左边,最后选出的放右边,结果如下。但这并不是真正的拓扑排序:

                  (图二)

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

                  (图三)

      按照Hass图的顺序排列各个顶点得到图三。一个重要的观察是,对于图中任意一对顶点u和v,若边<u,v>∈Edges,则u在线性序列中出现在v之前,因此我们得到的结果线性有序

    线性排列所有顶点,如下图所示:

      这个结果便是原图的拓扑排序。

      因为添加关系u≼v的方法不一定唯一,所以拓扑排序不一定唯一。

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

    二、算法与实现

      算法过程如下:对于输入的DAG,首先统计所有顶点的入度。然后:

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

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

    备注:

      上述的“移除”只是逻辑概念,不需要真的把顶点从图中移除。

      再次强调,对于带环有向图,环上顶点入度必不为0,拓扑排序不存在。

     

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

    UVa10305(Ordering Tasks)

    题目大意

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

    分析

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

    参考代码

    #include <iostream>
    #include <queue>
    #define N 100+2
    
    using namespace std;
    
    static vector<int> con[N];
    static int deg_in[N];
    
    int main(void) {
        ios::sync_with_stdio(false);
        
        int n,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<m; ++i) {
                int a,j;
                cin >> a >> j;
                con[a].push_back(j);
                ++deg_in[j];
            }
            
            //
            // 找出第一个度为0的顶点
            //
            queue<int> que;
            vector<int> ans;
            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<con[u].size(); ++i) {
                    int t = con[u][i];
                    if (--deg_in[t] == 0) {
                        que.push(t);
                    }
                }
            }
            
            for(size_t i=0; i<ans.size(); ++i) {
                cout << ans[i] << (i==ans.size()-1 ? "" : " ");
            }
            cout << endl;
        }
        
        return 0;
    }
  • 相关阅读:
    Python文件基础
    Python字符串基础操作
    Python ===if while for语句 以及一个小小网络爬虫实例
    Python 常用函数大体分类
    Atlas安装及配置
    (转)VS无法启动调试:“生成下面的模块时,启用了优化或没有调试信息“
    (转)数据库中视图的作用
    (转)ASP.NET MVC 学习第一天
    (转)介绍几个C#正则表达式工具
    关于textbox.attributes["value"]的问题
  • 原文地址:https://www.cnblogs.com/cassuto/p/11800821.html
Copyright © 2011-2022 走看看