N个重叠会议日程的最佳房间数量和大小

10

我碰到了这个问题,不确定我的解决方案是否最优。

问题

给定N个加权(Wi)可能重叠的时间段(代表会议日程),找出进行所有会议所需的最小数量"&" 会议室的容量。

示例

|---10------|. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .|---------8---------|

        |------8-----|          |----------10-----------|

                  |--------6-------|

针对上面的时间表,我们需要两个10人和10人的会议室。(我理解正确吗?)

我的解决方案

拿出一组会议室,从左边开始遍历时间段,如果有可用容量大于所需的会议室,则使用它,如果没有满足条件的会议室,则创建一个新的会议室或增加现有会议室的容量。

例子:

10的开始 - { 10 }

8的开始 - { 10, 8 }

10的结束 - { 10-free, 8 }

6的开始 - { 10, 8 }

8的结束 - {10, 8-free }

10的开始 = { 10, 8+=2 } 或 {10, 10 }

等等......

这本质上是贪心算法。

  • 有人能证明这是不最优的吗?
  • 如果这种方法不是最优的,那么有什么解决方案?是动态规划吗?

两个小会议可以或不可以在同一时间在同一个房间里举行? - libik
2
优化中更重要的是什么,房间数量吗?您也可以使用容量为10、8、6的3个房间进行工作。因此,您不需要两个大房间。 - Henry
@Henry。房间数量比容量更重要。 - Kshitij Banerjee
@libik.. 两个会议不能在同一个房间。 - Kshitij Banerjee
6个回答

9

5

直觉

我会尽力而为。一种天真的方法是枚举所有可能的解,并选择最佳的一个。因此,找到可以容纳n个会议的k个房间等价于在n个点中找到k路划分。一个5个会议的2路划分示例是[0,2,4][1,3],如OP示例所示:

|---0------|                     |---------4---------|

   |------1-----|          |----------3-----------|

             |--------2-------|

所以基本思想是枚举所有n个会议的k路划分,其中约束条件是两个重叠的会议不能属于同一组。例如,[0,1,2]和[3,4]不是有效的划分,因为会议[0,1,2]不能在同一个房间举行;对于会议[3,4]也是如此。幸运的是,使用递归方法时,这个约束条件很容易实现。
算法如下(Python代码):
def kWay( A, k, overlap ) :
    """
    A = list of meeting IDs, k = number of rooms, 
    overlap[ meeting ID m ] = set of meetings overlapping with m 
    """
    if k == 1 : # only 1 room: all meetings go there
        yield [ A[:] ]
    elif k == len(A) : # n rooms and n meetings: put 1 meeting per room
        yield [ [a] for a in A ]
    else :
        for partition in kWay( A[1:], k, overlap ) : # add new meeting to one existing room
            for i, ci in enumerate( partition ) :
                isCompatible = all( A[0] not in overlap[x] for x in ci ) # avoid 2 overlapping meetings in the same room
                res = partition[:i] + [ ci + [ A[0] ] ] + partition[ i+1: ]
                if isCompatible :
                    yield res
        for partition in kWay( A[1:], k-1, overlap ) : # add new meeting to a new room
            isValid = ( set(A[1:]) & set.union( * ( overlap[a] for a in A[ 1: ] ) ) == set() ) # avoid 2 overlapping meetings in the same room
            if (k-1>1) or ( k-1==1 and isValid ) :
                yield partition + [ [ A[0] ] ]

这看起来有点复杂,但当你意识到这只是递归算法的k路分区+2行额外代码以确保我们只考虑有效分区时,它实际上非常简单。
OP示例的解决方案:
现在让我们使用OP示例来准备输入数据。
import collections

n = 5
k = 2
#
A = range(n)
# prepare overlap dictionary
pairs = [ (0,1), (1,2), (2,3), (3,4) ] # overlapping meetings
size = dict( ( (0,10), (1,8), (2,6) , (3,10), (4,8) ) )

overlap = collections.defaultdict(set)
for (i,j) in pairs :
    overlap[i].add(j)
    overlap[j].add(i)

defaultdict(<type 'set'>, {0: set([1]), 1: set([0, 2]), 2: set([1, 3]), 3: set([2, 4]), 4: set([3])})
{0: 10, 1: 8, 2: 6, 3: 10, 4: 8}

