Существует ли приоритетная очередь с экстрактами ?

46

Существует множество структур данных, которые реализуют интерфейс очереди приоритетов:

  • Вставить: вставить элемент в структуру
  • Get-Min: вернуть самый маленький элемент в структуре
  • Extract-Min: удалить самый маленький элемент в структуре

Распространенными структурами данных, реализующими этот интерфейс, являются (мин) кучи .

Обычно (амортизированное) время выполнения этих операций:

  • Вставьте: (иногда )O ( log n )O(1)O(logn)
  • Get-Min:O(1)
  • Extract-Min:O(logn)

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

Существует ли структура данных со следующим (амортизированным) временем выполнения?

  • Вставить:O(logn)
  • Get-Min:O(1)
  • Извлечение-Мин:O(1)

Если мы можем построить такую ​​структуру за время с учетом отсортированного ввода, то, например, мы можем найти пересечения линий на предварительно отсортированных входах с помощью пересечения строго быстрее, чем если бы мы использовали «обычные» очереди с приоритетами.o ( nO(n)o(nlogn)

Алекс тен Бринк
источник
Я думаю, что использование сбалансированного BST не сработало бы, если бы Extra-Min мог работать. Или, может быть, пропустить список.
svick
@svick: списки пропусков рандомизированы, а это не то, что я ищу. Если вы можете сделать это с помощью BST, то это здорово, но я думаю, что вам придется сделать какую- то балансировку.
Алекс тен Бринк
С другой стороны: это очень важный вопрос, и я знаю ответ, но приятно видеть, что его не так легко решить. Если кто-нибудь знает ответ, не стесняйтесь дать его :)
Алекс тен Бринк
Если вы принимаете амортизированное время обновления, то вы можете сохранить стандартные структуры кучи и вносить незначительные изменения в свой анализ. Смотрите мой ответ ниже.
Джо

Ответы:

27

Наша идея состоит в том, чтобы использовать резьбовые сплайны . Помимо статьи в Википедии, мы разбиваем деревья так, чтобы у каждого узла был указатель nextна своего преемника в порядке обхода; мы также держим указатель startна самый маленький элемент в дереве.

Легко видеть, что извлечение наименьшего элемента возможно в (наихудшем случае) времени : просто следуйте указателю, уберите минимум и измените указатель на минимум . У минимума никогда не может быть оставленного ребенка; если у него есть правильный ребенок, мы помещаем его в минимальное место на дереве. Мы не выполняем операцию splay, обычно это делают splay-деревья. Результатом является дерево поиска, которое все еще достаточно сбалансировано: поскольку мы удаляем только узлы на левом фланге, мы знаем, что когда число узлов (в затронутом поддереве) уменьшается примерно до половины исходного числа из-за удалений, ) высота дерева уменьшается на единицу.O(1)startnext

Вставки возможны в амортизированное время; зигзагообразные (и не очень) операции здесь также хорошо сбалансируют дерево.O(logn)

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

Рафаэль
источник
2
Мне не ясно, как заставить работать амортизированный анализ, если вы не добавляете на ExtractMin. Можете ли вы дать подсказку?
Jbapple
Мы не сделали это подробно. Идея состоит в том, что ряд операций извлечения-минимума не разбалансирует дерево, поэтому нет необходимости в расширении и нормальный анализ должен работать для вставок.
Рафаэль
9
Осторожный! Splay деревья не обязательно сбалансированы. Узлы, к которым долгое время не обращались, могут быть очень глубоко в дереве. Чтобы провести анализ, вы должны рассуждать с точки зрения той же потенциальной функции, используемой для анализа сплайнов.
Джефф
20
  • Вставить:O(logn)
  • Get-Min:O(1)
  • Извлечение-Мин:O(1)

Амортизированное время

Простые реализации очереди с приоритетами (например, любой сбалансированный BST или стандартная двоичная минимальная куча) могут достичь этих (амортизированных) времен выполнения, просто взимая стоимость Extract-Min для вставки и поддерживая указатель на минимальный элемент. Например, у вас может быть потенциальная функция . Затем вставка нового элемента увеличивает потенциал на , поэтому амортизированная стоимость вставки по-прежнему равна , но Extract-Min () уменьшает потенциал на , и таким образом, амортизированная стоимость составляет всего .O ( log n ) O ( log n ) Ω ( log n ) O ( 1 )cnlognO(logn)O(logn)Ω(logn)O(1)

Худший случай

Вы можете использовать существующую структуру данных в литературе: деревья поиска пальцами и просто поддерживать указатель на минимальный элемент. См. Этот обзор для обзора, а также статью Levcopoulos and Overmars 1988 года для реализуемой версии, которая соответствует вашим потребностям.

Джо
источник
1
Как очень подлый. Вы правы, наверное, я должен был потребовать что-то более сильное, чтобы исключить это. Хорошая идея :)
Алекс тен Бринк
@AlextenBrink Вы можете требовать удаления худшем случае . (Похоже, это было то, к чему стремились другие ответы). Я добавил параграф к своему ответу, чтобы рассмотреть этот случай. O(1)
Джо
14

2-4 дерева имеют амортизированные модификации в известных местах. То есть, если у вас есть указатель на какое-то место в дереве, вы можете удалить или добавить элемент там за амортизированное время.O ( 1 )O(1)O(1)

Таким образом, вы можете просто сохранить указатель на минимальный элемент и корневой узел в дереве 2-4. Вставки должны проходить через корневой узел. Обновление указателя до минимума является тривиальным после deleteMin, а deleteMins - (амортизированное) время.O(1)

Интересное примечание: красно-чёрные деревья - это просто способ посмотреть на 2-4 дерева. Разработчики стандарта C ++ 98 ожидали, что разработчики библиотек предоставят контейнер на основе красно-черного дерева, а в стандарте указано, что вставка и удаление должны иметь время амортизации в известных местах (которые они называют «итераторами»). ). Тем не менее, это на самом деле намного сложнее для красно-черных деревьев, чем для 2-4 деревьев, поскольку требует ленивой разметки узлов, которые необходимо перекрасить. Насколько мне известно, никакие реализации стандартной библиотеки C ++ 98 не удовлетворяли этому конкретному требованию.O(1)

jbapple
источник
8

По запросу, вот структура, которую я нашел после того, как сформулировал вопрос:

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

Именно эта базовая структура дает вам следующие операции:

  • Поиск: по заданному ключу возвращает указатель на соответствующий узел за время.O(logn)
  • Вставить: по заданному ключу вставляет ключ в структуру, возвращая указатель на этот узел за время.O(logn)
  • Предшественник / преемник: при наличии указателя возвращает преемника или предшественника за время.O(1)
  • Get-Min / Max: возвращает указатель на минимум или максимум.

При анализе деревьев козла отпущения, накладные расходы на удаление анализируются как , но на самом деле анализ дает накладные расходы на баланс (который игнорируется в статье поскольку они также подсчитывают время, необходимое для поиска узла, который должен быть удален). Итак, если у нас есть указатель на узел, мы можем удалить его за постоянное время (вы можете сделать это в многопоточном бинарном дереве поиска за время) и объединить с накладные расходы на балансировку, это дает время удаления:O ( 1 ) O ( log n ) O ( 1 ) O ( 1 ) O ( 1 )O(logn)O(1)O(logn)O(1)O(1)O(1)

  • Удалить: при наличии указателя удаляет узел за время.O(1)

Сочетая это:

  • Extract-Min / Max: удаляет минимальный / максимальный узел за время .O(1)

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

Некоторые другие вещи:

  • Построение: учитывая ключей в отсортированном порядке, создайте кучу козла отпущения за время.nO(n)
  • Баланс: сбалансируйте дерево так, чтобы оно сформировало идеально сбалансированное дерево двоичного поиска (сокращает накладные расходы на поиск) за время (вы можете сделать это постоянным фактором быстрее, чем, кстати, предложено в статье, используя указателей предшественника / преемника).O(n)

И, наконец, я почти уверен, что вы можете поддерживать эти операции, но мне нужно немного подумать об этом, прежде чем точно знать это:

  • Insert-New-Min / Max: учитывая ключ, который меньше / больше любого ключа, уже существующего в структуре, вставляет ключ в структуру, возвращая указатель на этот узел за время.O(1)
