priority_queue模拟实现
- 1. priority_queue介绍
- 2. priority_queue使用
- 2.1 priority_queue显示定义
- 2.2priority_queue接口使用
- 3. 仿函数
- 4. priority_queue模拟实现
- 4.1 向上调整算法
- 4.2 向下调整算法
- 4.3 实现priority_queue的接口
- 4.4 使用[仿函数](https://legacy.cplusplus.com/reference/functional/less/?kw=less)控制[priority_queue](https://legacy.cplusplus.com/reference/queue/priority_queue/?kw=priority_queue)默认大小堆
- 4.5 priority_queue模拟实现代码
1. priority_queue介绍
优先级队列是一种容器适配器,优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。
注意:默认情况下priority_queue是大堆。
2. priority_queue使用
2.1 priority_queue显示定义
priority_queue默认vector作为容器,默认情况下为大堆。
//默认
priority_queue<int> p1;
//显示为大堆
priority_queue<int, vector<int>, less<int>> p2;
若是容器为vector,结构为小堆
priority_queue<int, vector<int>, greater<int>> p3;
2.2priority_queue接口使用
成员函数 | 说明 |
---|---|
push | 往优先级队列中尾插数据 |
pop | 删除堆顶元素 |
top | 获取堆顶元素 |
empty | 判断优先级队列是否为空 |
size | 获取优先级队列的元素个数 |
示例:
void test()
{priority_queue<int> pq;pq.push(1);pq.push(3);pq.push(4);pq.push(5);pq.push(2);pq.push(6);pq.push(7);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}
3. 仿函数
在我们模拟实现priority_queue前,还需要了解仿函数。
仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
示例:
struct Less
{bool operator()(const int& val1, const int& val2){return val1 < val2;}};int main()
{Less lessfunc;cout << lessfunc(1, 2) << endl;cout << lessfunc.operator()(1, 2) << endl;//显示调用cout << Less()(1, 2) << endl;//匿名对象return 0;
}
4. priority_queue模拟实现
priority_queue底层默认为堆,所以我们需要清楚向上调整算法和向下调整算法(这里以大堆例)。
4.1 向上调整算法
堆的向上调整算法思想(大堆):
- 目标结点与父结点相比较
- 如果目标结点大于父结点,目标结点就与父结点交换位置,后用当前新的位置作为目标结点继续向上调整。如果目标结点小于父结点,则停止向上调整。此时堆为大堆。
例子: 我们往堆中插入数据89
与父结点相比较大于父结点就交换,继续向上调整。
数据89大于88交换。
直到符合条件为止。
代码:
void adjust_up(size_t child)
{size_t parent = (child - 1) / 2;while (child > 0){if (c[child]>c[parent]){swap(c[child], c[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
4.2 向下调整算法
堆的向下调整算法思想(大堆):
- 目标结点与子结点比较
- 若是子结点大于目标结点,则与子结点交换位置。用当前新的位置作为目标结点继续向下调整。若是子结点小于目标结点,则停止向下调整,此时堆为大堆。
示例:
此时堆的左右子树都为大堆,因此我们可以将父结点向下调整。
判断子节点与当前位置数据的大小,大于则交换位置。
继续判断,向下调整。
直到符合目标结点大于子结点为止,此时堆为大堆。
代码:
void adjust_down(size_t parent)
{size_t child = parent * 2 + 1;while (child < c.size()){//检查左结点是否大于右结点if (child + 1 < c.size() && c[child+1]>c[child]){child++;}if (c[child]>c[parent]){swap(c[child], c[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
4.3 实现priority_queue的接口
bool empty() const{return c.empty();}size_t size() const{return c.size();}const T& top() {return c[0];}void push(const T& x){c.push_back(x);adjust_up(c.size()-1);}void pop(){swap(c[0], c[size() - 1]);c.pop_back();adjust_down(0);}
4.4 使用仿函数控制priority_queue默认大小堆
我们通过查阅文档可以发现,c++通过改变仿函数对priority_queue队列的默认大小堆的控制。
优势: 相比于函数指针,仿函数更加方便简单,更加利于控制(只需要跟换默认的仿函数)。
4.5 priority_queue模拟实现代码
namespace bit
{template <class T>struct less{bool operator()(const T& val1,const T& val2){return val1 < val2;}};template <class T>struct greater{bool operator()(const T& val1, const T& val2){return val1 > val2;}};template <class T, class Container = vector<T>, class Compare = less<T> >//这里默认容器是vector 因为要把优先级队列看成一个大堆,堆的物理结构是有序结构。class priority_queue{public:void adjust_up(size_t child){Compare com;size_t parent = (child - 1) / 2;while (child > 0){if (com(c[parent],c[child])){swap(c[child], c[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent){size_t child = parent * 2 + 1;while (child < c.size()){Compare com;if (child + 1< c.size() && com(c[child],c[child+1])){child++;}if (com(c[parent], c[child])){swap(c[child], c[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}bool empty() const{return c.empty();}size_t size() const{return c.size();}const T& top() {return c[0];}void push(const T& x){c.push_back(x);adjust_up(c.size()-1);}void pop(){swap(c[0], c[size() - 1]);c.pop_back();adjust_down(0);}private:Container c;Compare comp;};
};