Оптимальное количество и размеры комнат для N перекрывающихся графиков встреч

Я столкнулся с этим вопросом и не уверен, что мое решение оптимально.

Проблема

Учитывая N взвешенных (Wi) и, возможно, перекрывающихся интервалов (представляющих графики встреч), найдите минимальное количество "&" вместимость конференц-залов, необходимое для проведения всех собраний.

Пример

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

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

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

Для приведенного выше графика нам потребуются два конференц-зала на 10 и 10 мест. ( я прав ? )

Мое решение

Возьмите набор комнат и просмотрите интервалы слева, если у нас есть переговорная комната с вместимостью больше, чем необходимо, используйте ее, если нет ни одной, которая соответствует критериям, создайте новую комнату или увеличьте существующие комнаты с помощью новая емкость.

Пример:

Начало 10 - {10}

Начало 8 - {10, 8}

Конец 10 - {10-бесплатно, 8}

Начало 6 - {10, 8}

Конец 8 - {10, 8-свободный}

Начало 10 = {10, 8 + = 2} ИЛИ {10, 10}

и так далее.....

это по сути жадный ..

  • Может кто докажет, что это неоптимально?
  • Что делать, если это неоптимально? DP?

person Kshitij Banerjee    schedule 09.07.2014    source источник
comment
Две небольшие встречи могут или не могут проводиться в одной комнате в одно и то же время?   -  person libik    schedule 09.07.2014
comment
Что важнее для оптимизации, количество комнат? Вы также можете работать с 3-мя комнатами вместимостью 10, 8, 6. Таким образом, вам не нужны две большие комнаты.   -  person Henry    schedule 09.07.2014
comment
@Генри. Количество комнат важнее вместимости   -  person Kshitij Banerjee    schedule 10.07.2014
comment
@libik .. две встречи не могут быть в одной комнате.   -  person Kshitij Banerjee    schedule 10.07.2014


Ответы (6)


Интуиция

Я дам ему попробовать. Наивный подход - перечислить все возможные решения и выбрать лучший. Имея это в виду, поиск k комнат, в которых можно разместить n собрания, эквивалентен поиску k-разделенного n точек. Пример 2-стороннего разделения 5 собраний - это [ 0,2,4 ] и [ 1,3 ] в примере OP:

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

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

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

Итак, основная идея состоит в том, чтобы перечислить все k участников n собраний с ограничением, что два перекрывающихся собрания не могут принадлежать одному кластеру. Например, [ 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] ] ]

Это выглядит немного сложным, но на самом деле это довольно просто, когда вы понимаете, что это просто рекурсивный алгоритм для kway разбиения + 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
person usual me    schedule 11.07.2014

Я считаю, что эта проблема эквивалентна задаче «Минимальное количество платформ, необходимых для железнодорожного / автобусного вокзала».

Эта статья http://www.geeksforgeeks.org/minimum-number-platforms-required-railwaybus-station/ хорошо объясняет, как к ней подойти.

person alampada    schedule 23.08.2015

Рассмотрим этот сценарий:

(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.
На шаге (4) выше m4 перейдет в незанятую 1-комнатную, а 3-комнатную все еще будет открытой. Таким образом, это то, куда пойдет m5.

Сделанные предположения

  1. Оптимальное решение сначала оценивается по количеству комнат: в нем будет наименьшее количество комнат. Второй критерий - наименьшая совокупная вместимость: сумма вместимости каждой комнаты.
  2. Поскольку ваше решение определяется как жадное, когда вам нужно создать комнату, вы создадите одну из размеров оцениваемой комнаты.
  3. Два собрания не могут проводиться в одной комнате одновременно, независимо от размера.

Изменение алгоритма

Обновление: я только что понял, что даже с этим изменением создание комнаты может привести к неоптимальным решениям. Причина в том, что перед созданием новой комнаты можно изменить размер существующих комнат.

В качестве примера предположим, что у нас четыре собрания в четырех комнатах.

  • м1 (размер 4) находится в 4-х комнатном
  • м2 (размер 2) находится в 4-х комнатном
  • м3 (размер 1) находится в 2-х комнатном
  • м4 (размер 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.
person Romojr50    schedule 09.07.2014
comment
Великолепный пример имеет смысл! Спасибо, что заметили это. Я не совсем понимаю, как работают измененные алгоритмы. Может быть, пример? а насколько это оптимально? - person Kshitij Banerjee; 10.07.2014

Чтобы найти минимальное количество и вместимость конференц-залов, необходимых для проведения всех совещаний, вам сначала необходимо запланировать эти совещания по желанию в комнатах (с функцией оценки, которая минимизирует количество комнат). Это расписание (аналогично планированию курса) является NP-полным или NP-сложным. Значит, ваша проблема тоже.

Это, в свою очередь, означает, что не существует известного алгоритма решения вашей проблемы, который был бы оптимальным и масштабируемым. Жадные алгоритмы (включая ваш пример) не будут неизменно оптимальными (или даже близкими к оптимальным, если у вас больше ограничений), но, по крайней мере, они будут масштабироваться :) Чтобы получить еще лучшие результаты (при необходимости), изучите алгоритмы оптимизации, такие как метаэвристика.

person Geoffrey De Smet    schedule 11.07.2014

Вот мое решение от 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);
}
person Wei-Hsuan Chou    schedule 13.10.2017

person    schedule
comment
Пожалуйста, не размещайте код, только ответ. Добавьте пояснения, чтобы сделать его более полезным для потенциальных читателей. - person Alexei - check Codidact; 05.01.2017
comment
@Alexei: Я попытался написать код вместе с комментариями и распечатать каждый шаг в выводе программы, чтобы сделать его полезным для потенциальных читателей. Мы получаем вышеуказанный результат как недопустимый ввод, потому что время отправления было раньше времени прибытия второго поезда. - person Taruchit; 06.01.2017
comment
Нелегко обнаружить комментарии / операторы println в коде без выделения синтаксиса. Кроме того, println не заменяет объяснения. - person Abhijit Sarkar; 10.03.2019