Алгоритм сортировки для задачи сортировки, не основанной на сравнении?

В настоящее время я столкнулся с сложной проблемой сортировки. У меня есть набор событий, которые нужно отсортировать друг относительно друга (сортировка сравнения) и их относительное положение в списке.

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

// 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», даже если его приоритет ниже. (Надеюсь, это объясняет мою проблему, если не дайте мне знать, и я отредактирую ее.)

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

Я надеюсь найти ресурсы об алгоритмах сортировки и структурах данных, которые помогут мне разработать хорошее решение для такого рода проблем. Моя настоящая проблема на самом деле более сложна, чем эта: иерархическая сортировка, переменные буферы между событиями, множественные непостоянные временные ограничения, поэтому чем больше информации или идей, тем лучше. Скорость и пространство на самом деле не имеют значения. Точность сортировки и ремонтопригодность кода вызывают беспокойство.

Редактировать: Разъяснения (на основе комментариев)

  • События занимают всю свою продолжительность (то есть перекрытие событий не допускается).
  • События должен происходят в самое раннее время или после него, они не могут произойти раньше своего самого раннего времени.
  • События могут происходить позже их самого раннего времени, если существуют события с более низким приоритетом.
  • События не могут быть прерваны
  • Существует максимальная продолжительность - сумма всех событий, которые могут поместиться в список. Это не показано выше. (На самом деле продолжительность всех событий будет намного больше максимальной продолжительности временного списка.)
  • Пробелов быть не может. (Нет дыр, которые можно было бы заполнить заново.)

Редактировать: Ответ

В то время как Дэвид Нехме дал ответ, который я выбрал, я хотел указать, что его ответ - это сортировка вставками в глубине души, и несколько других людей предоставили ответы типа вставки сортировки. Это подтверждает для меня, что специализированная сортировка вставками, вероятно, лучший вариант. Спасибо всем за ответы.

Вопрос: Допускаются ли пробелы? Вы хотите их заполнить? т.е. хотите ли вы a, d, b, c в качестве решения, которое гарантирует, что b произойдет при t = 6, а не при t = 7?

Douglas Leeder 18.11.2008 00:59

# Максимальная продолжительность - сумма всех событий, которые могут поместиться в список. >> что это значит. Некоторые мероприятия не будут запланированы?

David Nehme 18.11.2008 04:29

# Пробелов быть не может. (Нет дыр, которые можно было бы залить заново.) Вы уверены, что это возможно? Например, как раз эти два события. Событие a = новое событие {duration = 4.0, earliestTime = 0.0}; Событие b = новое событие {duration = 5.0, earliestTime = 6.0};

David Nehme 18.11.2008 04:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
17
3
2 078
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Я думаю, вам следует отсортировать список дважды: сначала по приоритету, а затем по самому раннему времени, используя любой алгоритм сортировки стабильный, например сортировку вставкой. Таким образом, время будет увеличиваться, и каждый раз вещи будут сортироваться по приоритету.

Если вы не видите чего-то, чего я не вижу, вы можете полностью игнорировать продолжительность каждого события для целей сортировки.

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

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

ARKBAN 18.11.2008 00:51

Значит, события не могут происходить параллельно?

jakber 18.11.2008 00:53

Думаю, нет, поэтому, если у вас есть много задач с низким приоритетом, которые могут начинаться с 0, но одна задача с высоким приоритетом, которая должна начинаться с t = 4, вы можете сначала запланировать только несколько задач с низким приоритетом.

Douglas Leeder 18.11.2008 00:56

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

Кстати, решение jakber не работает. Даже без учета продолжительности следующий пример явно не работает:

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

Отсортированная последовательность будет a, b, в то время как желаемый результат обязательно будет b, a.

Спасибо, но вторая сортировка будет отсортирована по времени начала в моем решении, тем самым получая b, a. Если событие не может произойти параллельно, мое решение не работает, поэтому я думаю, что вы все равно правы.

jakber 18.11.2008 00:55
Ответ принят как подходящий

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

1|ri;pmtn|Σ wiCi 

и является NP-трудным. По этой теме существует множество документы, но это может быть больше, чем вам нужно.

В вашем случае вам никогда не нужно решение с пробелами, поэтому вам может просто понадобиться простая симуляция дискретного события (O (n log (n))) времени. Вам нужно сохранить Release_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
    }
}

