查找周期中最大空闲时隙中间的算法?

时间:2012-08-01 13:20:10

标签: algorithm scheduling

假设我想在00:00-00:59期间安排一系列活动。我安排他们完整的分钟(00:01,从不00:01:30)。

我希望在那段时间内将它们尽可能地分开,但我事先并不知道在那个小时内我将会有多少事件。我今天可以安排一个活动,明天安排两个。

我脑子里有明显的算法,我可以想到实施它的蛮力方法,但我确信有人知道更好的方法。我更喜欢Ruby或者我可以翻译成Ruby的东西,但我会拿走我能得到的东西。

所以我能想到的算法是:

事件1最终在00:00结束。

事件2在00:30结束,因为该时间离现有事件最远。

第3项比赛可能会在00:15或00:45结束。所以也许我只选择第一个,00:15。

事件4然后在00:45结束。

事件5在00:08左右结束(从00:07:30向上舍入)。

等等。

所以我们可以看看每对分钟(比如,00:00-00:15,00:15-00:30,00:30-00:00),选择最大范围(00:30- 00:00),将它除以2和圆。

但我相信它可以做得更好。分享!

4 个答案:

答案 0 :(得分:2)

由于您最多只能安排60个事件,因此我认为使用静态表值得一试(与思考算法相比并进行测试)。我的意思是对你来说,在一段时间内布置事件是一项非常简单的任务。但要告诉计算机如何做到这一点并不容易。

所以,我建议使用静态时间值来定义表,以便放置下一个事件。它可能是这样的:

00:00, 01:00, 00:30, 00:15, 00:45...

答案 1 :(得分:2)

您可以使用位反转来安排您的活动。只需获取事件序列号的二进制表示,反转其位,然后将结果缩放到给定范围(0..59分钟)。

另一种方法是按顺序生成位反转字(0000,1000,0100,1100,...)。

这样可以轻松分发多达32个事件。如果需要更多事件,在缩放结果后,应检查结果分钟是否已被占用,如果是,则生成并缩放下一个单词。

以下是Ruby中的示例:

class Scheduler
  def initialize
    @word = 0
  end

  def next_slot
    bit = 32
    while  (((@word ^= bit) & bit) == 0) do
      bit >>= 1;
    end
  end

  def schedule
    (@word * 60) / 64
  end
end


scheduler = Scheduler.new

20.times do
  p scheduler.schedule
  scheduler.next_slot
end

按顺序生成位反转字的方法借用“Matters Computational ”,第1.14.3章。


<强>更新

由于从0..63缩放到0..59,这个算法倾向于在0,15,30和45之后产生最小的槽。问题是:它总是从这些(最小的)槽开始填充间隔,而从最大的插槽开始填充更自然。因此,算法并不完美。另外一个问题是需要检查“已经占用的分钟”。

幸运的是,一个小修补程序可以解决所有这些问题。只需改变

while  (((@word ^= bit) & bit) == 0) do

while  (((@word ^= bit) & bit) != 0) do

并使用63初始化@word(或者将其初始化为0,但是执行一次迭代以获取第一个事件)。此修复将反转的字从63递减到零,它总是将事件分配到最大可能的插槽,并且在前60次迭代中不允许“冲突”事件。


其他算法

之前的方法很简单,但它只保证(在任何时刻)最大的空插槽不超过最小插槽的两倍。由于您希望尽可能远离事件,因此可能优先选择基于斐波纳契数或黄金比率的算法:

  1. 将初始间隔(0..59)放入优先级队列(max-heap,priority = interval size)。
  2. 要计划事件,请弹出优先级队列,将结果间隔以黄金比例(1.618)拆分,使用拆分点作为此事件的时间,并将两个结果间隔放回优先级队列。
  3. 这可以保证最大的空插槽不超过(大约)最小插槽的1.618倍。对于较小的槽,近似值恶化,尺寸与2:1相关。

    如果在计划更改之间保留优先级队列不方便,您可以提前准备一个包含60个可能事件的数组,并在每次需要新事件时从该数组中提取下一个值。

答案 2 :(得分:1)

由于您无法重新安排活动,并且您事先并不知道将会有多少活动,我怀疑您自己的建议(使用罗马的使用01:00的提示)是最好的。

但是,如果您对有多少事件达到最大值有任何估计,您可以对其进行优化。例如,假设您估计最多7个事件,您可以准备60 / (n - 1) = 10分钟的时段并安排如下事件:

  • 00:00
  • 01:00
  • 00:30
  • 00:10
  • 00:40
  • 00:20
  • 00:50 //相隔10分钟

请注意,最后几个事件可能无法到达,所以00:50的使用概率很低。

这将是 fairer 然后基于非估计的算法,特别是在最坏情况下使用所有插槽:

  • 00:00
  • 01:00
  • 0时30
  • 00:15
  • 00:45
  • 0时07分
  • 00:37 //相隔仅7分钟

答案 3 :(得分:0)

我写了一个我的解决方案的Ruby实现。它具有边缘情况,任何超过60的事件都将在0分钟叠加,因为现在每个可用空间都是相同的大小,并且它更喜欢第一个。

我没有说明如何处理60岁以上的事件,我并不在乎,但我认为随机化或循环法可以解决这种边缘情况。如果你关心的话。

each_cons(2) gets bigrams;其余的可能很简单:

class Scheduler
  def initialize
    @scheduled_minutes = []
  end

  def next_slot
    if @scheduled_minutes.empty?
      slot = 0
    else
      circle = @scheduled_minutes + [@scheduled_minutes.first + 60]
      slot = 0
      largest_known_distance = 0

      circle.each_cons(2) do |(from, unto)|
        distance = (from - unto).abs
        if distance > largest_known_distance
          largest_known_distance = distance
          slot = (from + distance/2) % 60
        end
      end
    end

    @scheduled_minutes << slot
    @scheduled_minutes.sort!
    slot
  end

  def schedule
    @scheduled_minutes
  end
end


scheduler = Scheduler.new

20.times do
  scheduler.next_slot
  p scheduler.schedule
end