订购具有依赖关系的任务的算法

时间:2018-01-28 18:41:23

标签: algorithm graph graph-algorithm cycle-detection

在私人开源项目中,我遇到以下问题:

要执行各种任务。其中一些任务将有注释

  • 必须在执行一项或多项特定其他任务后执行
  • 必须在一个或多个特定的其他任务之前执行

我正在寻找一种简单的算法,用于如何根据该信息构建有向图,然后可以用于循环检测并按顺序执行所有任务任务,以允许尊重关于其执行顺序的所有这些条件。

问:构建此类图表的有效方法是什么?

感谢您的帮助。

注意:很明显,我们将在图中需要两个额外的节点:起始节点和结束节点。我们将它们命名为START和END。很明显,没有依赖关系的节点必须以诸如START - >之类的结构结束。 A - >结束。但是我不太清楚如何找到一个如何最终进入START的好方法 - > B - > C - >结束序列,假设B必须跟随C,而没有从B到END的边缘,没有从START到C的边缘。

2 个答案:

答案 0 :(得分:0)

您可以从任何订单开始,然后走完该订单,交换任何乱序的元素。您可以重复此操作,直到没有任何更多的任务出现故障。

我会使用哈希表(或简称数组)进行快速查找,以确定任务是否出现故障。

伪代码:

class Task:
    id: int # serial id of task, ie 1..n
    not_before: array[int] # ids of tasks this task cannot precede
    not_after: array[int] # ids of tasks this task cannot come after

tasks: array[Task] = ... # tasks in order of ids

order: array[int] = [1,2,...,n] # task ids in initial order

positions: array[int] = ... # positions[i] is the index of task i in order array

def swap_tasks(i, j):
    swap(order[positions[i]], order[positions[j]])
    swap(positions[i], positions[j])

repeat:
    made_swap = False
    for i in 0..n: # loop over task ids
        for j in tasks[i].not_before:
            if positions[i] < positions[j]:
                swap_tasks(i, j)
                made_swap = True
        for j in tasks[i].not_after:
            if positions[i] > positions[j]:
                swap_tasks(i, j)
                made_swap = True
    if made_swap == False:
        break

对于每个任务的n个任务和O(k)个限制,这应该在O(n²log(k))中运行,因为任务最多可以移动n次(因为它不能回到它交换过的最后一个任务的位置。)

我考虑按顺序处理任务并插入not_after个任务,然后是task,然后是not_before个任务,然后插入(或移动,如果它们已经出现)后续任务满足约束,但这似乎并没有帮助,因为not_beforenot_after任务可能相互之间无序,所以我们仍然需要大量的交换。

答案 1 :(得分:0)

要求中存在“杀手级”功能,无法轻松解决问题。只有两个方向的约束:

  • 任务A必须在任务B =&gt;之后执行“取决于”B
  • 任务A必须在任务B =&gt;之前执行B“取决于”A

在现实世界的情况下,我面临的这些依赖性更加复杂,但这并不重要:所有这些都可以分解为刚刚描述的模拟情况。

现在算法:

步骤1:将每个任务的所有这些约束编译为每个任务的一组单向依赖关系。然后可以容易地处理单向依赖性。首先构建图形,然后执行第二次循环检测然后执行拓扑排序(感谢术语Dimitry)的第一个想法可以完全放弃。因此每个项目都有一组依赖项:

  • 如果必须在任务B之后执行任务A,我们将“B”存储在A的依赖关系集中。
  • 如果必须在任务B之前执行任务A,我们将“A”存储在B的依赖关系集中。

在执行此操作时,我们甚至可以对这些依赖项进行健全性检查。如果约束规范中存在问题,我们可以在此步骤中轻松检测到这一点。

第2步:现在我们遇到一个非常简单的问题,因为只有一种方式依赖。这些可以被认为是先决条件:只有满足所有先决条件才能执行任务。现在我们可以按照以下步骤进行:

pack all tasks into a list named /notYetProcessed/
create empty list /result/
while there are still tasks to process in /notYetProcessed/ do:
    create empty list /remaining/
    for all tasks /t/ in /notYetProcessed/ do:
        if all dependencies are met for /t/ then do:
            add /t/ to /result/
        else do:
            add /t/ to /remaining/
    if /length(notYetProcessed)/ matches /length(remaining)/ then do:
        terminate with error
    /notYetProcessed/ = /remaining/

在外部while条件终止后result将包含一个要按顺序处理的任务列表,该顺序遵循开头所定义的所有约束。

如果上述算法以错误终止,则意味着: *此循环中无法处理任务 *表示:某些依赖项无法解析 *表示:存在涉及剩余任务的任务依赖循环

第3步:现在逐个处理result中存储的所有任务,一个接一个,你会没事的。

正如您所看到的,这可以在不构建数据的特殊图形表示的情况下完成。拓扑排序是通过接受第一个解决方案(=所有可能的排序顺序的第一个变体)“直接”在数据上执行的,我们可以得到它们。

可能是一些更有效的算法来解决这个问题(在第一次执行依赖项编译之后)我可能不知道。如果是这样,我很乐意了解它们!