现在我们只需遍历有效的2路分区并打印房间大小。只有一个有效的分区,所以这就是我们的解决方案:
for partition in kWay( A, k, overlap ) :
    print partition, [ max( size[x] for x in c ) for c in partition ]
[[3, 1], [4, 2, 0]] [10, 10]

好的,会议 1,3 需要一个大小为 10 的房间,而会议 0,2,4 则需要一个大小为 10 的房间。

稍微复杂一些的例子

但是只有一种有效的 2-路分区,所以这当然也是最优解。太无聊了!让我们添加一个新的会议 5 和一个新的房间到 OP 示例中,使其更加有趣:

|---0------|            |---5---|        |---------4---------|

   |------1-----|          |----------3-----------|

             |--------2-------|

相应的输入数据:

n = 6
k = 3
#
A = range(n)
pairs = [ (0,1), (1,2), (2,3), (3,4), (5,2), (5,3) ] # overlapping meetings
size = dict( ( (0,10), (1,8), (2,6) , (3,10), (4,8), (5,2) ) )

overlap = collections.defaultdict(set)
for (i,j) in pairs :
    overlap[i].add(j)
    overlap[j].add(i)

defaultdict(<type 'set'>, {0: set([1]), 1: set([0, 2]), 2: set([1, 3, 5]), 3: set([2, 4, 5]), 4: set([3]), 5: set([2, 3])})
{0: 10, 1: 8, 2: 6, 3: 10, 4: 8, 5: 2}

结果如下:

for partition in kWay( A, k, overlap ) :
    print partition, [ max( size[x] for x in c ) for c in partition ]
[[3, 1], [4, 2, 0], [5]] [10, 10, 2]
[[3, 1], [4, 2], [5, 0]] [10, 8, 10]
[[3, 0], [4, 2], [5, 1]] [10, 8, 8]
[[3], [4, 2, 0], [5, 1]] [10, 10, 8]
[[4, 5, 1], [3, 0], [2]] [8, 10, 6]
[[4, 5, 1], [3], [2, 0]] [8, 10, 10]
[[4, 5, 0], [3, 1], [2]] [10, 10, 6]
[[4, 5], [3, 1], [2, 0]] [8, 10, 10]

最优的3路分区是[[3, 1], [4, 2, 0], [5]],最优房间尺寸为[10, 10, 2]。您还可以直接获取所有房间的最小尺寸:

min( sum( [ max( size[x] for x in c ) for c in partition ] ) for partition in kWay( A, k, overlap ) )

22

1
考虑以下情况:
(m1) |-3-|
(m2)  |--2--|
(m3)   |--1--|
(m4)      |-1-|
(m5)       |-2-|

您的解决方案将按以下方式进行:
  1. {3}(创建第一个房间)
  2. {3, 2}(同时进行两个会议,需要第二个房间)
  3. {3, 2, 1}(同时进行三个会议,需要第三个房间)
  4. {3, 2, 1}(m1 结束,因此 m4 进入 3 号房间)
  5. {3, 2, 1, 2}(同时进行四个会议,需要第四个房间,创建与最新会议同样大小的房间)

该解决方案的累计容量为 8。

现在考虑这个解决方案:{3, 2, 1, 1}。它的累计容量为 7。
在上述的第四步中,m4 将进入未被占用的 1 号房间,而 3 号房间仍然开放。因此,m5 将进入 3 号房间。

假设

  1. 最优解按照房间数量排名,即拥有最少的房间。第二个标准是累计容量最低,即每个房间容量之和最小。
  2. 由于您的解决方案被认为是贪心的,因此在创建房间时,您将创建一个与正在评估的房间大小相同的房间。
  3. 两个会议不能同时在同一房间内,无论大小如何。

算法修改

更新: 我刚意识到即使进行这种改变,创建房间仍可能导致次优解。原因是在创建新房间之前可以调整现有房间的大小。

例如,假设我们有四个会议在四个房间中。

  • m1(大小为4)在4号房间中
  • m2(大小为2)在4号房间中
  • m3(大小为1)在2号房间中
  • m4(大小为1)在2号房间中
