C ++数据结构的拓扑排序

时间:2015-02-01 13:11:30

标签: c++ multithreading algorithm sorting graph

在开始之前,我首先要提到的是术语“图形”,我将参考显示结构的图像。由于我的视力障碍,我既无法想象也无法画出它们。我可以看着他们并理解 - 但我很难自己制作它们。

我正在开发一个使用脚本语言生成目标的构建工具,这些目标经过处理并分成任务。任务表示为数据结构:https://github.com/IngwiePhoenix/IceTea/blob/master/src/main.cpp#L98

正如您所看到的,Task类注意到它是一个master(该过程的实际结果)并将一个引用 - 注释,仅一个引用而不是数组 - 存储到其子级和父级 - 如果有的话。

到目前为止,我可以使用所有必需的任务填充任务队列,并将其发送到执行者void Run(void*)https://github.com/IngwiePhoenix/IceTea/blob/master/src/main.cpp#L1107

但问题出现了:

  • 任务未正确排序,如果先前的构建被取消然后再次启动,构建将变得一团糟。如果您有a.cppb.cpp并且您的上一个构建在两个中的任何一个上终止而另一个未构建,则会导致父任务被运行两次。一个适当的排序机制 - 一个拓扑机制 - 很可能解决这个问题。目前,我已将此归类为不存在的依赖关系跟踪。

  • 当一个目标依赖另一个目标时,目标取决于另一个目标可能最终会在队列之前依赖于该目标。想象一下,您有libfoo.abar.exe。可能会发生,编译bar.exe的任务在创建libfoo.a的任务之前。这意味着,我们遇到了链接错误。

目前,该程序由一个线程池构成,该线程池从队列中提取任务并运行它们。但在我对Ninja工具进行了更多研究后,这种逻辑可能会发生变化;将有一个线程池只执行从脚本语言生成的命令。但是,现在必须等待。因此,我只在一个线程上运行该工具,以模拟我真正想要的行为;一个命令在另一个命令之后运行。

我主要面临的问题仍然是排序机制。从我对构建工具的研究中,他们倾向于使用所谓的DAG和拓扑排序。现在,我既没有找到关于如何编写拓扑排序算法的良好解释,也没有弄清楚它是如何工作的。我知道有常量uv。但我无法找到实现它的方法。

到目前为止,我已经明白这是一个线性图。我们假设以下结构:

myApp.exe
    - main.o
    - util.o
    | libfoo.a
        - foo_a.o
        - foo_b.o

这非常直截了当。有一个结果,一个依赖。但是如果我们有两个结果取决于相同的libfoo呢?

myApp.exe       libbar.so
    - main.o        - barlib.o
    - util.o        
-------------------------
            | libfoo.a
                - foo_a.o
                - foo_b.o

这就是我已经陷入困境的地方。

您是否可以向我解释如何实现拓扑算法,以及DAG实际上是什么?这将非常有帮助,因为我非常诚实地在这里遇到一个难以克服的障碍。提前谢谢!

附注:我希望尽可能小地保留工具,因此我无法添加像Boost.Graph这样的东西,我将其视为搜索结果。

1 个答案:

答案 0 :(得分:1)

好的,首先要做的事情是:DAG是 D 竖立的 A 循环 G raph。

  • 图表是具有连接的节点的数据结构 某种方式。例如,地图是交叉点所在的图形 节点和道路是边缘。

  • 有向图是边缘有某种方向的图形; 例如,许多道路是无向的(请保持正确 路边),但有些是单向的。

  • 非循环图是没有任何循环的图;这意味着一次 你离开一个节点,你就无法回到它。

您无法对循环图进行排序;周期的哪一部分会先来?但是你可以对非循环图进行排序,结果是“拓扑排序”。正如您在第二个示例中指出的(可能是偶然的),多个拓扑排序可能对同一个图形有效。

为了处理问题中的依赖关系,您将 使用图表。但是,既然你知道它将是非循环的,你可以编写自己的,非常简洁的图类。

struct node {
  string name;
  vector<node*> out_neighbors;
  vector<node*> in_neighbors;
}

您将所有节点存储在单个数组中graph,并且节点会在out_neighbors中保留它们之后的节点列表(这称为“邻接列表”格式)

现在你如何对它们进行排序?好吧,您希望对它们进行排序,以便节点不依赖于稍后出现的节点。首先,您希望找到没有传入边缘的所有节点(即,不依赖于任何东西)。

queue<node*> free;
for(int i=0; i<n; ++i)
  if(graph[i].in_neighbors.size() == 0)
    free.push(&graph[i]);

接下来,您将使用这些来查找其他节点。

queue<node*> topsort;         //this is going to be the sorted nodes
while(free.size() > 0) {
  node* top = free.front();   //remove the first element from the graph
  topsort.push(top);
  free.pop();

  for(int i=0; i<top->out_neighbors.size(); ++i) {
    node* neighbor = top->out_neighbors[i];
    neighbor->in_neighbors.erase(
      find(neighbor->in_neighbors.begin(), neighbor->in_neighbors.end(), top)
    );
    if(neighbor->in_neighbors.size() == 0)
      free.push(neighbor);
  }
}

最后,topsort将是排序拓扑顺序中的节点指针列表。但是,您的所有传入边缘也已从图形中删除(这可以使用传出边缘轻松重建)。

一些建议:

首先,你的Task已经像我在这里描述的简单结构,除了它们只有一个父指针和子指针;为什么没有父母(传入)和孩子(传出)的列表?然后,您可以对Task个对象进行排序。

其次,一旦它在拓扑上排序,你就可以在线程中运行;只要我的所有传入边缘都已编译完毕,我也可以随意使用。