Одна из проблем заключается в том, что у вас может быть низкоприоритетное задание со временем выпуска непосредственно перед коротким высокоприоритетным заданием, но оно будет создавать расписание со свойством, что нет пропусков (если возможно расписание без пропусков) .

Что вы имеете в виду под «одной машиной»? В отличие от проблемы типа распределенных вычислений?

ARKBAN 18.11.2008 00:52

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

David Nehme 18.11.2008 00:58

Я подозреваю, что ты прав. Но это может зависеть от того, что взвешивание всегда должно начинать с высокого приоритета как можно скорее (мой ответ) или всегда избегать пробелов, которые могут позволить другое линейное решение.

Douglas Leeder 18.11.2008 01:07

А может не линейно, но хотя бы П.

Douglas Leeder 18.11.2008 01:13

Можете ли вы порекомендовать какие-либо ресурсы для решения такой проблемы? (Кто-то еще разместил ссылку на страницу проблемы удовлетворения ограничений в Википедии.)

ARKBAN 18.11.2008 02:04

Похоже, вам действительно нужна сортировка на основе сравнения. Ваш ключ сортировки - {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;
}

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

Это не сработает, если у вас много задач с низким приоритетом, которые задерживают задачу с высоким приоритетом, которая может начаться позже.

Douglas Leeder 18.11.2008 01:00

Это не может быть сортировка, основанная на прямом сравнении. Вы не можете решить, является ли событие Event1 <Event2 или Event2 <Event1, если вы не знаете, что происходит перед Event1 и Event2.

Federico A. Ramponi 18.11.2008 01:06

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

P Daddy 18.11.2008 01:11

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

Я думаю:

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

Преобразуйте временную шкалу в список задач и ждите (для пробелов).

Вопросов:

  1. Допускаются ли пробелы?
  2. Можно ли разделять задачи?
  3. Учитывая задачи, как в вопросе: лучше отложить b до завершения c или сделать d, чтобы b мог начаться вовремя?

Редактировать:

О, ответы на мои вопросы таковы:

  1. Нет (иш - если нечего бежать, я думаю, у нас может быть пробел)
  2. Нет
  3. Все еще не ясно, но я предполагаю, что пример предлагает запустить c и задержать b.

В этом случае алгоритм может быть таким:

  1. Сортировать по приоритету
  2. Вести счетчик текущего времени, начиная с t = 0
  3. Найдите в отсортированном списке элемент с наивысшим приоритетом, который можно запустить в t.
  4. Добавьте элемент в текущий порядок и добавьте его продолжительность к t.
  5. Повторяйте 3 и 4, пока список не будет исчерпан. Если в момент t нет задач, которые можно запустить, и есть задачи, остающиеся отложенными, вставьте 1-секундную задачу в спящий режим в порядке выполнения.

Этот алгоритм также O (n ^ 2).

В худшем случае это будет O (n ^ 2), не так ли?

Federico A. Ramponi 18.11.2008 01:07

Ага, O (n ^ 2) я думаю. Все же лучше, чем НП.

Douglas Leeder 18.11.2008 01:11

Ваш алгоритм - это метод сортировки вставкой, который я описал. (Приятно видеть, что кто-то другой приходит к аналогичной идее.)

ARKBAN 18.11.2008 02:03

Между прочим, в самом общем случае решения может не быть (если, как указал Дуглас, не допускаются пробелы). Например:

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 };

Ты прав. (В реальной задаче время начала списка будет установлено на 4.0, и все события будут запланированы.)

ARKBAN 18.11.2008 01:15

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

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

Итак, будем ли мы запускать b в момент времени = 0 или ждать галочки, а затем запускать a, потому что это более высокий приоритет? Предположим, было больше событий с большим количеством приоритетов и более долгими временными компромиссами. Я думаю, вам нужно правило типа «если следующее событие имеет на X более высокий приоритет, а промежуток (между настоящим моментом и самым ранним временем) составляет менее Y секунд, подождите, а затем запустите событие с более высоким приоритетом. В противном случае запустите низкоприоритетный» событие (тем самым отодвигая высокоприоритетное) ".

Вы могли бы продолжить с событием «b» и отложить «a» до тех пор, пока не будет выполнено «b».

ARKBAN 18.11.2008 01:44

Вот код 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

Другие вопросы по теме