priority_queue概述
优先级队列也是一个容器适配器,它的底层数据结构是用堆来实现的,用数组来模拟堆,所以它的底层容器也就是vector,后面是一个仿函数,大概就像sort函数一样,你可以提供自己的比较函数。
priority_queue默认数值更大的优先级更高。
当然我们也可以指定小的优先级更高,第三个参数指定为greater就行
priority_queue<int,vector<int>,greater<int>> pq;
这个greater在头文件functional中
priority_queue模拟实现
#pragma once
#include <vector>
using namespace std;
namespace csy {
template <class T,class Container=vector<T>>
class priority_queue {
public:
//如果自己写了构造函数,编译器将不会调用默认构造函数
//所以还要自己定义一个无参的默认构造函数
priority_queue()
{
}
//迭代器区间构造函数
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
push(*first);
first++;
}
for (int i = (_con.size() - 1 - 1) / 2; i > 0; --i)
{
adjust_down(i);
}
}
void adjust_up(size_t child)
{
size_t parent = (child-1)/2;
while (child > 0)
{
if (_con[parent] < _con[child])
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1)/2;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void adjust_down(size_t parent)
{
size_t child = 2 * parent + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && _con[child + 1] > _con[child])
{
child++;
}
if (_con[parent] < _con[child])
{
swap(_con[parent], _con[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top() const
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
}
这里还不够,我们这个模拟实现只能建大堆,库里面为了能随意转换大小堆,模板里面还有一个参数——仿函数
仿函数
仿函数/函数对象 – 类,重载operator()
类对象可以像函数一样去使用
template<class T>
class less
{
public:
bool operator()(const T& l, const T& r) const
{
return l < r;
}
};
less(1,2);
template <class T,class Container=vector<T>,class Compare=less<T>>
less虽然是一个对象,但是却可以像函数一样被调用,这种类就叫仿函数。
于是我们可以在优先队列的模板中添加参数,注意这里的参数是一个类型,所以我们添加的是一个仿函数类型而不是一个具体的对象
在private中添加一个仿函数对象就行,然后把函数中比较的地方换成仿函数调用。
新代码:
#pragma once
#include <vector>
using namespace std;
namespace csy {
template <class T,class Container=vector<T>,class Compare=less<T>>
class priority_queue {
public:
//如果自己写了构造函数,编译器将不会调用默认构造函数
//所以还要自己定义一个无参的默认构造函数
priority_queue()
{
}
//迭代器区间构造函数
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
push(*first);
first++;
}
for (int i = (_con.size() - 1 - 1) / 2; i > 0; --i)
{
adjust_down(i);
}
}
void adjust_up(size_t child)
{
size_t parent = (child-1)/2;
while (child > 0)
{
//if (_con[parent] < _con[child])
if(_cmp(_con[parent] , _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1)/2;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void adjust_down(size_t parent)
{
size_t child = 2 * parent + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && _cmp(_con[child] , _con[child+1]))
{
child++;
}
//if (_con[parent] < _con[child])
if(_cmp(_con[parent] , _con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top() const
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
Compare _cmp;
};
}
还有一个小问题,我们查看priority文档,会发现它有这样一个构造函数
explicit priority_queue (const Compare& comp = Compare(),
const Container& ctnr = Container());
这个构造函数首先需要明确的是传进去的是一个具体的Compare对象,跟algorithm中的sort函数类似
但是非常非常重要一点的是,我们需要知道,前者是在构造函数传的引用,用来构造这个优先队列对象的,你传进去的这个参数可能会在其他成员函数中被继续使用,比如push或者pop函数,而sort只是一个函数,传进去的对象也只有自己使用,出了函数被销毁了也无所谓,所以我们经常传进去一个匿名对象,但需要注意的是,构造priority_queue对象时,千万不能传匿名的Compare对象,这个匿名对象在构造函数结束后就失效了,那么在push或者pop的时候可能就会出现野指针,程序就崩溃了。
同时还要注意的是,我们是这样使用这个函数的:
std::priority_queue<int> pq(ls);
模板参数因为你只传了int,所以其他两个默认就是vector和less,所以构造函数参数也必须是less,否则就冲突了。
至于为什么是const对象,自然是怕你修改,如果在函数内Compare对象被修改了,也可能会出现未知的问题。
priority_queue类模板中仿函数与大小堆的关系
其实就是你如果想要大堆的话,那么拍出来的序列就是升序,那么对于这个序列中的两个值来说,这个仿函数应该满足,对于给定的要比较的两个值,如果a在这个序列中在b的前面,那么就返回true,否则返回false
所以为什么我们的优先级队列默认是大堆,并且传Less呢,因为大堆就是升序,所以对于给定的两个数,如果a在b的前面,就是a<b,就要返回true,所以需要用less