(动态编程)如何通过会议列表最大化房间利用率?

时间:2014-09-14 08:52:45

标签: c# algorithm optimization backtracking knapsack-problem

我正在尝试使用动态编程

问题:

给出会议室和间隔列表(代表会议),例如:

  • 间隔1:1.00-2.00
  • 间隔2:2.00-4.00
  • 间隔3:14.00-16.00 ... 等。

问题:

如何安排会议以最大限度地提高会议室利用率,会议是否应该相互重叠

尝试解决方案

以下是我在C#中的初步尝试(知道它是带有约束的修改后的背包问题)。但是我很难正确地得到结果。

bool ContainsOverlapped(List<Interval> list)
    {
        var sortedList = list.OrderBy(x => x.Start).ToList();
        for (int i = 0; i < sortedList.Count; i++)
        {
            for (int j = i + 1; j < sortedList.Count; j++)
            {
                if (sortedList[i].IsOverlap(sortedList[j]))
                    return true;
            }
        }
        return false;
    }
    public bool Optimize(List<Interval> intervals, int limit, List<Interval> itemSoFar){
        if (intervals == null || intervals.Count == 0)
            return true; //no more choice

        if (Sum(itemSoFar) > limit) //over limit
            return false;

        var arrInterval = intervals.ToArray();

        //try all choices
        for (int i = 0; i < arrInterval.Length; i++){
            List<Interval> remaining = new List<Interval>();
            for (int j = i + 1; j < arrInterval.Length; j++) { 
                remaining.Add(arrInterval[j]);
            }

            var partialChoice = new List<Interval>();
            partialChoice.AddRange(itemSoFar);
            partialChoice.Add(arrInterval[i]);

            //should not schedule overlap
            if (ContainsOverlapped(partialChoice))
                partialChoice.Remove(arrInterval[i]);

            if (Optimize(remaining, limit, partialChoice))
                return true;
            else
                partialChoice.Remove(arrInterval[i]); //undo
        }

        //try all solution
        return false;
    }


public class Interval
{
    public bool IsOverlap(Interval other)
    {
        return (other.Start < this.Start && this.Start < other.End) || //other < this
                (this.Start < other.Start && other.End < this.End) || // this covers other
                (other.Start < this.Start && this.End < other.End) || // other covers this
                (this.Start < other.Start && other.Start < this.End); //this < other
    }
    public override bool Equals(object obj){
        var i = (Interval)obj;
        return base.Equals(obj) && i.Start == this.Start && i.End == this.End;
    }
    public int Start { get; set; }
    public int End { get; set; }
    public Interval(int start, int end){
        Start = start;
        End = end;
    }
    public int Duration{
        get{
            return End - Start;
        }
    }
}

编辑1

房间利用率 =房间被占用的时间。抱歉混淆。

编辑2

为简单起见:每个间隔的持续时间是整数,开始/结束时间从整个小时开始(1,2,3..24)

4 个答案:

答案 0 :(得分:3)

我不确定你是如何将这与背包问题联系起来的。对我而言,这似乎更像是一个顶点覆盖问题。

首先按照开始时间对间隔进行排序,并以邻接矩阵或列表的形式形成图形表示。

顶点应为区间数。如果相应的间隔相互重叠,则两个顶点之间应该有一条边。此外,每个顶点应与一个等于间隔持续时间的值相关联。

然后问题就是以总值最大的方式选择独立顶点。

这可以通过动态编程来完成。每个顶点的递归关系如下:

V[i] = max{ V[j]            | j < i and i->j is an edge, 
            V[k] + value[i] | k < i and there is no edge between i and k }

Base Case V[1] = value[1]

注意:
顶点应按其开始时间的递增顺序编号。然后,如果有三个顶点:
i&lt; j&lt; k,如果顶点i和顶点j之间没有边,则顶点i和顶点k之间不能有任何边。

答案 1 :(得分:1)

好的方法是创建可以轻松处理的类。

首先,我创建了一个帮助类来轻松存储间隔

public class FromToDateTime
{
    private DateTime _start;
    public DateTime Start
    {
        get
        {
            return _start;
        }
        set
        {
            _start = value;
        }
    }

