基于非比较排序问题的排序算法?

时间:2008-11-17 21:38:08

标签: c# algorithm sorting comparison mathematical-optimization

我目前面临一个困难的排序问题。我有一组需要相互排序的事件(comparison sort)和它们在列表中的相对位置。

在最简单的术语中,我有事件列表,每个事件都有一个优先级(整数),一个持续时间(秒),以及事件可以出现在列表中的最早发生时间。我需要根据优先级对事件进行排序,但是在最早发生的时间之前,列表中不会出现任何事件。这是一个(希望)让它更清晰的例子:

// Psuedo C# code
class Event { int priority; double duration; double earliestTime ; }

void Example()
{
    Event a = new Event { priority = 1, duration = 4.0, earliestTime = 0.0 };
    Event b = new Event { priority = 2, duration = 5.0, earliestTime = 6.0 };
    Event c = new Event { priority = 3, duration = 3.0, earliestTime = 0.0 };
    Event d = new Event { priority = 4, duration = 2.0, earliestTime = 0.0 };

    // assume list starts at 0.0 seconds
    List<Event> results = Sort( new List<Event> { a, b, c, d } );

    assert( results[ 0 ] == a ); // 4.0 seconds elapsed
    assert( results[ 1 ] == c ); // 7.0 seconds elapsed
    assert( results[ 2 ] == b ); // 12.0 seconds elapsed
    assert( results[ 3 ] == d ); // 14.0 seconds elapsed
}

项目“b”必须是最后的,因为它不允许在列表中的6.0秒之前开始,所以它被推迟并且“c”在“b”之前开始,即使它的优先级较低。 (希望上面解释我的问题,如果不让我知道,我会编辑它。)

我目前的想法是使用insertion sort来管理排序过程。与许多其他常见的排序算法不同,插入排序一次一个地按顺序决定列表的顺序。因此,对于每个索引,我应该能够找到满足最早发生时间的下一个最低优先级事件。

我希望找到有关排序算法和数据结构的资源,以帮助我为这种“排序”问题设计一个很好的解决方案。我真正的问题实际上比这更复杂:分层排序,事件之间的变量缓冲,多个非恒定时间约束,因此越多的信息或想法越好。速度和空间并不是真正的问题。排序的准确性和代码的可维护性是一个问题。

修改:澄清(基于评论)

  • 事件消耗其整个持续时间(即不允许事件重叠)
  • 事件必须在其最早的时间或之后发生,它们不能在最早的时间之前发生。
  • 如果存在优先级较低的事件,事件可能发生在最早的时间之后
  • 活动不能中断
  • 最大持续时间是列表中可容纳的所有事件的总和。这未在上面显示。 (实际上,所有事件的持续时间将远远大于时间列表的最长持续时间。)
  • 没有任何差距。 (没有空洞可以尝试回填。)

修改:回答

虽然David Nehme给出了我选择的答案,但我想指出他的答案是插入的内心,其他几个人提供了插入排序类型的答案。这向我证实了专门的插入排序可能是要走的路。感谢大家的回答。

10 个答案:

答案 0 :(得分:10)

这实际上不仅仅是一个排序问题。这是发布日期的单机调度问题。根据您的尝试,问题可能是NP-Hard。例如,如果您试图最小化完成时间的加权和(权重与优先级成反比),问题是categorized

1|ri;pmtn|Σ wiCi 

并且是NP难的。这个主题有很多papers,但它可能超出了你的需要。

在您的情况下,您永远不需要具有间隙的解决方案,因此您可能只需要进行简单的离散事件模拟(O(n log(n)))时间。您需要将released_jobs存储为优先级队列。

unreleased_jobs = jobs  // sorted list of jobs, by release date
released_jobs = {}      // priority queue of jobs, by priority
scheduled_jobs = {}     // simple list
while (!unreleased_jobs.empty() || !released_jobs.empty()) {

    while (unreleased_jobs.top().earliestTime  <= t) {
        released_jobs.push(unreleased_jobs.pop())
    }
    if (!released_jobs.empty()) {
       next_job = released_jobs.pop();
       scheduled_jobs.push_back(next_job)
       t = t + next_job.duration
    } else {
       // we have a gap
       t = unreleased_jobs.top().earliestTime
    }
}

一个问题是,您可能会在短暂的高优先级作业之前拥有一个发布时间较低的优先级作业,但它会生成一个没有间隙的属性计划(如果计划没有间隙)是可能的。

