目录
1.priority_queue的局部模拟实现
(1)模拟实现的函数种类
(2)局部模拟实现
[1]priority_queue类的模板参数
[2]成员变量
[3]堆的向下调整函数
[4]堆的向上调整函数
[5]无参构造
[6]区间构造
[7]插入新元素
[8]删除队头元素(堆顶元素)
[9]获取队头元素的引用
[10]获取优先队列中元素个数
[11]优先队列判空
2.priority_queue的整体模拟实现
上一篇博客讲解了priority_queue的认识与使用,这篇博客带来的是priority_queue的模拟实现。在上一篇博客中我们也详细讲解了存储自定义类型的对象或者自定义类型对象的地址时应该如何实现比较器类。
因此这篇博客为了方便起见,我们模拟实现的是存储内置类型的priority_queue。如果读者需要模拟实现存储自定义类型对象的priority_queue,可以去上一篇博客看一下比较类的实现即可。同时有关于概念性的知识点我们就不再赘述了,因为在上一篇博客写的非常详细,不清楚的可以看一下。博客链接:http://t.csdn.cn/osbWU
同时,因为priority_queue可以当作堆使用,因此我们需要用堆构造的方法来构造priority_queue的结构,因此需要掌握堆的相关知识,特别是堆的向下调整和向上调整算法。不熟悉的可以看这篇博客,里面也写得非常详细。博客链接:http://t.csdn.cn/udeOQ
本文在win10系统的vs2019上验证。
如下是priority_queue中经常使用的函数,我们将这些使用频率较高的函数模拟实现即可。
函数说明 | 功能说明 |
priority_queue(const Compare& comp = Compare(), const Container& ctnr = Container()); |
构造函数,当不传递参数时,括号内的参数就用默认值填充。 此时底层空间是vector,按大堆存储 |
priority_queue(InputIterator first, InputIterator last, const Compare& comp = Compare(), const Container& ctnr = Container()); |
区间构造函数 |
push(const value_type& val) | 插入元素 |
void pop() | 删除队头元素 |
const value_type& top() const | 返回队头元素的引用(队头元素不可以修改,队头元素也就是堆顶元素) |
bool empty() const | 判断队列是否是空 |
跟语法规定中的参数列表相似,我们也给出了三个参数,含义也是一样的。同时也将默认的底层结构设置为vector,将默认的比较方式设置为less,默认构造大堆。
//设置模板参数,默认以vector为底层结构,用less比较类构造大堆
template ,typename Compare = less>
成员变量只需要一个就好,那就是底层结构的对象,这里采用的底层结构是vector,后面模拟实现的那些函数只需要复用vector类的函数即可。
class Priority_Queue {
private:
//创建底层结构的对象
Container con;
};
这里的比较器对象具体原理在上一篇博客介绍过,这里就不多说了,这里来谈一下比较器对象在这里的使用。
比较器对象在这里被当作仿函数使用,com(con[child], con[child + 1]) ,这个表达式的参数顺序不可以随意调换。
com(con[child], con[child + 1]) :这个仿函数的参数左边是左孩子,右边是右孩子,顺序不可以搞错。因为如果比较器是less类,左边小于右边就会返回true,用小于的比较方式,创建大堆;如果是比较器是greater类,左边大于右边才会返回true,用大于的方式,创建小堆。这里一旦把参数顺序传递错误,结果可就完全不同了。
因为在堆的博客中详细讲解了调整算法,这里就不多赘述了,如果不了解可以去看一下。
//堆的向下调整算法
void AdjustDown(size_t parent) {
size_t child = parent * 2 + 1;
size_t size = con.size();
//创建比较器对象
Compare com;
while (child < size) {
if (child + 1 < size && com(con[child], con[child + 1])) {
child += 1;
}
if (com(con[parent], con[child])) {
swap(con[parent], con[child]);
parent = child;
child = parent * 2 + 1;
}
else {
return;
}
}
}
这里的仿函数使用的注意事项跟向下调整函数中一样,千万不可以把参数顺序搞错。
//堆的向上调整算法
void AdjustUp(size_t child) {
size_t parent = (child - 1) / 2;
//创建比较器对象
Compare com;
while (child) {
if (com(con[parent], con[child])) {
swap(con[parent], con[child]);
child = parent;
parent = (child - 1) / 2;
}
else {
return;
}
}
}
//无参构造
Priority_Queue()
:con()
{}
因为优先队列中可能用各种类型的元素来进行区间构造,所以区间构造函数也是模板。在初始化列表会复用底层结构vector的区间构造函数,然后在函数体中需要从第一个非叶子结点开始进行向上调整,将优先队列构造成堆。
//区间构造
//数组数据传入后需要从第一个非叶子结点开始进行向下调整
template
Priority_Queue(Iterator first, Iterator last)
:con(first,last)
{
for (int i = (con.size() - 2) / 2; i >= 0; i--) {
AdjustDown(i);
}
}
插入新元素是复用底层结构vector的尾插函数,插入新元素后可能会破坏堆的结构,所以需要从新元素的位置开始进行向上调整修复堆结构。
//给堆中插入元素(在优先队列队尾将元素入队列)
void Push(const T& value) {
con.push_back(value);
//插入元素可能破坏堆结构
//要向上调整修复堆结构
AdjustUp(con.size()-1);
}
弹出堆顶元素时要先判断优先队列中是否是空,不空才可以弹出元素。交换堆顶元素和最后一个元素后,复用底层结构vector的尾删函数,这样就把堆顶的那个元素删除了。但这样就会造成堆的特殊情况(特殊情况在堆的那篇博客中讲解了),为了解决特殊情况,需要向下调整。
//将堆顶元素弹出(优先队列的队头元素出队)
void Pop() {
//优先队列非空才可以弹出队头元素
if (Empty()) {
return;
}
//交换堆顶元素和最后一个元素
swap(con.front(), con.back());
//用vector类中的尾删函数删除最后一个元素
con.pop_back();
//删除堆顶元素后堆的结构可能被破坏
//使用向下调整修复堆结构
AdjustDown(0);
}
注意堆顶元素不可以修改,因此返回类型是const类型的引用,复用vector的front函数即可。
//返回优先队列队头元素的引用
//因为队头元素(即堆顶元素)不可以被修改
//所以要返回const类型的引用
const T& Top()const {
return con.front();
}
复用vector的size函数即可。
//统计优先队列中元素个数
size_t Size() {
return con.size();
}
复用vector的判空函数即可。到这是不是发现特别方便,大多数函数只需要复用底层结构的函数即可。
//判断优先队列是否是空
bool Empty() {
return con.empty();
}
#include "iostream"
#include "vector"
using namespace std;
//设置模板参数,默认以vector为底层结构,用less比较类构造大堆
template ,typename Compare = less>
class Priority_Queue {
private:
//创建底层结构的对象
Container con;
//堆的向下调整算法
void AdjustDown(size_t parent) {
size_t child = parent * 2 + 1;
size_t size = con.size();
//创建比较器对象
Compare com;
while (child < size) {
if (child + 1 < size && com(con[child], con[child + 1])) {
child += 1;
}
if (com(con[parent], con[child])) {
swap(con[parent], con[child]);
parent = child;
child = parent * 2 + 1;
}
else {
return;
}
}
}
//堆的向上调整算法
void AdjustUp(size_t child) {
size_t parent = (child - 1) / 2;
//创建比较器对象
Compare com;
while (child) {
if (com(con[parent], con[child])) {
swap(con[parent], con[child]);
child = parent;
parent = (child - 1) / 2;
}
else {
return;
}
}
}
public:
//无参构造
Priority_Queue()
:con()
{}
//区间构造
//数组数据传入后需要从第一个非叶子结点开始进行向下调整
template
Priority_Queue(Iterator first, Iterator last)
:con(first,last)
{
for (int i = (con.size() - 2) / 2; i >= 0; i--) {
AdjustDown(i);
}
}
//给堆中插入元素(在优先队列队尾将元素入队列)
void Push(const T& value) {
con.push_back(value);
//插入元素可能破坏堆结构
//要向上调整修复堆结构
AdjustUp(con.size()-1);
}
//将堆顶元素弹出(优先队列的队头元素出队)
void Pop() {
//优先队列非空才可以弹出队头元素
if (Empty()) {
return;
}
//交换堆顶元素和最后一个元素
swap(con.front(), con.back());
//用vector类中的尾删函数删除最后一个元素
con.pop_back();
//删除堆顶元素后堆的结构可能被破坏
//使用向下调整修复堆结构
AdjustDown(0);
}
//返回优先队列队头元素的引用
//因为队头元素(即堆顶元素)不可以被修改
//所以要返回const类型的引用
const T& Top()const {
return con.front();
}
//统计优先队列中元素个数
size_t Size() {
return con.size();
}
//判断优先队列是否是空
bool Empty() {
return con.empty();
}
};
int main() {
int arr[] = { 1,2,10,3,4,5,6,9,12 };
//使用数组区间构造小堆
Priority_Queue,greater> pq(arr, arr + sizeof(arr) / sizeof(arr[0]));
//给堆中插入元素
pq.Push(-1);
//获取堆顶元素的引用
int a = pq.Top();
//获取优先队列中的元素个数
int size = pq.Size();
//将优先队列的队头元素出队列
pq.Pop();
//判断优先队列是否是空
bool empty = pq.Empty();
}