本期主题:priority_queue的介绍和模拟实现
博客主页: 小峰同学
分享小编的在Linux中学习到的知识和遇到的问题
小编的能力有限,出现错误希望大家不吝赐

priority_queue介绍和使用
1.1.priority_queue介绍
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素 (优先队列中位于顶部的元 素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特 定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板, 也可以是其他特定设计的容器类。容器应该可以通过随机访问迭 代器访问, 并支持以下操作: empty():检测容器是否为空 size():返回容器中有效元素个数 front():返回容器中第一个元素的引用 push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指 定容器类,则使用vector,一般不会使用deque,因为deque的随机访问,效率不高.建堆效率很低.
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap和pop_heap来自动完成此操作。

1.2.priority_queue使用

priority_queue的接口很简单,但是他的结构很厉害.
比如在使用堆排序,topK 问题的时候 ,是无可替代的.
模拟实现
直接上源码
namespace zxf
{
//仿函数
template<class T>
struct lass
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<class T>
struct greater
{
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
//优先级队列其实就是一个堆
//底层也是一种适配器模式,可以使用 vector 或者 deque 来适配。
//priority_queue的底层数据结构必须满足可以随机访问,
//所以 只有 vector 和 deque,满足。
template<class T,class container = vector<T>, class compare = lass<T>>//默认使用 lass 建立大堆
class priority_queue
{
container _con;
public:
//向上调整
void adjust_up(size_t child)
{
compare com;
size_t parent;
while (child > 0){//注意这里 child 不能等于 0
parent = (child - 1) / 2;
if (com( _con[parent] ,_con[child])){
std::swap(_con[child], _con[parent]);
child = parent;
}
else{
break;
}
}
}
//向下调整
void adjust_down(size_t parent)
{
//parent*2+1=left_child
//parent*2+2=right_child
compare com;
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && _con[child + 1] > _con[child])
{
child++;
}
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else{
break;
}
}
}
priority_queue() {}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
//while (first != last)
//{
// push(*first);
// ++first;
//}
//也可以
//_con(first, last);
//(child-1) / 2 = parent
for (int i = (_con.size() - 1 - 1) / 2 ; i >= 0;i--)
{
adjust_down(i);
}
}
void push(const T& x){
_con.push_back(x);
//从最后一个元素的位置开始向上调整。
adjust_up(_con.size() - 1);
}
void pop(){
//首尾 交换
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
//堆顶的元素不允许修改,因为修改可能会导致结构破坏。
const T& top()const
{
return _con[0];
}
};
}
常见问题
3.1.仿函数
仿函数,顾名思义就是仿照的函数.通常情况下会
//仿函数
//其实是一个类
//他的实例化出来的对象,可以像函数一样去使用。
namespace zxf
{
template<class T>
struct lass
{
bool operator()(const T& x,const T& y)
{
return x < y;
}
};
template<class T>
struct greater
{
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
}
这个仿函数(类),实例化出来的对象可以像函数一样去使用
int main()
{
//需要先给模板显式实例化一下。
//greater是类名(类模板),greater<int> 是一个类型。
zxf::greater<int> greaterfunc;
//greaterfunc 是类型实例化出来的一个对象。其实和 vector<int> v; 相同。
zxf::lass<int> lassfunc;
//只不过这个自定义类型里面没有成员变量,是有一个函数调用访问符() 的重载。
cout << greaterfunc(2, 1) << endl;//1
cout << greaterfunc.operator()(2, 1) << endl;//1
cout << lassfunc(2, 5) << endl;//1
cout << lassfunc.operator()(2, 5) << endl;//1
//这里不是
return 0;
}
在priority_qeueue底层实现的时候就用到了仿函数 lass 和 greater 来控制建立大堆还是小堆.
3.2.如果在priority_queue中放自定义类型的数据
用户需要在自定义类型中提供> 或者< 的重载。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue()
{
// 大堆,需要用户在自定义类型中提供<的重载
priority_queue<Date> q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout << q1.top() << endl;
// 如果要创建小堆,需要用户提供>的重载
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
cout << q2.top() << endl;
}
这里重载> 和 < 是为了供 函数greater或者lass 里面调用的.
注意 :
我们知道假如堆里面的每一个元素是自定义类型,我们只需要在类里面 重载 < 和> 即可.
但是如果是一个自定义的指针我们怎么办.
假如里面模板实例化的类型是Date* (自定义类型的指针) 的类型怎么办.
这个也简单,就是默认使用的是库里面的仿函数lass 和 greater ,底层就是通过 运算符< 和> 来比较的,但是我们自己写一个仿函数传给他,按照我们自己的意愿来比较就可以.
template <class T>
struct Date_lass
{
bool operator()(const T& t1 , const T& t2)
{
return (*t1) < (*t2);//直接复用Date类里面的重载即可。
}
};
template <class T>
struct Date_greater
{
bool operator()(const T& t1, const T& t2)
{
return (*t1) > (*t2);//直接复用Date类里面的重载即可。
}
};
void TestPriorityQueue()
{
//我们可以看到这里每个堆元素是 Date* (日期类的指针)
//如果我们还继续使用 库函数给我提供的 ;lass 和 greater 仿函数
//直接就是地址相比。不是我们想要的效果。
zxf:: priority_queue<Date*, vector<Date*>, Date_lass<Date*>> q1;//建立大堆
q1.push(new Date(2018, 10, 29));
q1.push(new Date(2018, 10, 28));
q1.push(new Date(2018, 10, 30));
cout << *(q1.top()) << endl;
zxf::priority_queue<Date*, vector<Date*>, Date_greater<Date*>> q2;//建立小堆
q2.push(new Date(2018, 10, 29));
q2.push(new Date(2018, 10, 28));
q2.push(new Date(2018, 10, 30));
cout << *(q2.top()) << endl;
}
int main()
{
TestPriorityQueue();
return 0;
}