我有一个我想用来创建堆的向量。我不确定是否应该使用C ++ make_heap函数或将我的向量放在优先级队列中?在性能方面哪个更好?我应该何时使用其中一个?
答案 0 :(得分:29)
性能方面没有区别。 std::priority_queue
只是一个适配器类,它将容器和与堆相关的函数调用包装在一个类中。 std::priority_queue
的规范公开声明。
通过从公开的std::vector
构建基于堆的优先级队列(通过直接调用与堆相关的函数),可以保持对外部访问的可能性,从而可能破坏堆/队列的完整性。 std::priority_queue
充当了限制访问“规范”最低要求的屏障:push()
,pop()
,top()
等。您可以将其视为自律执行措施。< / p>
此外,通过使您的队列接口适应“规范”操作集,您可以使其统一并与符合相同外部规范的其他基于类的优先级队列实现互换。
答案 1 :(得分:4)
priority_queue(至少通常)实现为堆。因此,真正的问题是priority_queue是否提供了您所需要的。使用make_heap时,您仍然可以访问所有元素。当您使用priority_queue时,您只有少数操作提供对元素的非常有限的访问权限(基本上只是插入一个项目,并删除队列头部的项目)。
答案 2 :(得分:1)
priority_queue
不是容器。它是一个使用特定底层容器的容器适配器,例如vector
或deque
,并提供了一组特定的方法来处理数据。此外,它的实现依赖于*_heap
算法。
例如,无论何时将新值推送到vector
,都应调用push_heap
来扩展被视为堆的范围。如果您不使用priority_queue
,它允许您将vector
的一半视为堆(std::make_heap(v.begin(), v.begin() + (v.size() / 2))
),而另一半将按原样考虑。< / p>
当你在其上调用priority_queue
时push
做了什么:它将一个新元素推送到底层容器的后面并调用push_heap
以保持堆属性的优先级(它只对第一个元素是最伟大的。)
我想说你最好考虑解决方案设计和你的具体要求,而不是性能问题。
答案 3 :(得分:1)
C ++ 11标准
C++11 N3337 standard draft指定在{23.6.4.1的syntax of gensim
的构造函数中使用std::make_heap
priority_queue构造函数”:
明确的priority_queue
2效果:用x和c用y初始化comp(适当的复制构造或移动构造); 调用make_heap(c.begin(),c.end(),comp)。
其他方法说:
void push(const value_type&x);
效果: c.push_back(x); push_heap(c.begin(),c.end(),comp)
但是,从更新的n4724开始,非构造方法的措辞变为“仿佛”,因此我认为不能保证对std::priority_queue
方法的实际调用,而只是对其功能行为的保证。
逐步调试到*_heap
6.4 stdlibc ++源中,以确认g++
转发到priority_queue
在Ubuntu 16.04默认的make_heap
软件包或GCC 6.4 build from source上,您无需任何进一步设置即可直接进入C ++库。
使用该代码,我们可以轻松地确认g++-6
只是带有基础std::priority_queue
的{{1}}系列的包装,这意味着性能将是相同的。
a.cpp:
std::make_heap
编译和调试:
std::vector
现在,如果您首先进入构造函数#include <cassert>
#include <queue>
int main() {
std::priority_queue<int> q;
q.emplace(2);
q.emplace(1);
q.emplace(3);
assert(q.top() == 3);
q.pop();
assert(q.top() == 2);
q.pop();
assert(q.top() == 1);
q.pop();
}
,它将首先进入g++ -g -std=c++11 -O0 -o a.out ./a.cpp
gdb -ex 'start' -q --args a.out
构造函数,因此我们已经可以猜测std::priority_queue<int> q
包含vector
。
现在,我们在GDB中运行std::priority_queue
来找到队列构造函数,然后再次进入,这将导致我们进入实际的队列构造函数std::vector
:
finish
显然,这只是转发到/usr/include/c++/6/bits/stl_queue.h
对象顶部的443 explicit
444 priority_queue(const _Compare& __x = _Compare(),
445 _Sequence&& __s = _Sequence())
446 : c(std::move(__s)), comp(__x)
447 { std::make_heap(c.begin(), c.end(), comp); }
。
因此,我们在std::make_heap
中打开源文件并找到c
的定义:
vim
,因此我们得出结论,c
是 template<typename _Tp, typename _Sequence = vector<_Tp>,
typename _Compare = less<typename _Sequence::value_type> >
class priority_queue
{
[...]
_Sequence c;
。
如果我们采用其他方法,或者通过进一步检查源代码,我们很容易看到所有其他c
方法也都可以用于vector
函数家族
选择堆还是说说均衡的BST是有意义的,因为堆的平均插入时间更短,请参见:Heap vs Binary Search Tree (BST)
答案 4 :(得分:0)
如果您不想修改该向量,则应使用priority queue
,因为它会创建单独的向量。但是,如果你有能力编辑它,那么显然使用make_heap
会更好,因为它不会创建辅助空间并就地修改该向量,因此可以节省空间。而且,优先级队列易于实现。例如,在弹出元素时使用make_heap时,必须使用两个命令,首先是pop_heap
然后是pop_back
..但是在优先级队列的情况下,只能使用一个命令。同样,将元素推入堆中。
现在,两者的性能都是相同的,因为优先级队列不是容器,它使用一些底层容器作为vector或deque,它使用make_heap操作所使用的相同堆操作。所以,你应该使用一个取决于你的要求。
答案 5 :(得分:0)
make_heap允许以封装为代价的灵活性,例如,打印出堆。
make_heap的一种有趣用法是就地合并排序,它在合并的一侧使用make_heap,以实现n / 2(log(n / 2))的最坏情况。
此示例说明了如何使用输入向量并打印出创建的堆:
#include <queue>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void print(string prefix,vector<int>& v)
{
cout << prefix;
for(int i : v)
cout << i << " ";
cout << endl;
}
int main()
{
vector<int> v={1,2,9,0,3,8,4,7,1,2,9,0,3,8,4,7};
typedef priority_queue< int,vector<int>,greater<int> > MinQ;
MinQ minQ(v.begin(),v.end()); //minQ
print("After priority_queue constructor: ",v);
make_heap(v.begin(),v.end(),greater<int>());
print("After make_heap: ", v);
return 0;
}
输出:
After priority_queue constructor: 1 2 9 0 3 8 4 7 1 2 9 0 3 8 4 7
After make_heap: 0 1 0 1 2 3 4 7 2 3 9 8 9 8 4 7