我们希望增加m5(大小为5)。我提出的算法修改将创建一个新的5房间,将5添加到累积容量中。但是,我们可以将m2的房间调整为5房间,让m5去那里,并为m2创建一个大小为2的新房间。这只会将2添加到累积容量中。
有人可能会想,为什么不将m2放入其中一个2房间(取代m3)并创建一个新的1房间。调整房间的大小更困难,因为我们无法保证该房间在需要它的会议开始时会开放。添加房间更容易,因为那个房间将一直存在;因为我们在算法的这一步刚刚创建了它,所以它没有被使用。
次优算法修改 如上所述,这被证明是次优的,但在我想到更好的替代方案之前,我将保留它。
要考虑上述情况,您需要在每次创建新房间时进行一些额外的工作:
  1. 查找当前所有正在进行的会议列表(包括您当前评估的会议)。
  2. 从最大的会议开始,将每个会议分配到一个房间中。
  3. 当你遇到一个不能分配的会议,而这个会议的大小正好等于你需要创建的房间大小时,你必须创建一个新的房间。

因此,在上面的示例中,当需要创建新房间时,在第5步中会应用此更改。如上述每个步骤所述:

  1. 当前所有正在进行的会议:{m2,m3,m4,m5}。记录一下,当前房间为{3,2,1}
  2. 从最大的会议开始,将每个会议分配到一个房间中{m2分配到3号房间,m5分配到2号房间,m3分配到1号房间}
  3. m4无法分配到房间。因此,我们必须为它创建一个房间。m4的大小为1,因此新房间也是大小为1。

很好,这个例子完全有道理!谢谢你指出来。我不是完全明白修改后的算法是如何工作的。可以举个例子吗?它为什么是最优的呢? - Kshitij Banerjee

1
为了确定举行所有会议所需的最小会议室数量和容量,您首先需要将这些会议可选地安排到房间中(使用最小化房间数量或容量的分数函数)。该调度(类似于课程调度)是NP完全或NP难问题。这意味着您的问题也是如此。
反过来,这意味着没有已知的算法可以优化并扩展您的问题。贪心算法(包括您的示例)不会始终是最优的(如果有更多约束条件,甚至可能不接近最优)-但至少它们会扩展:) 为了获得更好的结果(如果需要),请查看优化算法,例如元启发式算法。

-1

这是我的Java解决方案。

class Meeting{

    LocalTime start;
    LocalTime end;
    Meeting(LocalTime start, LocalTime end){
        this.start = start;
        this.end = end;
    }
}


