简体   繁体   English

给定一个非循环有向图,返回一组“在同一级别”的节点集合?

[英]Given an acyclic directed graph, return a collection of collections of nodes “at the same level”?

Firstly I am not sure what such an algorithm is called, which is the primary problem - so first part of the question is what is this algorithm called? 首先,我不确定这样的算法是什么,这是主要的问题 - 所以问题的第一部分是这个算法叫做什么?

Basically I have a DiGraph() into which I insert the nodes [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] and the edges ([1,3],[2,3],[3,5],[4,5],[5,7],[6,7],[7,8],[7,9],[7,10]) 基本上我有一个DiGraph() ,我插入节点[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]和边缘([1,3],[2,3],[3,5],[4,5],[5,7],[6,7],[7,8],[7,9],[7,10])

From this I'm wondering if it's possible to get a collection as follows: [[1, 2, 4, 6], [3], [5], [7], [8, 9, 10]] 从这里我想知道是否有可能获得如下收集: [[1, 2, 4, 6], [3], [5], [7], [8, 9, 10]]

EDIT: Let me add some constraints if it helps. 编辑:如果它有帮助,让我添加一些约束。 - There are no cycles, this is guaranteed - There is no one start point for the graph - 没有循环,这是有保证的 - 图表没有一个起点

What I'm trying to do is to collect the nodes at the same level such that their processing can be parallelized, but within the outer collection, the processing is serial. 我正在尝试做的是收集同一级别的节点,以便它们的处理可以并行化,但在外部集合中,处理是串行的。

EDIT2: So clearly I hadn't thought about this enough, so the easiest way to describe "level" is interms of deepest predecessor , all nodes that have the same depth of predecessors. EDIT2:很明显我没有想过这个,所以最简单的描述“级别”的方法是最深层的前任 ,所有节点都具有相同深度的前辈。 So the first entry in the above list are all nodes that have 0 as the deepest predecessor, the second has one, third has two and so on. 因此,上面列表中的第一个条目是所有节点,其中0是最前面的,第二个有一个,第三个有两个,依此类推。 Within each list, the order of the siblings is irrelevant as they will be parallelly processed. 在每个列表中, 兄弟姐妹的顺序是无关紧要的,因为它们将被并行处理。

Your question states that you would like the output, for this graph, to be [[1, 2, 4, 6], [3], [5], [7], [8, 9, 10]] . 您的问题表明您希望此图表的输出为[[1, 2, 4, 6], [3], [5], [7], [8, 9, 10]] IIUC, the pattern is as follows: IIUC,模式如下:

  • [1, 2, 4, 6] are the nodes that have no in edges. [1, 2, 4, 6]是没有边缘的节点。

  • [3] are the nodes that have no in edges, assuming all previous nodes were erased. 假设所有先前的节点都被擦除, [3]是没有边缘的节点。

  • [4] are the nodes that have no in edges, assuming all previous nodes were erased. 假设所有先前的节点都被擦除, [4]是没有边缘的节点。

  • Etc. (until all nodes have been erased) 等(直到所有节点都被删除)

Say we start with 假设我们开始

g = networkx.DiGraph()
g.add_edges_from([[1,3],[2,3],[3,5],[4,5],[5,7],[6,7],[7,8],[7,9],[7,10]])

Then we can just code this as 然后我们可以将其编码为

def find_levels(g):
    levels = []
    while g.nodes():
        no_in_nodes = [n for (n, d) in g.in_degree(g.nodes()).items() if d == 0]
        levels.append(no_in_nodes)
        for n in no_in_nodes:
            g.remove_node(n)
    return levels

If we run this, we get the result: 如果我们运行它,我们得到结果:

>>> find_levels(g)
[[1, 2, 4, 6], [3], [5], [7], [8, 9, 10]]

The complexity here is Θ(|V| 2 + |E|) . 这里的复杂度是Θ(| V | 2 + | E |) A somewhat more complicated version can be built using a Fibonnacci Heap . 可以使用Fibonnacci Heap构建更复杂的版本。 Basically, all vertices need to be placed into a heap, with each level consisting of the vertices with 0 in degree. 基本上,所有顶点都需要放入堆中,每个级别由度数为0的顶点组成。 Each time one is popped, and the edges to other vertices are removed, we can translate this to a heap decrease-key operation (the in-degrees of remaining vertices are reduced). 每次弹出一个,并删除其他顶点的边缘,我们可以将其转换为堆减少键操作(剩余顶点的度数减少)。 This would reduce the running time to Θ(|V| log(|V|) + |E|) . 这会将运行时间减少到Θ(| V | log(| V |)+ | E |)

A topological sort will achieve this, as Ami states. Ami表示,拓扑排序将实现这一目标。 The following is a Boost Graph Library implementation, with no context, but the pseudocode can be extracted. 以下是Boost Graph Library实现,没有上下文,但可以提取伪代码。 The toporder object just provides the iterator to a topological ordering. toporder对象只提供拓扑排序的迭代器。 I can extract the general algorithm if desired. 如果需要,我可以提取通用算法。

template<typename F>
void 
scheduler<F>::set_run_levels()
{

  run_levels = std::vector<int>(tasks.size(), 0);
  Vertexcont toporder;

  try
    {
      topological_sort(g, std::front_inserter(toporder));
    }
  catch(std::exception &e)
    {
      std::cerr << e.what() << "\n";
      std::cerr << "You most likely have a cycle...\n";
      exit(1);
    }

  vContIt i = toporder.begin();

  for(;
      i != toporder.end();
      ++i)
    {
      if (in_degree(*i,g) > 0)
        {
          inIt j, j_end;
          int maxdist = 0;
          for(boost::tie(j,j_end) = in_edges(*i,g);
              j != j_end;
              ++j)
            {
              maxdist = (std::max)(run_levels[source(*j,g)], maxdist);
              run_levels[*i] = maxdist+1;
            }
        }
    }
}

I think I once applied this to the same problem, then realized it was unnecessary. 我想我曾经把它应用到同样的问题,然后意识到这是不必要的。 Just set up the tasks on a hair-trigger, with all tasks signaling completion to their dependents (by a condition_variable, promise). 只需在头发触发器上设置任务,所有任务都向其家属发出信号(通过condition_variable,promise)。 So all I needed was to know the dependencies of each task, find the initial task, then fire. 所以我需要的只是知道每个任务的依赖关系,找到初始任务,然后开火。 Is a full specification of run_level needed in your case? 您的案例中是否需要完整的run_level规范?

Why would bfs not solve it? 为什么bfs无法解决呢? A bfs algorithm is breadth traversal algorithm, ie it traverses the tree level wise. bfs算法是广度遍历算法,即它遍历树级。 This also means, all nodes at same level are traversed at once, which is your desired output. 这也意味着,同一个级别的所有节点都会被遍历,这是您想要的输出。 As pointed out in comment, this will however, assume a starting point in the graph. 正如评论中指出的那样,这将假设图中的起点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM