什么更快:插入优先级队列,还是追溯排序?

时间:2010-09-21 09:52:50

标签: c++ sorting complexity-theory priority-queue

什么更快:插入优先级队列,还是追溯排序?

我正在生成一些我需要在最后排序的项目。我想知道,在复杂性方面更快的是:将它们直接插入priority_queue或类似的数据结构中,或者在结尾处使用排序算法?

10 个答案:

答案 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的书"启发式搜索:理论与应用"有以下方便的表格显示各种优先级队列算法的时间复杂度(请记住,优先级队列相当于排序和堆管理):

Priority Queue Time Complexities

正如您所看到的,许多优先级队列的O(log n)成本不仅仅用于插入,还用于提取,甚至是队列管理!虽然通常会丢弃系数来测量算法的时间复杂度,但这些成本仍然值得了解。

但是所有这些队列仍然具有可比较的时间复杂性。哪个最好?由Cris L. Luengo Hendriks撰写的2010年论文题为"重新审视图像分析的优先级队列"解决了这个问题。

Hold Times for Priority Queues

在亨德里克斯'保持测试,优先级队列使用 [0,50] 范围内的 N 随机数播种。然后队列的最顶层元素出列,在 [0,2] 范围内增加一个随机值,然后排队。该操作重复 10 ^ 7 次。从测量的时间中减去产生随机数的开销。通过此测试,梯形队列和分层堆很好地完成了。

还测量了每个元素初始化和清空队列的时间 - 这些测试与您的问题非常相关。

Per-Element Enqueue and Dequeue Times

正如您所看到的,不同的队列通常对入队和出队的反应非常不同。这些数字意味着虽然可能存在优先于连续操作的优先级队列算法,但是没有最佳选择算法来简单地填充然后清空优先级队列(您正在进行的操作)。

让我们回顾你的问题:

什么更快:插入优先级队列,还是追溯排序?

如上所示,可以提高优先级队列的效率,但仍需要插入,删除和管理的成本。插入矢量很快。它的摊销时间是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)

这个问题有很多很好的答案。合理的“经验法则”是

  • 如果所有元素都在“前面”,则选择排序。
  • 如果您要“动态”添加元素/删除最少的元素,请使用优先级队列(例如堆)。

对于第一种情况,最好的“最坏情况”排序还是堆排序,并且您只关注排序即可获得更好的缓存性能(即,而不是与其他操作交错)