【问题标题】:calendar scheduler algorithm日历调度算法
【发布时间】:2011-03-15 04:47:48
【问题描述】:

我正在寻找一种算法,给定一组包含开始时间、结束时间、类型和 ID 的项目,它将返回一组所有项目组合在一起(没有重叠时间和所有类型在集合中表示)。

S = [("8:00AM", "9:00AM", "Breakfast With Mindy", 234),
     ("11:40AM", "12:40PM", "Go to Gym", 219),
     ("12:00PM", "1:00PM", "Lunch With Steve", 079),
     ("12:40PM", "1:20PM", "Lunch With Steve", 189)]

Algorithm(S) => [[("8:00AM", "9:00AM", "Breakfast With Mindy", 234),
                  ("11:40AM", "12:40PM", "Go to Gym", 219),
                  ("12:40PM", "1:20PM", "Lunch With Steve", 189)]]

谢谢!

【问题讨论】:

  • 如果您打开赏金,您应该在收到的答案中告诉我们您不喜欢什么 :-)
  • 您的回答很好:) 但就像您说的那样,它很贪婪,理想情况下不会如此。我基本上正在寻找不需要> = n的解决方案!时间
  • 你能把问题清理一下吗? “组合在一起的所有项目集”有点模棱两可。特别是因为您的示例仅显示了一组(并非所有可能性),并且不清楚为什么它更喜欢与史蒂夫共进午餐的一个版本而不是另一个版本。
  • 我的答案需要多项式时间:O(n^2),其中 n 是任务数。
  • 是否更容易使用 24 小时时间戳来避免在每个时间戳中检查 AM/PM?

标签: algorithm scheduled-tasks


【解决方案1】:

这可以使用graph theory 解决。我将创建一个数组,其中包含按开始时间和结束时间排序的相同开始时间的项目:(在示例中添加了更多项目):

no.:  id: [ start  -   end  ] type
---------------------------------------------------------
 0:  234: [08:00AM - 09:00AM] Breakfast With Mindy
 1:  400: [09:00AM - 07:00PM] Check out stackoverflow.com
 2:  219: [11:40AM - 12:40PM] Go to Gym
 3:   79: [12:00PM - 01:00PM] Lunch With Steve
 4:  189: [12:40PM - 01:20PM] Lunch With Steve
 5:  270: [01:00PM - 05:00PM] Go to Tennis
 6:  300: [06:40PM - 07:20PM] Dinner With Family
 7:  250: [07:20PM - 08:00PM] Check out stackoverflow.com

之后,我将创建一个包含数组编号的列表。可能是下一个项目的最少项目。如果没有下一项,则添加 -1:

 0 |  1 |  2 |  3 |  4 |  5 |  6 |  7
 1 |  7 |  4 |  5 |  6 |  6 |  7 | -1

使用该列表可以生成directed acyclic graph。每个顶点都与从下一个项目开始的顶点连接。但是对于已经是它们之间的顶点的顶点,则不会产生边。我会尝试用这个例子来解释。对于顶点 0,下一项是 1。所以一条边是 0 -> 1。从 1 开始的下一项是 7,这意味着从顶点 0 连接的顶点的范围现在是 1 to (7-1)。因为顶点 2 在 1 到 6 的范围内,所以生成了另一条边 0 -> 2,并且范围更新为 1 to (4-1)(因为 4 是 2 的下一项)。因为顶点 3 在 1 到 3 的范围内,所以又生成了一条边 0 -> 3。那是顶点 0 的最后一条边。所有顶点都必须继续这样做:

到目前为止,我们都在 O(n2) 中。之后,可以使用类似depth first search 的算法找到所有路径,然后从每个路径中消除重复的类型。 对于该示例,有 4 个解决方案,但没有一个具有所有类型,因为该示例不可能执行 Go to GymLunch With SteveGo to Tennis

此外,搜索所有路径的最坏情况复杂度为 O(2n)。例如,下图有 2n/2 条从开始顶点到结束顶点的可能路径。


