priority_queue的介绍
通常用堆来实现,能在O(log n)的时间复杂度内插入和提取最高(或最低)优先级的元素。
- 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的(默认情况)。
- 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
- 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
- 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素- 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
- 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
弱排序标准是一种在数学和编程中用于定义元素之间排序关系的二元关系。它要求关系满足以下三个主要性质:
1.自反性:对于任何元素a,a与自身是相等的。
2.传递性:如果a小于b,且b小于c,则a小于c。
3.连通性:对于任何两个元素a和b,要么a小于b,要么b小于a
priority_queue的使用
1.默认情况下是大堆,其底层按照less比较;若创建小堆,将第三个模板参数换成greater的比较方式;
2.如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。
数组中第k大的元素
大堆方法:第三个模板参数直接使用less排序,利用前置–的特性,k–
将优先级队列中的前k-1个元素删除掉
小堆方法:第三个模板参数要传入greater排序函数,运用topk问题思想,创建前k个元素的小堆,从数组的第k个元素开始遍历。如果当前元素大于堆顶元素(堆顶是最小值),则移除堆顶元素,并将当前元素加入堆。最后,堆顶元素即为数组中第k大的元素。
priority_queue的模拟实现
仿函数
又称函数对象,是类模板,通过重载()运算符,使得类模板的对象可以像函数一样被调用。
区分:函数模板要传对象,类模板要传参数是类型,不能加括号
向下调整
void Adjustdown(int parent)
{
Compare com;
int child = 2 * parent + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child],_con[child + 1]))
{
child++;
}
if (com(_con[parent],_con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
通过比较器 Compare 确定堆的类型。用于从父节点开始向下调整堆结构,确保堆的性质得到满足。
作用:维护堆的性质,确保插入或移除操作后堆的结构仍然有效。
应用场景:插入新元素后或移除堆顶元素后调用。
向上调整
void Adjustup(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent],_con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 2) / 2;
}
else
{
break;
}
}
}
通过比较器 Compare 确定堆的类型。用于从子节点开始向上调整堆结构,确保堆的性质得到满足。
作用:维护堆的性质,确保插入新元素后堆的结构仍然有效。
应用场景:插入新元素后调用。
构造函数
复制元素:将迭代器范围内的所有元素复制到内部容器 _con 中。
构建堆:从最后一个非叶子节点开始,运用向下调整法逐步向上调整堆结构,确保堆的性质得到满足。
删除
交换堆顶元素与末尾元素,然后调用尾删函数,或者直接size–实现删除功能,再从堆顶即下标为0的位置开始向下调整来满足堆序
插入
插入新元素后,从最后索引位置size()-1来向上调整满足堆序。
自定义类测试(日期类)
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);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
struct LessPDate
{
bool operator()(const Date* p1, const Date* p2)
{
return *p1 < *p2;
}
};
}
void test_priority_queue2()
{
priority_queue<Date*, vector<Date*>, LessPDate> pq;
pq.push(new Date(2024, 6, 7));
pq.push(new Date(2025, 1, 19));
pq.push(new Date(2025, 10, 24));
while (!pq.empty())
{
cout << *pq.top() << " ";
pq.pop();
}
cout << endl;
//测试仿函数的调用,与日期类无关
Less<int>lessfunc;
cout << lessfunc(10, 24) << endl;
cout << lessfunc.operator()(10, 24) << endl;
}
优先级队列的定义:priority_queue<Date*, vector<Date*>, LessPDate> 表示优先级队列中存储的是 Date 类型的指针,底层容器是 vector,比较器是 LessPDate。
插入元素:通过 push 方法插入三个 Date 对象。
输出元素:通过 while 循环依次输出队列中的元素,直到队列为空。
整体代码
#pragma once
#include<vector>
#include<functional>
//仿函数/函数对象
template <class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template <class T>
class Greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
namespace ee
{
template<class T,class Container=vector<T>, class Compare=less<T>>
class priority_queue
{
private:
void Adjustdown(int parent)
{
Compare com;
int child = 2 * parent + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child],_con[child + 1]))
{
child++;
}
if (com(_con[parent],_con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
void Adjustup(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent],_con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 2) / 2;
}
else
{
break;
}
}
}
public:
priority_queue()
{
}
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
first++;
}
//建堆
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
Adjustdown(i);
}
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
Adjustdown(0);
}
void push(const T& x)
{
_con.push_back(x);
Adjustup(_con.size() - 1);
}
const T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
反向迭代器
顾名思义是用于反向遍历的工具。反向迭代器通常通过容器的 rbegin() 和 rend() 方法获取。rbegin() 返回指向容器最后一个元素下一个位置的反向迭代器(end),而 rend() 返回指向容器第一个元素的反向迭代器(begin)。
namespace ee
{
template<class Iterator,class ref,class ptr>
struct ReverseIterator
{
typedef ReverseIterator<Iterator, ref, ptr> self;
Iterator _it;
ReverseIterator(Iterator it)
:_it(it)
{
}
ref operator*()
{
Iterator tmp = _it;
return *(--tmp);
}
ptr operator->()
{
//返回解引用对象的地址
return &(operator*());
}
self& operator++()//前置++
{
--_it;
return *this;
}
self& operator--()
{
++_it;
return *this;
}
bool operator!=(const self& s)const
{
return _it != s._it;
}
};
}
一般采用镜像对称的方式来模拟实现,即rbegin对应end,rend对应begin。
在重载*运算符时需要注意解引用的是迭代器当前指向的前一个位置,因为rbegin是最后元素的下一个位置,有可能为空会造成非法访问,或者是哨兵位头节点的位置。
++和–分别实现自减和自增的操作来满足反向迭代器的功能。若不使用镜像对称的方式来模拟实现反向迭代器,那么*操作符的重载就要跟随发生变化。
vector中适配
记得包含ReverseIterator.h头文件即可