答案 1 :(得分:2)

换句话说,您希望在制定两个约束时强化整体运行时间(强:最早执行点,弱:优先级)?这称为constraint satisfaction problem。这种问题有特殊的解决方法。

顺便说一下,jakber的解决方案不起作用。即使没有持续时间,以下示例显然也会失败:

event a (priority = 1, start = 5)
event b (priority = 2, start = 0)

排序后的序列为ab,而想要的结果肯定是ba

答案 2 :(得分:2)

我想:

  1. 按优先级排序任务
  2. 将任务安排到一个时间线上,在他们最早的时间之后占据第一个可用的插槽,这个插槽有一个足够大的空间来完成任务。
  3. 将时间线转换为任务列表,并等待(为间隙)。

    问题:

    1. 是否允许空白?
    2. 可以拆分任务吗?
    3. 鉴于问题中的任务:是否最好延迟b完成c,或者d以便b可以按时开始?
    4. 编辑:

      我的问题的答案是:

      1. 不(是的 - 如果没有什么可以运行,我想我们可能会有差距)
      2. 没有
      3. 仍然不清楚,但我想这个例子建议运行c和延迟b。
      4. 在这种情况下,算法可能是:

        1. 按优先顺序排序
        2. 从t = 0
        3. 开始,为当前'时间'保留一个计数器
        4. 搜索排序列表,查找可在t开始的最高优先级项目。
        5. 将项目添加到正在运行的订单中,并将其持续时间添加到t。
        6. 重复3&amp; 4直到列表用完为止。如果在t处没有可运行的任务,并且还有待处理的任务,则在运行顺序中执行1秒的睡眠任务。
        7. 该算法也是O(n ^ 2)。

答案 3 :(得分:0)

我认为您应该对列表进行两次排序:首先按优先级排序,然后按最早时间排序,使用任何稳定排序算法,例如插入排序。这样,时间会增加,每次事情都会按优先顺序排序。

除非您看到我不知道的内容,否则您可以完全忽略每个事件的持续时间以进行排序。

http://en.wikipedia.org/wiki/Category:Stable_sorts

答案 4 :(得分:0)

听起来你确实想要一个基于比较的排序。您的排序键是{earliestTime,priority},按此顺序排列。由于您的示例是伪C#,我将为您提供伪C#解决方案:

class Event : IComparable<Event>, IComparable{
    int    priority;
    double duration;
    double earliestTime;

    public int CompareTo(Event other){
        if(other == null)
            return 1; /* define: non-null > null */

        int cmp = earliestTime.CompareTo(other.earliestTime);
        if(cmp != 0)
            return cmp;

        /* earliestTimes were equal, so move on to next comparison */
        return priority.CompareTo(other.priority);
    }

   int IComparable.CompareTo(object other){ /* for compatibility with non-generic collections */
       if(other == null)
            return 1; /* define: non-null > null */

       Event e_other = other as Event;
       if(e_other == null) /* must have been some other type */
           throw new ArgumentException("Must be an Event", "other");

       return CompareTo(e_other); /* forward to strongly-typed implementation */
   }
}

现在,您的列表将按照您的断言所期望的那样排序。

修改

我最初的假设是,事件将被从列表中挑选出来并传递给一个单独的线程,以便队列管理器可以按时触发下一个事件,但是根据我收到的评论,我得知这可能是一个单线程的方法,但仍允许更高优先级的事件尽可能接近其开始时间,这是更理想的。在这种情况下,CompareTo函数应更改如下:

public int CompareTo(Event other){
    if(other == null)
        return 1; /* define: non-null > null */

    int cmp = priority.CompareTo(other.priority);

    if(cmp == 0)
        /*
         * calculate and compare the time each event will be late
         * if the other one were to start first.  This time may be
         * negative if starting one will not make the other one late
         */
        return (earliestTime + duration - other.earliestTime).CompareTo(
            other.earliestTime + other.duration - earliestTime);

    /*
     * they're different priorities. if the lower-priority event
     * (presume that greater priority index means lower priority,
     * e.g. priority 4 is "lower" priority than priority 1), would
     * would make the higher-priority event late, then order the
     * higher-priority one first.  Otherwise, just order them by
     * earliestTime.
     */
    if(cmp < 0){/* this one is higher priority */
        if(earliestTime <= other.earliestTime)
            /* this one must start first */
            return -1;

        if(other.earliestTime + other.duration <= earliestTime)
            /* the lower-priority event would not make this one late */
            return 1;

        return -1;
    }

    /* this one is lower priority */
    if(other.earliestTime <= earliestTime)
        /* the other one must start first */
        return 1;

    if(earliestTime + duration <= other.earliestTime)
        /* this event will not make the higher-priority one late */
        return -1;

    return 1;
}