Алекс тен Бринк
источник
Основная идея заключается в том, что деревья козла отпущения гарантируют вам, что удаление любого узла без перебалансировки не влияет на производительность других операций в долгосрочной перспективе, даже если вы удаляете много узлов.
Рафаэль
Я знаю два способа удаления в деревьях козла отпущения. Одним из способов зеркального отражения вставок является амортизированного времени. Другой способ, о котором я слышал, использует глобальную перестройку и амортизируется , но я не знаю, как поддерживать многопоточность в этом случае. Представьте себе, что вы вставляете новый ключ в ту часть дерева, где все удаленные ключи еще не удалены. Как вы находите предшественника вставляемого ключа за времени? O(lgn)O(1)O(lgn)
Jbapple
2
@jbapple: есть два варианта, как делать удаления за времени для деревьев козла отпущения. Один из них - оставить узел, пометить его как удаленный и удалить все эти удаленные узлы с помощью глобального перестроения, а другой - действительно удалить узел. Первый легче анализировать (а также дает оценку для второго, поэтому обычно это объясняется), но второй - тот, который мне нужен: вы можете удалить за время в ванильном дереве двоичного поиска если вы можете выполнять запросы предшественника / преемника за время, а балансировка за амортизированное время дает вам остальную часть границы. O(1)O(1)O(1)O(1)
Алекс тен Бринк
Ах, теперь я понимаю.
Jbapple
2

Мягкая куча - это тонкая модификация биномиальной очереди. Структура данных является приблизительной с параметром ошибки . Он поддерживает вставку, удаление, объединение и findmin. Амортизируется сложность каждой операции , для вставки , которая принимает , за исключением время. Новизна мягкой кучи заключается в преодолении логарифмической границы сложности кучи в модели, основанной на сравнении. Чтобы преодолеть теоретико-информационный барьер, энтропия структуры данных снижается путем искусственного повышения значений некоторых ключей. Это называется повреждением ключей. Структура данных полностью основана на указателях (без массивов и числовых предположений) и является оптимальной для любого значенияϵO(1)log(1/ϵ)ϵ в модели на основе сравнения.

Приложения мягкой кучи включают в себя вычисление минимального связующего дерева для графа, динамическое ведение процентилей и статистику линейного временного порядка. Это также может быть использовано для приближенных вычислений, таких как приближенная сортировка, где ранг элемента никогда не отличается более чем на от истинного ранга.ϵn

Оригинальную, ясную и хорошо написанную статью см. В статье Бернарда Шазеля «Мягкая куча: очередь с приблизительным приоритетом с оптимальным коэффициентом ошибок», журнал ACM, 47 (6), стр. 1012-1027, 2000 . Для альтернативной реализации и анализа, который утверждает, что является более простым и интуитивно понятным из SODA'09, см. Kaplan H. & Zwick U., Более простая реализация и анализ мягких куч Хазелля, 2009 .

Юхо
источник
Хотя очень интересная структура данных, мягкие кучи не являются точными: findmin может возвращать значение, которое не является минимумом, а является лишь приблизительным минимумом. Спасибо за ссылки в любом случае :)
Алекс тен Бринк
1
@AlextenBrink: смысл структуры данных (как и многих вероятностных алгоритмов) в том, что вы можете использовать приблизительную структуру данных для получения точных ответов. Действительно, приблизительная природа мягких куч не помешала использовать его в единственном известном линейном алгоритме времени для минимального остовного дерева.
Жереми
2

Хорошо, наконец-то у вас сложность, которую вы искали, и, что лучше, я нашел ее в литературе:

Наихудшая Сложность

Удалить :O(1)

Delete-min :O(1)

Find-min :O(1)

Вставьте :O(log n)

Ссылка

Если MELD разрешено занимать линейное время, можно поддерживать DELETE-MIN в наихудшем постоянном времени, используя деревья поиска пальцев Дитца и Рамана [3]. При использовании их структуры данных MAKEQUEUE , FINDMIN , DELETEMIN , УДАЛИТЬ может поддерживаться в худшем случае время , ВСТАВИТЬ в худшем случае время и MELD в худшем случае время .O(1)O(log n)O(n)

Бродал, Герт Штелтинг. «Быстрые приоритетные очереди». В материалах 4-го международного семинара по алгоритмам и структурам данных, 282–290. WADS '95. Лондон, Великобритания, Великобритания: Springer-Verlag, 1995.

[3]: Дитц, Пол Ф. и Раджив Раман. «Дерево поиска пальца с постоянным временем обновления». Письма обработки информации 52, нет. 3 (1994): 147 - 154.

Хотя для этого используется модель вычисления RAM :

В нашей структуре данных используется модель машины с произвольным доступом (RAM) с мерой удельной стоимости и логарифмическим размером слова;

Совсем недавно была предложена модель вычислительного решения Pointer-Machine[1] .

[1]: Бродал, Герт Stølting, Джордж Lagogiannis, Christos Makris, Атанашес Тсакалидис и Костас Tsichlas. «Оптимальные деревья поиска пальцев в указателе». J. Comput. Сист. Sci. 67, нет. 2 (сентябрь 2003 г.): 381–418.