(来源:archive.org

可以进行更多优化,例如在搜索所有路径之前合并一些顶点。但这永远不可能。在第一个示例中,顶点 3 和 4 不能合并,即使它们属于同一类型。但是在最后一个示例中,如果顶点 4 和 5 属于同一类型,则可以合并它们。这意味着您选择哪个活动都没有关系,两者都是有效的。这可以显着加快所有路径的计算速度。

也许还有一种聪明的方法可以更早地考虑重复类型以消除它们,但如果您想要所有可能的路径,最坏的情况仍然是 O(2n)。

编辑1:

可以确定是否存在包含所有类型的集合,并在多项式时间内得到至少一个这样的解。我发现了一个最坏情况时间为 O(n4) 和 O(n2) 空间的算法。我将举一个新示例,该示例具有所有类型的解决方案,但更复杂。

no.:  id: [ start  -   end  ] type
---------------------------------------------------------
 0:  234: [08:00AM - 09:00AM] A
 1:  400: [10:00AM - 11:00AM] B
 2:  219: [10:20AM - 11:20AM] C
 3:   79: [10:40AM - 11:40AM] D
 4:  189: [11:30AM - 12:30PM] D
 5:  270: [12:00PM - 06:00PM] B
 6:  300: [02:00PM - 03:00PM] E
 7:  250: [02:20PM - 03:20PM] B
 8:  325: [02:40PM - 03:40PM] F
 9:  150: [03:30PM - 04:30PM] F
10:  175: [05:40PM - 06:40PM] E
11:  275: [07:00PM - 08:00PM] G

1.) 计算项目集中的不同类型。这在 O(nlogn) 中是可能的。该示例为 7。

2.) 创建一个 n*n 矩阵,表示哪些节点可以到达实际节点,哪些可以从实际节点到达。例如,如果位置(2,4)设置为1,则表示图中存在从节点2到节点4的路径,并且(4,2)也设置为1,因为可以从节点2到达节点4 . 这在 O(n2) 中是可能的。例如,矩阵如下所示:

111111111111
110011111111
101011111111
100101111111
111010111111
111101000001
111110100111
111110010111
111110001011
111110110111
111110111111
111111111111

3.) 现在我们在每一行中都有可以到达的节点。我们还可以标记一行中尚未标记的每个节点,如果它与可以到达的节点的类型相同。我们将该矩阵位置设置为从 0 到 2。这在 O(n3) 中是可能的。在示例中,从节点 1 到节点 3 是没有路的,但是节点 4 与节点 3 具有相同的类型 D,并且从节点 1 到节点 4 有一条路径。所以我们得到这个矩阵:

111111111111
110211111111
121211111111
120121111111
111212111111
111121020001
111112122111
111112212111
111112221211
111112112111
111112111111
111111111111

4.) 仍然包含 0 的节点(在相应的行中)不能成为解决方案的一部分,我们可以将它们从图中删除。如果至少要删除一个节点,我们将在步骤 2 中重新开始。)使用较小的图。因为我们至少删除了一个节点,所以我们必须返回步骤 2。)最多 n 次,但大多数情况下这种情况只会发生几次。如果矩阵中没有 0,我们可以继续执行步骤 5。)。这在 O(n2) 中是可能的。例如,不可能用节点 1 构建一个路径,该路径还包含一个类型为 C 的节点。因此它包含一个 0 并像节点 3 和节点 5 一样被删除。在下一个循环中,具有较小的图形节点 6 和节点8 个将被删除。

5.) 计算剩余项目/节点集中的不同类型。如果它小于第一个计数,则没有可以表示所有类型的解决方案。所以我们必须找到另一种方法来获得好的解决方案。如果它与第一个计数相同,我们现在有一个较小的图表,它仍然包含所有可能的解决方案。 O(nlogn)

6.) 为了得到一个解决方案,我们选择一个起始节点(哪个都没有关系,因为图中剩下的所有节点都是解决方案的一部分)。 O(1)

7.) 我们从所选节点中删除无法到达的每个节点。 O(n)

8.) 我们在步骤 2.) 和 3.) 中为该图创建一个矩阵,并删除无法到达任何类型节点的节点,如步骤 4.)。 O(n3)

9.) 我们从之前选择的节点中选择一个下一个节点并继续 7.) 直到我们到达一个结束节点并且图只剩下一条路径。

这样也可以获得所有路径,但仍然可能是指数级的。毕竟它应该比在原始图中找到解决方案更快。

【讨论】:

  • 这看起来很有希望。今晚晚些时候我会尝试实现它,我会告诉你它是如何进行的。这就是为什么大学很重要:)
  • +1 非常好的答案 :) 顺便说一句,您使用哪个程序来绘制这些酷图?
  • 使用 graphviz 的 dot 命令行工具。见en.wikipedia.org/wiki/DOT_language
【解决方案2】:

嗯,这让我想起了大学里的一个任务,我会描述一下我能记住的 运行时间为 O(n*logn),相当不错。

这是一个贪婪的方法.. 我会细化您的要求,如果我错了,请告诉我.. 算法应该返回非冲突任务的 MAX 子集(就总长度而言?或活动量?我猜总长度)

我会首先按完成时间排序列表(第一个最小完成时间,最后一个最大)= O(nlogn)

