【问题标题】:What type of heap is used and time complexity of std::priority_queue in c++? [duplicate]c++中std::priority_queue使用什么类型的堆和时间复杂度? [复制]
【发布时间】:2017-05-05 09:31:28
【问题描述】:

我想知道什么

我想问以下两个问题。

  • 在 C++ 中的 std::priority_queue 中使用什么类型的堆?
  • C++ 中 std::priority_queue 的 top(), pop(), push() 操作的时间复杂度是多少?

我上网查了一下,没找到答案。
请告诉我答案。如果您不知道 C++ 的所有版本,请告诉我这个 GCC C++11 或 C++14 的答案。

我为什么需要

我想针对最短路径问题实施Dijkstra's Algorithm

number of vertex = |V|, number of edge = |E|在图中。
时间复杂度为O(|E| log |V|),使用Binary Heap
但时间复杂度为O(|E| + |V| log |V|),使用Fibonacci Heap

如您所见,如果图形密集,时间会非常不同。
其实有O(|V|^2)算法不使用堆,所以我也想知道是否必须实现。

我的实现

这是我使用Binary Heap 实现的优先级队列。
insert(x) 等于push(x)extract() 等于top() --> pop()
但是 std::priority_queue 比我的实现要快得多。

#include <vector>
#include <algorithm>
using namespace std;
template<class T> class PriorityQueue {
private:
    size_t size_; vector<T> heap;
public:
    PriorityQueue() : size_(1), heap(vector<T>(1, 0)) {};
    PriorityQueue(PriorityQueue& que) : size_(que.size_), heap(que.heap) {};
    size_t size() { return size_ - 1; }
    inline void insert(int x) {
        unsigned ptr = size_++; heap.push_back(x);
        while (ptr > 1) {
            if (heap[ptr >> 1] < heap[ptr]) swap(heap[ptr], heap[ptr >> 1]);
            ptr >>= 1;
        }
    }
    inline int extract() {
        int ret = heap[1]; unsigned ptr = 1;
        while ((ptr << 1) + 1 < size_) {
            ptr <<= 1;
            if (heap[ptr] > heap[ptr + 1]) swap(heap[ptr >> 1], heap[ptr]);
            else swap(heap[ptr + 1], heap[ptr >> 1]), ptr++;
        }
        heap[ptr] = heap[--size_], heap[size_] = 0;
        while (ptr > 1) {
            if (heap[ptr >> 1] < heap[ptr]) swap(heap[ptr], heap[ptr >> 1]);
            ptr >>= 1;
        }
        heap.pop_back();
        return ret;
    }
};

编辑:这不是 this question 的重复。只有时间复杂度。我也想知道使用的是什么类型的堆。请简单告诉我。

【问题讨论】:

  • top 和 pop 具有恒定的复杂度,而 push 具有对数复杂度。 C++ 标准没有指定应该使用什么 type 堆。它只是指定实现必须遵守的目标复杂性。
  • @ilim 没有关于什么类型的堆(例如二进制堆/斐波那契堆)的信息,所以它不是重复的。
  • @square1001 但是 std::priority_queue 比我的实现要快得多。 -- 只需查看头文件即可获得源代码。
  • @ArmenTsirunyan pop 不是恒定的
  • 二进制堆和斐波那契堆之间有什么区别(操作的时间复杂度)?如果你能回答这个问题,你可以看到 C++ 标准的要求(它可能允许任何一个),如果你不能回答这个问题 - 你为什么在乎?

标签: c++ algorithm c++11 priority-queue dijkstra


【解决方案1】:

该标准没有规定所使用的堆的具体实现,而是规定了其操作的复杂度和容器的内存复杂度。操作的复杂性如下:

  • 顶部 - O(1)
  • pop - O(log(n))
  • 推送 - O(log(n))
  • 内存复杂度 - O(n)

请注意,斐波那契和二叉堆都符合这些要求。但是从我所看到的情况来看,通常实现是binary heap

还有一个重要的评论 - 斐波那契堆确实具有更好的渐近复杂度,但它的常数因子更大,因此您需要一个相对较大的图来使其值得使用斐波那契堆而不是二叉堆。这是一个常见的错误 - 更好的渐近复杂度并不意味着该算法优于 任何 输入的替代方案。

【讨论】:

  • 查看en.wikipedia.org/wiki/Fibonacci_heap#Summary_of_running_times,似乎推送的标准要求意味着二进制或斐波那契堆都是合法的。
  • @MartinBonner 我的回答以什么方式说明了其他内容?
  • 我从来没有暗示您的回答确实说明了其他任何内容(但我明白为什么您可能认为我这样做了)。我只是把它说清楚(已经完成了研究,在这里其他任何地方都没有看到这个声明)。可能在我的评论前加上“更进一步,......”会有助于更好地定下基调。
  • @MartinBonner 我将更新我的答案以明确声明。谢谢。
  • 当前的 C++17 草案对堆 [alg.heap.operations] 的要求更加严格。要使范围 [a, b) 成为堆,现在要求 comp(a[(i-1)/2], a[i]) 为假(而之前只要求 *a 是最大的)。如果我没记错的话,这要求布局是二进制最大堆的布局。甚至在此之前,push_heap 等操作都在范围内操作,我不知道如何在这种设置下完成斐波那契堆。
【解决方案2】:

@IvayloStrandjev 的回答已经提到 C++ 标准不需要特定的实现,而是需要时间复杂度。所以这取决于实现。既然你在问题中提到了 GCC,我找到了this documentation of GCC about the implementation of priority queue

基本上它说存在多个实现:

  • 配对堆
  • 二进制堆
  • 二项式堆
  • RC 二项式堆
  • 薄堆

并且可以通过标签参数来配置使用的那个。默认标记用于配对堆。所以我猜这是 GCC 中默认使用的那个。

编辑:正如@MartinBonner 在评论中指出的那样,该链接是针对__gnu_pbds::priority_queue 而不是std::priority_queue。我以前错过了。

std::priority_queue 使用 make_heap 函数,该函数最终使用 __push_heap method from bits/stl_heap.h。快速浏览一下代码看起来像是 Binary Heap 的实现。

【讨论】:

  • 非常有趣,谢谢。如何提供标签参数?
  • @IvayloStrandjev 老实说,我不知道。还在想办法。
  • 它是构建堆时的模板参数,如文档所示。
  • 是的。我想了解的是它是否是固定的,或者我们作为 c++ 应用程序开发人员(与编译器开发人员相反)可以选择更改它。
  • 在实例化模板时(可以)指定tag参数:__gnu_pbds::priority_queue&lt;int, std::less&lt;int&gt;, __gnu_pbds::binary_heap_tag&gt; my_heap;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-01
  • 2016-03-20
  • 1970-01-01
  • 2012-04-15
  • 2012-08-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多