使用拓扑排序查找计划任务的最小完成时间

时间:2014-01-01 00:51:28

标签: algorithm topological-sort

  

假设每个工人都有无限数量的工人   完成一项任务,每项任务都需要一些时间。还有   优先约束,其中一个任务直到完成才能完成   另一个是。每项任务所需的最短时间是多少   在尊重优先顺序的同时完成?


  

举个例子,假设您有3个任务。

     

第一项任务需要10个单位时间才能完成,第二项任务需要5个时间   时间单位完成,第三个需要6个单位的时间   完整。

     

约束是第二个任务直到第三个任务才能开始   任务完成了。


  

鉴于此,您可以得出结论,所有的最短时间   尊重优先权时要完成的任务是11。

     

这是因为你可以完成任务1和3(需要10和6次   分别)同时,任务3完成后,你可以   从任务2开始(需要5个单位时间)。因此,所有任务都将   在11时结束。


看起来这可以通过拓扑排序来解决,它可以为您提供完成任务以满足约束的顺序。

例如,在问题中,在对任务进行拓扑排序后,最终会得到

Task 1 (10 Units of time) -> Task 3 (6 Units of time) -> Task 2 (5 Units of time)

但是,一旦你获得了执行任务的顺序,你如何确定哪些可以同时完成,哪些需要一个接一个地完成?此外,您如何计算所有这些完成所需的最短时间?

2 个答案:

答案 0 :(得分:5)

我假设任务没有循环依赖。因此,任务及其依赖关系可以表示为有向无环图(从这一点缩写为 dag ),其中任务是顶点,边{{1 }}表示必须在任务u -> v开始之前完成任务u

您的方法是正确的使用拓扑排序来计算任务的完成顺序,因为图形是一个dag。我看到你有两个主要问题。

  1. 计算可能同时完成的任务
  2. 计算完成所有任务的最短时间
  3. 2号更简单,所以我先解决它。一旦计算出拓扑顺序,找到完成所有任务的最短时间并不困难。它基本上是在dag 中找到最长的路径,你可能已经在Critical path method中听到了它的应用。从本质上讲,完成所有任务所需的最短时间是完成最长持续时间的任务链所需的时间。乍一看,这似乎有悖常理,但想法是最终完成所有任务取决于我们需要多长时间才能完成任何任务链,因此,这取决于所需的时间完成占用时间最长的任务链。

    这是算法的伪代码(我认为它应该是正确的,但是因为我有一段时间没有这样做,我可能错了,所以要自己验证):

    v

    对于数字1,计算可以同时完成的任务,这稍微有些棘手。我以前没有实际做过这个,但我认为可以使用名为 Kahn算法的拓扑排序算法来完成(这是维基百科上拓扑排序的第一个算法,链接是{{3} })。我不了解你,但通常我使用深度优先搜索和堆栈执行拓扑排序,然后从堆栈中弹出顶点以获取顺序。从本质上讲,Kahn算法是拓扑排序算法,稍作修改,它应该能够解决我们的第一个问题。我之前没有这样做过,所以请再次验证。伪代码在评论中有解释:

    min_time_for_tasks(Graph, topological order):
        distance <- integer array whose size is number of vertices
        set all entries in visited array to negative infinity
        maxDist <- 0
        for v in topological order:
            if distance[v] == negative infinity:
                maxDist <- max(maxDist, longest_path(Graph, distance, v))
        return maxDist
    
    // computes the longest path from vertex v
    longest_path(Graph, distance, v):
        maxDist <- 0
        for all edges (v,w) outgoing from v:
            // vertex w has not been visited
            if distance[w] == negative infinity:
                longest_path(Graph, distance, w)
            // get the longest path among all vertices reachable from v
            maxDist <- max(maxDist, distance[w])
        distance[v] <- maxDist + time to complete task v
        return distance[v]
    

    因此// This is a modified kahn's algorithm. // Again, the graph is assumed to be a dag, and we first compute // the number of incoming edges to every vertex, since Kahn's // algorithm is dependent on this information. // This function will return an array of sets of tasks which // can be done at the same time. // So all tasks in the set in returnArray[0] can be done at the same // time, all tasks in the set in returnArray[1] can be done at the // same time, etc. Explanation will be available after the // pseudocode compute_simultaneous_tasks(Graph): into <- integer array, all entries initialized to 0 // this function is defined below compute_incoming_edges(Graph, into) returnArray <- empty array VSet <- Set of all vertices in the graph while VSet is not empty: // retrieve all vertices with no incoming edges // this means their prerequisite tasks have been completed, // and we can execute that particular task headVertices <- all vertices `v` in VSet such that into[v] is 0 // remove all the vertices in headVertices from VSet VSet <- VSet.remove(headVertices) // we are going to remove the vertices in headVertices, // so the edges outgoing from them will be removed as well. for each vertex v in headVertices: for each vertex w outgoing from v: into[w] <- into[w] - 1 // all the vertices in headVertices can be started at the // same time, since their prerequisite tasks have been // completed returnArray.append(headVertices) return returnArray // computes the number of edges leading into each vertex // so into[x] gives the number of edges leading into vertex x // from some other vertex compute_incoming_edges(Graph, into): for each vertex v in Graph: for each vertex w outgoing from v: into[w] <- into[w] + 1 函数将返回一组数组。每个集合包含可以同时完成的顶点/任务。为了弄清楚为什么会出现这种情况,在compute_simultaneous_tasks的主循环中,我们提取所有没有传入边的顶点。这相当于提取没有先决条件的所有任务。因此,我们可以看到同时执行这组任务是安全的,因为它们没有先决条件,并且当然不依赖于彼此!然后,我们删除它们的传出边缘,以模拟它们的完成。然后我们继续循环的下一次迭代,等等。

    所以我们看到在compute_simultaneous_tasks循环的每次迭代中,我们正在执行我们刚刚描述的内容,因此同时执行while中的所有任务是安全的,所有任务都在只有returnArray[0]中的所有任务完成后returnArray[1]同时returnArray[0]returnArray[2]之后的所有任务都已完成,并且等等。

    因此,在Kahn算法的这个修改中,我们不是一步删除一个带有0个入射边的顶点,而是一步删除所有带有0个入射边的顶点,以便获得所有可以在一个步骤中模拟完成的任务。 “wave”,其中returnArray[1]为wave 0,returnArray[0]为wave 1,等等。请注意,我们只删除之后中所有顶点的外出边所有顶点都有0个入边。

    虽然我无法提供正式的证据,但我确实相信,如果要同时完成所有波浪,我们将能够在上面计算的最短时间内完成所有任务,因为依赖性如何被考虑在内。

    希望有所帮助,新年快乐=)

答案 1 :(得分:1)

拓扑排序是正确的方法。它有助于识别任务相关性,并找到相互依赖的所有任务的持续时间之和。您必须对每个未访问的任务重复该过程,直到找到每组相关任务的总和。总和的最大值是同时完成所有任务所需的最短时间。