针对任何假设进行测试,但我认为这是我们正在寻找的。

答案 5 :(得分:0)

如果您拥有一组有限的优先级,则可以保留一组时间排序列表,每个级别为1。每当您需要下一个事件时,请按优先级顺序检查每个列表的头部,直到找到其开始时间已过去的列表。 (在检查时跟踪最短的开始时间 - 如果尚未准备好任何事件,您知道要等待哪一个)

答案 6 :(得分:0)

听起来像是我前几天遇到的一个问题,答案是here 假设你正在使用C#...

答案 7 :(得分:0)

顺便提一下,在最一般的情况下,可能没有解决方案(除非允许空白,正如道格拉斯指出的那样)。例如:

Event a = new Event { priority = 1, duration = 1.0, earliestTime = 4.0 };
Event b = new Event { priority = 2, duration = 1.0, earliestTime = 4.0 };
Event c = new Event { priority = 3, duration = 1.0, earliestTime = 4.0 };
Event d = new Event { priority = 4, duration = 1.0, earliestTime = 4.0 };

答案 8 :(得分:0)

我不完全确定我理解你问题的复杂性,但我的直觉告诉我你需要定义优先级和开始时间之间的关系。例子是:

Event a = new Event { priority = 1, duration = 4.0, earliestTime = 1.0 };
Event b = new Event { priority = 2, duration = 5.0, earliestTime = 0.0 };

那么,我们是否继续在时间= 0开始b,或者我们等待勾选然后开始a因为它的优先级更高?假设有更多事件具有更多优先级和更长时间的权衡。我认为您需要一条规则:“如果下一个事件是X更高优先级并且间隙(现在和最早时间之间)小于Y秒,则等待然后启动更高优先级事件。否则启动低优先级事件(因此推回高优先级)“。

答案 9 :(得分:0)

以下是道格拉斯答案中的一些Python代码。首先我们按优先顺序排序,然后我们以选择排序方式拟合时间轴:

#!/usr/bin/env python
MIN_PRIORITY = 100

class Event(object):
    def __init__(self, name, priority, duration, earliestTime):
        self.name = name
        self.priority = priority
        self.duration = duration
        self.earliestTime = earliestTime
    def __str__(self):
        return "%-10s:  P %3d  D %3.1f  T %3.1f" % (self.name, self.priority, self.duration, self.earliestTime)

def sortEvents(_events):
    def comparePriority(event1, event2):
        if event1.priority < event2.priority: return -1
        if event1.priority > event2.priority: return 1
        return 0

    # Get a copy of the events and sort by priority
    events = [e for e in _events]
    events.sort(cmp=comparePriority)

    # Select one event at a time, checking for compatibility with elapsed time
    elapsedTime = 0.0
    sortedEvents = []
    while events:
        minGap = events[0].earliestTime - elapsedTime
        for e in events:
            currentGap = e.earliestTime - elapsedTime
            if currentGap < minGap:
                minGap = currentGap
            if currentGap <= 0.0:
                sortedEvents.append(e)
                elapsedTime += e.duration
                events.remove(e)
                break

        # If none of the events fits, add a suitable gap
        if minGap > 0:
            sortedEvents.append( Event("gap", MIN_PRIORITY, minGap, elapsedTime) )
            elapsedTime += minGap
    return sortedEvents

if __name__ == "__main__":
    #e1 = Event("event1", 1, 1.0, 4.0)
    #e2 = Event("event2", 2, 1.0, 6.0)
    #e3 = Event("event3", 3, 1.0, 8.0)
    #e4 = Event("event4", 4, 1.0, 10.0)

    e1 = Event("event1", 1, 4.0, 0.0)
    e2 = Event("event2", 2, 5.0, 6.0)
    e3 = Event("event3", 3, 3.0, 0.0)
    e4 = Event("event4", 4, 2.0, 0.0)

    events = [e1, e2, e3, e4]

    print "Before:"
    for event in events: print event
    sortedEvents = sortEvents(events)
    print "\nAfter:"
    for event in sortedEvents: print event