В
источник
2

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

Ω(lognloglogn)Ω(logn)

O(logn)O(logn)

nullO(logn)

O(1)O(1)

O(logn)O(1)delete_at(idx)


1 Патраску, Михай и Эрик Д. Демейн. «Логарифмические нижние границы в модели клеточного зонда». SIAM J. Comput. 35, нет 4 (апрель 2006 г.): 932–963. DOI: 10.1137 / S0097539705447256.

В
источник
1
Вы имеете в виду, что используете дерево AVL или подобное сбалансированное дерево? Как сделать так, чтобы удаление минимума не вызывало больше, чем постоянно много оборотов дальше, чем постоянно много шагов (от сайта удаления или корня)? В частности, удаление элементов из деревьев AVL может вызвать поворотов, поэтому вам нужно поспорить, как это предотвратить. O(logn)
Рафаэль
Что означает «преобразовать двоичное дерево поиска в массив»?
Jbapple
@AT: Я разделяю мнение jbapple.
Рафаэль
Хранение двоичного дерева в массиве таким способом (как классическая двоичная куча) заставляет каждый поворот занимать время, где - размер поддерева, укорененного в повернутом узле. В этом случае выполнение «только» поворотов в обновлении может занять много времени. Ω(k)kO(1)
Jbapple
Ваше обновление, в котором вы объясняете, как выполнять ротации в постоянное время, не работает в массивах. Этот ответ все еще неверен. Тарьяновская статья, на которую вы ссылаетесь, касается деревьев, хранящихся с узлами и указателями.
Jbapple
-2

Find-min в с ожидаемым временем обновленияO(1)O(log log n)

См. Статью 2007 года: Эквивалентность между приоритетными очередями и сортировкой Миккеля Торупа.

Примечание: он ссылается на статью 2002 года Хана и Торапа: Целочисленная сортировка в Ожидаемое время и линейное пространствоO(n log log n) .

В
источник
Хотя документ, который вы связали, интересен, приоритетная очередь, которую они представляют, не имеет удалений с постоянным временем (если я правильно прочитал тезисы), и, следовательно, это не то, что я прошу.
Алекс тен Бринк
-2

Анализ

Вставить :o(n log log n)

Поиск :o(log log n)

Удалить :O(1)

Пробел :O(n)

Get-Min :O(1)

Извлечение-Мин :O(1)

Реализация

  1. Формируем список из произвольного (постоянного) числа элементов, скажем, 6:O(1)
  2. Сортировать список: =O(6)O(1)
  3. Точкой вставки для каждого последующего узла будут вторые элементы (') pos (-1 для <, +1 для> и с двумя аргументами в зависимости от того, с чего вы начинаете / заканчиваете: и будет найден с использованием динамической интерполяции Поиск [1] в:k±
    ((k>nsize1)(k<n0)((k<ni)(k>ni+1)))
    o(log log n)

[1]: Андерссон, Арне и Кристер Маттссон. «Динамический интерполяционный поиск за O (log log n) Time». В «Автоматах, языках и программировании» под редакцией Анджея Лингаса, Рольфа Карлссона и Сванте Карлссона, 700: 15–27. Конспект лекций в области компьютерных наук. Springer Berlin / Heidelberg, 1993. http://dx.doi.org/10.1007/3-540-56939-1_58 .

В
источник
2
Ну, время вставки далеко от цели.
Рафаэль
Это слишком схематично. Например, что такое , , и ? n 0 n i n i + 1nsize1n0nini+1
Юхо
Читая реферат статьи, на которую вы ссылаетесь, кажется, что эти границы являются ожидаемыми границами для входных данных конкретного распределения, что, следовательно, не то, что я ищу: я хочу границы, которые я упоминаю для любого входного файла.
Алекс тен Бринк
@ Рафаэль: Нет, это не так. mrm: позиции в списке. AlextenBrink: его можно легко заменить на худший случай в любом дистрибутиве, используя алгоритм двоичного поиска, чтобы найти точку вставки. O(log n)
AT
@AT Логарифмический двоичный поиск требует произвольного доступа. Как реализован базовый список? Вы должны действительно спорить о ваших заявленных границах. Кроме того, «позиции в списке» расплывчаты - какие позиции и на что ссылаются символы? Не у всех есть доступ к бумаге, на которую вы ссылаетесь. Пожалуйста, постарайтесь сделать свой ответ (более) самостоятельным и хотя бы обобщить факты. На данный момент я не верю, что ваш ответ правильный.
Юхо