Find_set(A):
  G<-Empty set;
  S<-A
  f<-0
  while S!='Empty set' do
    i<-index of activity with earliest finish time(**O(1)**)
    if S(i).finish_time>=f
      G.insert(S(i)) \\add this to result set
      f=S(i).finish_time
    S.removeAt(i) \\remove the activity from the original set
  od
  return G

运行时分析: 初始订购:nlogn 每次迭代 O(1)*n = O(n)

总 O(nlogn)+O(n) ~ O(nlogn)(好吧,考虑到 O 表示法在小数上表示真实复杂性的弱点。但随着规模的增长,这是一个很好的算法)

享受吧。

更新:

好的,我好像看错了帖子,你也可以使用动态编程来减少运行时间,link text page 7-19 有解决方案。

你需要稍微调整一下算法,首先你应该建立表格,然后你可以很容易地得到它的所有变体。

【讨论】:

  • +1,这是标准解决方案(假设 OP 对最大化计划任务的数量感兴趣) - CLRS 关于贪心算法的书中使用的示例。虽然有点令人费解的伪代码。我将其描述为:“1. 按完成时间对任务进行排序,2. 迭代一次排序的任务列表,将任何不与先前选择的任务重叠的任务添加到解决方案中”。 CLRS 中包含了为什么贪婪选择是正确的一个很好的证明。
  • 但正如我所见,OP 需要所有有效的时间表,而不仅仅是其中包含最多任务的时间表,所以这不是一个合适的答案。
  • OP 希望在解决方案中表示所有类型的活动。所以一个反例是("8:00AM", "10:00AM", "Activity A")("9:00AM", "10:20AM", "Activity B")("10:20AM", "11:00AM", "Activity A")。贪心算法会选择("8:00AM", "10:00AM", "Activity A")("10:20AM", "11:00AM", "Activity A"),同时有一个包含所有类型的解决方案:("9:00AM", "10:20AM", "Activity B")("10:20AM", "11:00AM", "Activity A")
【解决方案3】:

我会为此使用Interval Tree

构建数据结构后,您可以迭代每个事件并执行交集查询。如果未找到交叉路口,则会将其添加到您的日程安排中。

【讨论】:

  • 也许区间树是一个好的开始,但是找到一个好的时间表的算法需要重新设计。仅获得那些没有交叉路口的人是不够的。您可能需要回溯。
  • 是的,但提问者没有发布最小化间隙或时间表总持续时间的约束。
【解决方案4】:

是的,详尽的搜索可能是一种选择:

使用最早重叠的任务初始化部分计划(例如 9-9.30 和 9.15-9.45)

foreach 到目前为止生成的部分计划生成一个新的部分计划列表,将不重叠的最早任务附加到每个部分计划中(在出现关系的情况下生成多个)

以新的部分时间表重复

在你的情况下,初始化只会产生(8-9 breakfast)

第一次迭代后:(8-9 brekkie, 11.40-12.40 gym)(无关联)

第二次迭代后:(8-9 brekkie, 11.40-12.40 gym, 12.40-1.20 lunch)(不再绑定)

这是一个树搜索,但它是贪婪的。它排除了诸如跳过健身房和去吃早午餐之类的可能性。

【讨论】:

  • 耶“跳过健身房去吃早午餐”
  • 您将如何确保所有类型的活动都包含在解决方案中并采用贪婪的方法?它会选择较早的持久午餐而不是较晚的较短午餐,因此可能会在短暂的午餐后直接跳过一些活动,不是吗?并不是说这是一个糟糕的选择;)但我认为这不是 OP 想要的。
  • 当然,它保证是最佳的。但是 OP 没有定义要最大化的目标函数。
【解决方案5】:

由于您正在寻找所有可能的时间表,我认为您会找到的最佳解决方案将是简单的详尽搜索。

我只能从算法上说,你的字符串列表数据结构非常糟糕。

实现很大程度上依赖于语言,所以我什至认为伪代码没有意义,但我会尝试给出基本算法的步骤。

  1. 弹出相同类型的前n个项目并放入列表中。

  2. 对于列表中的每个项目,将该项目添加到计划集。

  3. 从列表中弹出下一个相同类型的项目。

  4. 对于在第一个项目结束后开始的每个项目,放入列表中。 (如果没有,失败)

  5. 继续直到完成。

最困难的部分是确定如何构造列表/递归,使其最优雅。

【讨论】:

    猜你喜欢
    • 2023-02-03
    • 1970-01-01
    • 2020-06-04
    • 1970-01-01
    • 2013-05-14
    • 1970-01-01
    • 2011-04-06
    • 2015-08-23
    • 2015-06-03
    相关资源
    最近更新 更多