    private DateTime _end;
    public DateTime End
    {
        get
        {
            return _end;
        }
        set
        {
            _end = value;
        }
    }

    public FromToDateTime(DateTime start, DateTime end)
    {
        Start = start;
        End = end;
    }
}

然后这里是类Room,其中所有的间隔都是,并且有方法&#34; addInterval&#34;,如果间隔是好的并且被添加则返回true,如果没有则返回false。

btw:我在这里找到了重叠的检查条件:Algorithm to detect overlapping periods

public class Room
{
    private List<FromToDateTime> _intervals;
    public List<FromToDateTime> Intervals
    {
        get
        {
            return _intervals;
        }
        set
        {
            _intervals = value;
        }
    }

    public Room()
    {
        Intervals = new List<FromToDateTime>();
    }

    public bool addInterval(FromToDateTime newInterval)
    {
        foreach (FromToDateTime interval in Intervals)
        {
            if (newInterval.Start < interval.End && interval.Start < newInterval.End)
            {
                return false;
            }
        }
        Intervals.Add(newInterval);
        return true;
    }
}

答案 2 :(得分:1)

虽然更普遍的问题(如果您有多个会议室)确实是NP-Hard,并且被称为interval scheduling problem

一个教室的一维问题的最佳解决方案:
对于一维问题,选择(仍然有效)最早的截止日期首先能够最佳地解决问题。

证明:通过归纳,base子句是void子句 - 该算法最佳地解决了零会议的问题。

归纳假设是算法针对任意数量的k任务最佳地解决问题。

步骤:如果n次会议出现问题,请在最早的截止日期前填写,并在选择后删除所有无效会议。让所选择的最早截止日期任务为T
您将得到一个较小尺寸的新问题,通过在提醒上调用算法,您将获得最佳解决方案(归纳假设)。
现在,请注意,鉴于最佳解决方案,您最多可以添加一个丢弃的任务,因为您可以添加T或其他丢弃的任务 - 但所有这些任务都重叠T - 否则它们不会被丢弃),因此,您可以从所有丢弃的任务中添加最多一个,与建议的算法相同。

结论:对于1个会议室,该算法是最佳的。

<强> QED

解决方案的高级伪代码:

findOptimal(list<tasks>):
   res = [] //empty list
   sort(list) //according to deadline/meeting end
   while (list.IsEmpty() == false):
        res = res.append(list.first())
        end = list.first().endTime()
        //remove all overlaps with the chosen meeting
        while (list.first().startTine() < end):
              list.removeFirst()
   return res

澄清:此答案假设“房间利用率”意味着最大限度地增加会议室内的会议次数。

答案 3 :(得分:0)

谢谢大家,这是基于this Princeton note on dynamic programming的解决方案。