public static int meeingRoom(List<Meeting> list){


    //use queue structure to store the room in use
    Queue<Meeting> rooms = new LinkedList<Meeting>();
    rooms.add(list.get(0)); 

    for(int i = 1; i< list.size(); i++){ 

         Meeting current = list.get(i); 

         //max: keep the max of ever occupied
         //occupied: so far occupied room

         int  max = 1, occupied = 1; 
         List<Meeting> rooms = new ArrayList<Meeting>();
         rooms.add(list.get(0));

         for(int i = 1; i< list.size(); i++){ 

            Meeting current = list.get(i); 
            int roomSize = rooms.size();

            //check all previous rooms to release finish room
            for(int j = 0; j < roomSize; j++){ 
               if(j >= rooms.size()) break;

               Meeting previous = rooms.get(j);

               if(current.start.compareTo(previous.end) >= 0){ 
                   rooms.remove(j);
               } 

           rooms.add(current); 

           //when all the rooms once occupied, all remove
           //reset the occupied
           if(rooms.size() == 1 ){ 
               max = Math.max(occupied, max);
               occupied = 1; 
           }else{ 
              occupied = Math.max(occupied, rooms.size()); 
           };


    }

    //the last time added room hasn't been check
    return Math.max(occupied, max);
}

-1
import java.util.*;
    class Codechef
     {
         //Sorting by exchange
         public static int[] Sort(int arr[],int n)
         {
             int temp=0;
             for(int i=0;i<n-1;++i)
             {
                 for(int j=i+1;j<n;++j)
                 {
                     if(arr[i]>arr[j])
                     {
                         temp=arr[i];
                         arr[i]=arr[j];
                         arr[j]=temp;
                     }
                 }
            }
            return arr;
        }
        public static void main (String[] args) throws java.lang.Exception
        {
            Scanner sc = new Scanner(System.in);
            int n=0; //n : Total number of trains arriving on the platform
            n=sc.nextInt();
            String UserInp;
            String [] inp = new String[n]; //inp[] : Accepting the user input ....Arrival time#Departure time
            int []Ar = new int[n];
            int []Dp = new int[n];
            for(int i=0;i<n;++i)
            {
                UserInp=sc.next();
                inp[i]=UserInp;
            }
            System.out.println("Displaying the input:\n");
            for(int i=0;i<n;++i)
            {
                System.out.println("inp[i] : "+inp[i]);
            }
            for(int i=0;i<n;++i)
            {
                String temp=inp[i];
                String a=temp.substring(0,2);
                String b=temp.substring(3,5);
                String c=temp.substring(6,8);
                String d=temp.substring(9);
                System.out.println("a : "+a);
                System.out.println("b : "+b);
                String x=a+b;
                Ar[i]=Integer.parseInt(x);
                System.out.println("x : "+x);
                System.out.println("c : "+c);
                System.out.println("d : "+d);
                String y=c+d;
                Dp[i]=Integer.parseInt(y);
                System.out.println("y : "+y);
            }
            System.out.println("Displaying the arrival time : ");
            for(int i=0;i<n;++i)
            {
                System.out.println(Ar[i]);
            }
            System.out.println("Displaying the departure time : ");
            for(int i=0;i<n;++i)
            {
                System.out.println(Dp[i]);
            }
            Ar=Sort(Ar,n);
            System.out.println("Displaying arrival time in ascending order :");
            for(int i=0;i<n;++i)
            {
                System.out.println(Ar[i]);
            }
            Dp=Sort(Dp,n);
            System.out.println("Displaying departure time in ascending order :");
            for(int i=0;i<n;++i)
            {
                System.out.println(Dp[i]);
            }
            int count=0;
            int need=0;
            int i=0,j=0;
            while(i<n && j<n)
            {
                if(Ar[i]<Dp[j])
                {
                    ++need;
                    if(need>count)
                    {
                        count=need;
                    }
                ++i;
            }
            else if(Ar[i]>Dp[j])
            {
                --need;
                ++j;
            }
            if(need==-1)
            {
                break;
            }
        }
        if(need!=-1)
        {
            System.out.println("Required answer : "+count);
        }
        else
        {
            System.out.println("Invalid input");
        }

    }
}

    Input:
    6
    09:00#09:10
    12:00#09:40
    09:50#11:20
    11:00#11:30
    15:00#19:00
    18:00#20:00

    Output:
    Displaying the input:

    inp[i] : 09:00#09:10
    inp[i] : 12:00#09:40
    inp[i] : 09:50#11:20
    inp[i] : 11:00#11:30
    inp[i] : 15:00#19:00
    inp[i] : 18:00#20:00
    a : 09
    b : 00
    x : 0900
    c : 09
    d : 10
    y : 0910
    a : 12
    b : 00
    x : 1200
    c : 09
    d : 40
    y : 0940
    a : 09
    b : 50
    x : 0950
    c : 11
    d : 20
    y : 1120
    a : 11
    b : 00
    x : 1100
    c : 11
    d : 30
    y : 1130
    a : 15
    b : 00
    x : 1500
    c : 19
    d : 00
    y : 1900
    a : 18
    b : 00
    x : 1800
    c : 20
    d : 00
    y : 2000
    Displaying the arrival time : 
    900
    1200
    950
    1100
    1500
    1800
    Displaying the departure time : 
    910
    940
    1120
    1130
    1900
    2000
    Displaying arrival time in ascending order :
    900
    950
    1100
    1200
    1500
    1800
    Displaying departure time in ascending order :
    910
    940
    1120
    1130
    1900
    2000
    Invalid input

    The above is a detailed solution for the approach stated in the link below:
    http://www.geeksforgeeks.org/minimum-number-platforms-required-railwaybus-station/

1
请不要只发布代码答案。添加一些解释,使其对潜在读者更有用。 - Alexei - check Codidact
@Alexei:我尝试编写代码并在程序输出中打印每个步骤,以使其对潜在读者有用。我们得到了上述结果,因为第二列车的出发时间早于到达时间,属于无效输入。 - Taruchit
没有语法高亮,很难在代码中找到注释/println语句。此外,println不能替代解释。 - Abhijit Sarkar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接