什么更快:插入优先级队列,还是追溯排序?
我正在生成一些我需要在最后排序的项目。我想知道,在复杂性方面更快的是:将它们直接插入priority_queue或类似的数据结构中,或者在结尾处使用排序算法?
答案 0 :(得分:75)
就你的问题而言,这可能会在游戏中稍晚一些,但让我们完成。
测试是针对特定计算机体系结构,编译器和实现回答此问题的最佳方法。除此之外,还有一些概括。
首先,优先级队列不一定是O(n log n)。
如果您有整数数据,则有优先级队列在O(1)时间内工作。 Beucher和Meyer的1992年出版物"分割的形态学方法:分水岭变换"描述了分层队列,它对于范围有限的整数值非常快速地工作。 Brown的1988年出版物"日历队列:模拟事件集问题的快速0(1)优先级队列实现"提供另一种解决方案,可以很好地处理更大范围的整数 - 在布朗的出版物之后的二十年工作已经为完成整数优先级队列 fast 产生了一些不错的结果。但是这些队列的机制可能变得复杂:桶类和基数类仍然可以提供O(1)操作。在某些情况下,您甚至可以量化浮点数据以利用O(1)优先级队列。
即使在浮点数据的一般情况下,O(n log n)也有一点误导性。 Edelkamp的书"启发式搜索:理论与应用"有以下方便的表格显示各种优先级队列算法的时间复杂度(请记住,优先级队列相当于排序和堆管理):
正如您所看到的,许多优先级队列的O(log n)成本不仅仅用于插入,还用于提取,甚至是队列管理!虽然通常会丢弃系数来测量算法的时间复杂度,但这些成本仍然值得了解。
但是所有这些队列仍然具有可比较的时间复杂性。哪个最好?由Cris L. Luengo Hendriks撰写的2010年论文题为"重新审视图像分析的优先级队列"解决了这个问题。
在亨德里克斯'保持测试,优先级队列使用 [0,50] 范围内的 N 随机数播种。然后队列的最顶层元素出列,在 [0,2] 范围内增加一个随机值,然后排队。该操作重复 10 ^ 7 次。从测量的时间中减去产生随机数的开销。通过此测试,梯形队列和分层堆很好地完成了。
还测量了每个元素初始化和清空队列的时间 - 这些测试与您的问题非常相关。
正如您所看到的,不同的队列通常对入队和出队的反应非常不同。这些数字意味着虽然可能存在优先于连续操作的优先级队列算法,但是没有最佳选择算法来简单地填充然后清空优先级队列(您正在进行的操作)。
让我们回顾你的问题:
什么更快:插入优先级队列,还是追溯排序?
如上所示,可以提高优先级队列的效率,但仍需要插入,删除和管理的成本。插入矢量很快。它的摊销时间是O(1),并且没有管理成本,加上向量是要读取的O(n)。
假设您有浮点数据,对向量进行排序将花费您O(n log n),但这次的复杂性并没有隐藏优先级队列之类的东西。 (你必须要小心一点.Quicksort在某些数据上运行得很好,但它的最坏情况时间复杂度为O(n ^ 2)。对于某些实现,这是一个严重的安全风险。)
我担心我没有关于分拣成本的数据,但我要说追溯分拣抓住了你尝试做得更好的本质,因此更好的选择。基于优先级队列管理与后期排序的相对复杂性,我要说后期排序应该更快。但同样,你应该测试一下。
我正在生成一些我需要在最后排序的项目。我想知道,在复杂性方面哪些更快:将它们直接插入优先级队列或类似的数据结构,或者在结尾处使用排序算法?
我们可能已经涵盖了上述内容。
但是你还没有问过另一个问题。也许你已经知道了答案。这是一个稳定的问题。 C ++ STL表示优先级队列必须保持严格的弱"订购。这意味着具有相同优先级的元素是无法比拟的,并且可以按任何顺序放置,而不是总顺序"每个元素都具有可比性。 (这里有一个关于排序here的很好的描述。)在排序中,"严格的弱"类似于不稳定的排序和总排序"类似于稳定的类别。
结果是,如果具有相同优先级的元素应保持相同的顺序,则将它们推送到数据结构中,那么您需要稳定的排序或总顺序。如果您打算使用C ++ STL,那么您只有一个选项。优先队列使用严格的弱排序,所以它们在这里没用,但是#34; stable_sort" STL算法库中的算法将完成工作。
我希望这会有所帮助。如果您想要提及任何论文的副本或希望澄清,请与我们联系。 : - )
答案 1 :(得分:21)
将 n 项插入优先级队列将具有渐近复杂度O( n log n ),因此就复杂性而言,它不是更多最后一次使用sort
一次效率很高。
它在实践中是否更有效取决于它。你需要测试。事实上,在实践中,即使将插入继续插入线性数组(如插入排序,而不构建堆)也可能是最有效的,即使渐渐地更糟运行时。
答案 2 :(得分:5)
取决于数据,但我通常认为InsertSort更快。
我有一个相关的问题,我发现最终的瓶颈只是我做了一个默认的排序(只有当我最终需要它时)和大量的项目,我通常有最坏的情况-scenario for my QuickSort(已按顺序),所以我使用了插入排序
Sorting 1000-2000 elements with many cache misses
分析您的数据!
答案 3 :(得分:5)
对你的第一个问题(更快):这取决于。试试吧。假设您希望最终结果在向量中,替代方案可能如下所示:
#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
的良好实施,其最糟糕的情况将很难构建,并且可能实际上并不是那么糟糕。
编辑:我添加了一个multiset的使用,因为有些人建议了一个树:
#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))并假设通过“排序”你的意思是比较排序。其他类型的排序可以具有较低的复杂性。
答案 4 :(得分:2)
据我所知,您的问题不需要优先级队列,因为您的任务听起来像“多次插入,然后排序所有内容”。这就像用激光射击鸟类,而不是合适的工具。使用标准排序技术。
如果您的任务是模仿一系列操作,则需要优先级队列,其中每个操作可以是“向集合中添加元素”或“从集合中删除最小/最大元素”。例如,这可以用于在图上找到最短路径的问题。在这里,您不能只使用标准排序技术。
答案 5 :(得分:1)
我认为在几乎所有生成数据的情况下插入都会更有效(例如,在列表中没有插入数据)。
优先级队列不是您随时插入的唯一选项。如在其他答案中所提到的,二叉树(或相关的RB树)同样有效。
我还会检查优先级队列是如何实现的 - 很多都是基于b树的,但是有些实现不是很好地提取元素(它们基本上遍历整个队列并寻找最高优先级)。
答案 6 :(得分:1)
优先级队列通常作为堆实现。使用堆进行排序平均比快速排序慢,除了快速排序具有更差的最坏情况性能。堆也是相对繁重的数据结构,因此有更多的开销。
我最后会推荐排序。
答案 7 :(得分:1)
为什么不使用二叉搜索树?然后,元素始终排序,插入成本等于优先级队列。 阅读有关RedBlack平衡树here
的信息答案 8 :(得分:0)
在max-insert优先级队列上,操作是O(lg n)
答案 9 :(得分:0)
这个问题有很多很好的答案。合理的“经验法则”是
对于第一种情况,最好的“最坏情况”排序还是堆排序,并且您只关注排序即可获得更好的缓存性能(即,而不是与其他操作交错)