算法:

  1. 按结束时间排序所有事件。
  2. 对于每个事件,找到p [n] - 与其不重叠的最新事件(按结束时间)。
  3. 计算优化值:选择包括/不包括事件之间的最佳值。

    Optimize(n) {
        opt(0) = 0;
        for j = 1 to n-th {
            opt(j) = max(length(j) + opt[p(j)], opt[j-1]);
        }               
    }
    
  4. 完整的源代码:

        namespace CommonProblems.Algorithm.DynamicProgramming {
        public class Scheduler {
            #region init & test
            public List<Event> _events { get; set; }
    
            public List<Event> Init() {
                if (_events == null) {
                    _events = new List<Event>();
                    _events.Add(new Event(8, 11));
                    _events.Add(new Event(6, 10));
                    _events.Add(new Event(5, 9));
                    _events.Add(new Event(3, 8));
                    _events.Add(new Event(4, 7));
                    _events.Add(new Event(0, 6));
                    _events.Add(new Event(3, 5));
                    _events.Add(new Event(1, 4));
                }
    
                return _events;
            }
    
            public void DemoOptimize() {
                this.Init();
                this.DynamicOptimize(this._events);
            }
            #endregion
    
            #region Dynamic Programming
            public void DynamicOptimize(List<Event> events) {
                events.Add(new Event(0, 0));
                events = events.SortByEndTime();
    
                int[] eventIndexes = getCompatibleEvent(events);
                int[] utilization = getBestUtilization(events, eventIndexes);
                List<Event> schedule = getOptimizeSchedule(events, events.Count - 1, utilization, eventIndexes);
    
                foreach (var e in schedule) {
                    Console.WriteLine("Event: [{0}- {1}]", e.Start, e.End);
                }
            }
    
            /*
            Algo to get optimization value:
            1) Sort all events by end time, give each of the an index.
            2) For each event, find p[n] - the latest event (by end time) which does not overlap with it.
            3) Compute the optimization values: choose the best between including/not including the event.
    
            Optimize(n) {
                opt(0) = 0;
                for j = 1 to n-th {
                    opt(j) = max(length(j) + opt[p(j)], opt[j-1]);
                }
                display opt();
            }
            */
            int[] getBestUtilization(List<Event> sortedEvents, int[] compatibleEvents) {
                int[] optimal = new int[sortedEvents.Count];
                int n = optimal.Length;
    
                optimal[0] = 0;
    
                for (int j = 1; j < n; j++) {
                    var thisEvent = sortedEvents[j];
    
                    //pick between 2 choices:
                    optimal[j] = Math.Max(thisEvent.Duration + optimal[compatibleEvents[j]],  //Include this event
                                           optimal[j - 1]);                                   //Not include
                }
    
                return optimal;
            }
    
            /* 
            Show the optimized events: 
                sortedEvents: events sorted by end time.
                index: event index to start with.
                optimal: optimal[n] = the optimized schedule at n-th event.
                compatibleEvents: compatibleEvents[n] = the latest event before n-th
             */
            List<Event> getOptimizeSchedule(List<Event> sortedEvents, int index, int[] optimal, int[] compatibleEvents) {
                List<Event> output = new List<Event>();
                if (index == 0) {
                    //base case: no more event
                    return output;
                }
    
                //it's better to choose this event
                else if (sortedEvents[index].Duration + optimal[compatibleEvents[index]] >= optimal[index]) {
                    output.Add(sortedEvents[index]);
    
                    //recursive go back
                    output.AddRange(getOptimizeSchedule(sortedEvents, compatibleEvents[index], optimal, compatibleEvents));
                    return output;
                }
    
                //it's better NOT choose this event
                else {
                    output.AddRange(getOptimizeSchedule(sortedEvents, index - 1, optimal, compatibleEvents));
                    return output;
                }
            }
    
            //compatibleEvents[n] = the latest event which do not overlap with n-th.
            int[] getCompatibleEvent(List<Event> sortedEvents) {
                int[] compatibleEvents = new int[sortedEvents.Count];
    
                for (int i = 0; i < sortedEvents.Count; i++) {
                    for (int j = 0; j <= i; j++) {
                        if (!sortedEvents[j].IsOverlap(sortedEvents[i])) {
                            compatibleEvents[i] = j;
                        }
                    }
                }
                return compatibleEvents;
            }
            #endregion
        }
        public class Event {
            public int EventId { get; set; }
            public bool IsOverlap(Event other) {
                return !(this.End <= other.Start ||
                        this.Start >= other.End);
            }
            public override bool Equals(object obj) {
                var i = (Event)obj;
                return base.Equals(obj) && i.Start == this.Start && i.End == this.End;
            }
            public int Start { get; set; }
            public int End { get; set; }
            public Event(int start, int end) {
                Start = start;
                End = end;
            }
            public int Duration {
                get {
                    return End - Start;
                }
            }
        }
        public static class ListExtension {
            public static bool ContainsOverlapped(this List<Event> list) {
                var sortedList = list.OrderBy(x => x.Start).ToList();
                for (int i = 0; i < sortedList.Count; i++) {
                    for (int j = i + 1; j < sortedList.Count; j++) {
                        if (sortedList[i].IsOverlap(sortedList[j]))
                            return true;
                    }
                }
                return false;
            }
            public static List<Event> SortByEndTime(this List<Event> events) {
                if (events == null) return new List<Event>();
    
                return events.OrderBy(x => x.End).ToList();
            }
        }
    }