В настоящее время я столкнулся с сложной проблемой сортировки. У меня есть набор событий, которые нужно отсортировать друг относительно друга (сортировка сравнения) и их относительное положение в списке.
Проще говоря, у меня есть список событий, каждое из которых имеет приоритет (целое число), продолжительность (секунды) и время самого раннего появления, когда событие может появиться в списке. Мне нужно отсортировать события по приоритету, но ни одно событие не может появиться в списке до самого раннего момента его возникновения. Вот пример, чтобы (надеюсь) прояснить ситуацию:
// 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 = новое событие {duration = 4.0, earliestTime = 0.0}; Событие b = новое событие {duration = 5.0, earliestTime = 6.0};





Я думаю, вам следует отсортировать список дважды: сначала по приоритету, а затем по самому раннему времени, используя любой алгоритм сортировки стабильный, например сортировку вставкой. Таким образом, время будет увеличиваться, и каждый раз вещи будут сортироваться по приоритету.
Если вы не видите чего-то, чего я не вижу, вы можете полностью игнорировать продолжительность каждого события для целей сортировки.
http://en.wikipedia.org/wiki/Category:Stable_sorts
Я не могу игнорировать продолжительность, так как текущее время увеличивается по мере того, как я двигаюсь вперед, в список можно добавлять разные события.
Значит, события не могут происходить параллельно?
Думаю, нет, поэтому, если у вас есть много задач с низким приоритетом, которые могут начинаться с 0, но одна задача с высоким приоритетом, которая должна начинаться с t = 4, вы можете сначала запланировать только несколько задач с низким приоритетом.
Другими словами, вы хотите оптимизировать общее время выполнения, формулируя два ограничения (сильное: самая ранняя точка выполнения, слабое: приоритет)? Это называется проблема удовлетворения ограничений. Для такого рода задач есть специальные решения.
Кстати, решение jakber не работает. Даже без учета продолжительности следующий пример явно не работает:
event a (priority = 1, start = 5)
event b (priority = 2, start = 0)
Отсортированная последовательность будет a, b, в то время как желаемый результат обязательно будет b, a.
Спасибо, но вторая сортировка будет отсортирована по времени начала в моем решении, тем самым получая b, a. Если событие не может произойти параллельно, мое решение не работает, поэтому я думаю, что вы все равно правы.
На самом деле это больше, чем проблема сортировки. Это проблема планирования одной машины с датами выпуска. В зависимости от того, что вы пытаетесь сделать, проблема может быть 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
}
}
Одна из проблем заключается в том, что у вас может быть низкоприоритетное задание со временем выпуска непосредственно перед коротким высокоприоритетным заданием, но оно будет создавать расписание со свойством, что нет пропусков (если возможно расписание без пропусков) .
Что вы имеете в виду под «одной машиной»? В отличие от проблемы типа распределенных вычислений?
Я имею в виду, что это похоже на планирование заданий на одном процессоре. Я не имею в виду код, который вы напишете для решения этой проблемы.
Я подозреваю, что ты прав. Но это может зависеть от того, что взвешивание всегда должно начинать с высокого приоритета как можно скорее (мой ответ) или всегда избегать пробелов, которые могут позволить другое линейное решение.
А может не линейно, но хотя бы П.
Можете ли вы порекомендовать какие-либо ресурсы для решения такой проблемы? (Кто-то еще разместил ссылку на страницу проблемы удовлетворения ограничений в Википедии.)
Похоже, вам действительно нужна сортировка на основе сравнения. Ваш ключ сортировки - {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;
}
Проверьте это против любых предположений, но я думаю, что это то, что мы ищем.
Это не сработает, если у вас много задач с низким приоритетом, которые задерживают задачу с высоким приоритетом, которая может начаться позже.
Это не может быть сортировка, основанная на прямом сравнении. Вы не можете решить, является ли событие Event1 <Event2 или Event2 <Event1, если вы не знаете, что происходит перед Event1 и Event2.
Я думаю, вы оба имеете в виду продолжительность более ранних событий с более низким приоритетом, чем отсрочку начала более поздних событий с более высоким приоритетом. Я предполагал, что многопоточный подход запускает события в свое время (независимо от того, что уже было запущено), но я отредактирую, чтобы предположить однопоточный подход.
Если у вас ограниченный набор уровней приоритета, вы можете сохранить набор отсортированных по времени списков, по одному на каждый уровень. Всякий раз, когда вам нужно следующее событие, проверяйте заголовок каждого списка в порядке приоритета, пока не найдете то, время начала которого прошло. (Следите за минимальным временем начала, пока вы проверяете - если событие еще не готово, вы знаете, какое из них ждать)
Я думаю:
Преобразуйте временную шкалу в список задач и ждите (для пробелов).
Вопросов:
Редактировать:
О, ответы на мои вопросы таковы:
В этом случае алгоритм может быть таким:
Этот алгоритм также O (n ^ 2).
В худшем случае это будет O (n ^ 2), не так ли?
Ага, O (n ^ 2) я думаю. Все же лучше, чем НП.
Ваш алгоритм - это метод сортировки вставкой, который я описал. (Приятно видеть, что кто-то другой приходит к аналогичной идее.)
Между прочим, в самом общем случае решения может не быть (если, как указал Дуглас, не допускаются пробелы). Например:
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, и все события будут запланированы.)
Я не совсем уверен, что понимаю всю сложность вашей проблемы, но мой инстинкт подсказывает мне, что вам нужно определить взаимосвязь между приоритетом и временем начала. Примером может быть:
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».
Вот код 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
Вопрос: Допускаются ли пробелы? Вы хотите их заполнить? т.е. хотите ли вы a, d, b, c в качестве решения, которое гарантирует, что b произойдет при t = 6, а не при t = 7?