Что быстрее: вставка в очередь приоритетов или сортировка ретроспективно?
Что быстрее: вставка в очередь приоритетов или сортировка ретроспективно?
Я создаю некоторые элементы, которые мне нужно отсортировать в конце. Мне было интересно, что быстрее с точки зрения сложности: вставка их непосредственно в priority_queue или аналогичная структура данных или с использованием алгоритма сортировки в конце?
Ответы
Ответ 1
Вставка n элементов в очередь приоритета будет иметь асимптотическую сложность O (n log n), поэтому с точки зрения сложности она не будет более эффективной, чем использование sort
один раз, в конце.
На самом деле это зависит от его эффективности. Тебе нужно протестировать. Фактически, на практике даже продолжительная вставка в линейный массив (как при сортировке вставки, без создания кучи) может быть наиболее эффективной, хотя асимптотически она имеет худшее время исполнения.
Ответ 2
Это, вероятно, доходит до вас немного поздно в игре, насколько ваш вопрос, но пусть будет полным.
Тестирование - лучший способ ответить на этот вопрос для конкретной компьютерной архитектуры, компилятора и реализации. Кроме того, существуют обобщения.
Во-первых, очереди приоритетов не обязательно O (n log n).
Если у вас есть целочисленные данные, есть очереди приоритетов, которые работают в O (1) раз. Публикация Beucher and Meyer 1992 "Морфологический подход к сегментации: преобразование водоразделов" описывает иерархические очереди, которые довольно быстро работают для целочисленных значений с ограниченным диапазоном. Brown 1988 публикация "Календарные очереди: быстрая реализация очереди 0 (1) для задачи набора симуляции" предлагает еще одно решение, которое отлично справляется с более широкими диапазонами целых чисел - два десятилетия работы после публикации в Brown опубликовало несколько приятных результатов для выполнения целого числа очереди приоритетов. Но механизм этих очередей может усложниться: сортировка ведра и сортировка по методу рад может по-прежнему обеспечивать работу O (1). В некоторых случаях вы даже можете квантовать данные с плавающей запятой, чтобы воспользоваться очередью приоритетов O (1).
Даже в общем случае данных с плавающей запятой O (n log n) мало вводит в заблуждение. Книга Edelkamp "Эвристический поиск: теория и приложения" имеет следующую удобную таблицу, показывающую сложность времени для различных алгоритмов очереди приоритетов (помните, приоритетные очереди эквивалентны сортировке и управлению кучей):
![Priority Queue Time Complexities]()
Как вы можете видеть, во многих очередях приоритетов O (log n) стоит не только для вставки, но также для извлечения и даже управления очередью! Хотя коэффициент обычно снижается для измерения временной сложности алгоритма, эти затраты все еще стоит знать.
Но у всех этих очередей все еще есть сложности времени, которые сопоставимы. Что лучше? В документе 2010 года Cris L. Luengo Hendriks, озаглавленном "Пересмотр очередей приоритетов для анализа изображений", рассматривается этот вопрос.
![Hold Times for Priority Queues]()
В тесте удержания Хендрикса приоритетная очередь была засеяна N случайными числами в диапазоне [0,50]. Самый верхний элемент очереди был затем удален, увеличен на случайное значение в диапазоне [0,2], а затем поставлен в очередь. Эта операция повторялась 10 ^ 7 раз. Накладные расходы на создание случайных чисел были вычтены из измеренных времен. Тесты лестницы и иерархические кучи выполнялись достаточно хорошо.
Время элемента для инициализации и опорожнения очередей также было измерено --- эти тесты очень важны для вашего вопроса.
![Per-Element Enqueue and Dequeue Times]()
Как вы можете видеть, в разных очередях часто возникали очень разные ответы на очереди и деактивацию. Эти цифры подразумевают, что, хотя могут существовать алгоритмы приоритетной очереди, которые являются превосходными для непрерывной работы, нет лучшего выбора алгоритма для простого заполнения, а затем для опорожнения очереди приоритетов (операция, которую вы выполняете).
Оглянитесь на свои вопросы:
Что быстрее: вставка в очередь приоритетов или сортировка ретроспективно?
Как показано выше, приоритетные очереди могут быть эффективными, но по-прежнему существуют затраты на вставку, удаление и управление. Вставка в вектор выполняется быстро. Это O (1) в амортизированном времени, и нет никаких затрат на управление, плюс вектор O (n) для чтения.
Сортировка вектора будет стоить вам O (n log n), если у вас есть данные с плавающей запятой, но на этот раз сложность не скрывала такие вещи, как очереди приоритетов. (Тем не менее, вы должны быть немного осторожны. Quicksort очень хорошо работает с некоторыми данными, но имеет худшую временную сложность O (n ^ 2). Для некоторых реализаций это серьезный риск для безопасности.)
Я боюсь, что у меня нет данных о стоимости сортировки, но я бы сказал, что ретроактивная сортировка отражает суть того, что вы пытаетесь сделать лучше, и поэтому лучший выбор. Исходя из относительной сложности управления очередью приоритетов и пост-сортировки, я бы сказал, что пост-сортировка должна быть быстрее. Но опять же, вы должны проверить это.
Я создаю некоторые элементы, которые мне нужно отсортировать в конце. Мне было интересно, что быстрее с точки зрения сложности: вставка их непосредственно в очередь приоритетов или аналогичную структуру данных или с помощью алгоритма сортировки в конце?
Мы, вероятно, рассмотрели это выше.
Тем не менее, другой вопрос вы не задавали. И, возможно, вы уже знаете ответ. Это вопрос стабильности. С++ STL говорит, что очередь приоритетов должна поддерживать "строгий слабый" порядок. Это означает, что элементы равного приоритета несравнимы и могут быть размещены в любом порядке, а не в "общем порядке", где каждый элемент сопоставим. (Здесь есть приятное описание порядка здесь.) При сортировке "строгий слабый" аналогичен неустойчивой сортировке, а "полный порядок" аналогичен стабильный вид.
Результат состоит в том, что если элементы одного и того же приоритета должны оставаться в том же порядке, что и вы вставляете их в свою структуру данных, вам нужен стабильный вид или общий порядок. Если вы планируете использовать С++ STL, у вас есть только один вариант. Приоритетные очереди используют строгий слабый порядок, поэтому они бесполезны здесь, но алгоритм "stable_sort" в библиотеке алгоритмов STL выполнит свою работу.
Надеюсь, это поможет. Дайте мне знать, если вы хотите получить копию любой из упомянутых статей или хотите получить разъяснения.: -)
Ответ 3
Зависит от данных, но обычно я вставляю InsertSort быстрее.
У меня был связанный с ним вопрос, и я обнаружил, что в конечном итоге узким местом было то, что я делал дефферированную сортировку (только когда мне это было нужно) и на большом количестве предметов я обычно был в худшем случае -scenario для моего QuickSort (уже по порядку), Итак, я использовал сортировку вставки
Сортировка 1000-2000 элементов со многими промахами кеша
Итак, проанализируйте свои данные!
Ответ 4
К вашему первому вопросу (который быстрее): это зависит. Просто проверьте это. Предполагая, что вы хотите получить конечный результат в векторе, альтернативы могут выглядеть примерно так:
#include <iostream>
#include <vector>
#include <queue>
#include <cstdlib>
#include <functional>
#include <algorithm>
#include <iterator>
#ifndef NUM
#define NUM 10
#endif
int main() {
std::srand(1038749);
std::vector<int> res;
#ifdef USE_VECTOR
for (int i = 0; i < NUM; ++i) {
res.push_back(std::rand());
}
std::sort(res.begin(), res.end(), std::greater<int>());
#else
std::priority_queue<int> q;
for (int i = 0; i < NUM; ++i) {
q.push(std::rand());
}
res.resize(q.size());
for (int i = 0; i < NUM; ++i) {
res[i] = q.top();
q.pop();
}
#endif
#if NUM <= 10
std::copy(res.begin(), res.end(), std::ostream_iterator<int>(std::cout,"\n"));
#endif
}
$ g++ sortspeed.cpp -o sortspeed -DNUM=10000000 && time ./sortspeed
real 0m20.719s
user 0m20.561s
sys 0m0.077s
$ g++ sortspeed.cpp -o sortspeed -DUSE_VECTOR -DNUM=10000000 && time ./sortspeed
real 0m5.828s
user 0m5.733s
sys 0m0.108s
Итак, std::sort
бьет std::priority_queue
, в этом случае. Но, может быть, у вас есть лучшее или худшее std:sort
, и, возможно, у вас есть лучшая или худшая реализация кучи. Или, если не лучше или хуже, просто более или менее подходит для вашего точного использования, которое отличается от моего изобретенного использования: "создайте отсортированный вектор, содержащий значения".
Я могу с большой уверенностью сказать, что случайные данные не попадут в худший случай std::sort
, поэтому в некотором смысле этот тест может его обольстить. Но для хорошей реализации std::sort
его худший случай будет очень сложно построить, и на самом деле это может быть не так уж плохо.
Изменить: я добавил использование мультимножества, так как некоторые люди предложили дерево:
#elif defined(USE_SET)
std::multiset<int,std::greater<int> > s;
for (int i = 0; i < NUM; ++i) {
s.insert(std::rand());
}
res.resize(s.size());
int j = 0;
for (std::multiset<int>::iterator i = s.begin(); i != s.end(); ++i, ++j) {
res[j] = *i;
}
#else
$ g++ sortspeed.cpp -o sortspeed -DUSE_SET -DNUM=10000000 && time ./sortspeed
real 0m26.656s
user 0m26.530s
sys 0m0.062s
К вашему второму вопросу (сложности): все они O (n log n), игнорируя детали нереальной реализации, такие как распределение памяти O (1) или нет (vector::push_back
и другие формы вставки в конце амортизируется O (1)) и полагая, что под "сортировкой" вы подразумеваете сортировку. Другие виды сортировки могут иметь более низкую сложность.
Ответ 5
Насколько я понимаю, ваша проблема не требует Priority Queue, так как ваши задачи звучат так: "Сделайте много вставок, после этого сортируйте все". Это как стрельба птиц с лазера, а не подходящий инструмент. Для этого используйте стандартные методы сортировки.
Вам понадобится очередь приоритетов, если ваша задача должна имитировать последовательность операций, где каждая операция может быть либо "Добавить элемент в набор", либо "Удалить наименьший/наибольший элемент из набора". Это может быть использовано, например, при поиске кратчайшего пути на графике. Здесь вы не можете просто использовать стандартные методы сортировки.
Ответ 6
Я думаю, что вставка более эффективна почти во всех случаях, когда вы генерируете данные (т.е. не имеете ее в списке).
Приоритетная очередь не является вашим единственным вариантом для вставки при прохождении. Как упоминалось в других ответах, бинарное дерево (или связанное с ним дерево RB) одинаково эффективно.
Я бы также посмотрел, как реализована очередь приоритетов - многие из них основаны на b-деревьях, но несколько реализаций не очень хороши в извлечении элементов (они, по сути, проходят всю очередь и ищут наивысший приоритет).
Ответ 7
Очередь приоритетов обычно реализуется как куча. Сортировка с использованием кучи в среднем медленнее, чем quicksort, за исключением того, что quicksort имеет худшую производительность в худшем случае. Кроме того, кучи представляют собой относительно тяжелые структуры данных, поэтому на них больше накладных расходов.
Я бы посоветовал сортировать в конце.
Ответ 8
Почему бы не использовать двоичное дерево поиска? Затем элементы сортируются во все времена, а затраты на вставку равны очереди приоритетов.
Читайте о сбалансированных деревьях RedBlack здесь
Ответ 9
В операциях очереди приоритетов max-insert есть O (lg n)