全部代码
-
以容器适配器的玩法来实现,底层容器默认为vector
-
使用了模板参数T表示存储在队列中的元素类型,Container表示底层容器类型,默认为vector,Compare表示比较器类型,默认为less。
-
adjustDown函数用于向下调整堆,保持堆的性质。它从指定的父节点开始,将其与子节点进行比较,如果子节点的值更大,则交换父节点和子节点的位置,并继续向下调整直到满足堆的性质。
-
adjustUp函数用于向上调整堆,保持堆的性质。它从指定的子节点开始,将其与父节点进行比较,如果子节点的值更大,则交换父节点和子节点的位置,并继续向上调整直到满足堆的性质。
-
构造函数可以接受一个迭代器范围[first, last],用于初始化优先队列。在构造过程中,首先将迭代器范围内的元素存储到底层容器中,然后从最后一个非叶子节点开始,依次调用adjustDown函数,使得整个容器满足堆的性质。
-
empty函数用于判断优先队列是否为空,即底层容器是否为空。
-
size函数用于返回优先队列中元素的个数,即底层容器的大小。
-
top函数用于返回优先队列中的最大元素(根节点),但并不删除该元素。
-
push函数用于将一个元素插入到优先队列中。它将元素添加到底层容器的末尾,并调用adjustUp函数向上调整堆。
-
pop函数用于删除优先队列中的最大元素(根节点)。它首先将根节点与最后一个叶子节点交换位置,然后删除最后一个叶子节点,并调用adjustDown函数向下调整堆。
#pragma once
#include<vector>
namespace hqj
{
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
private:
void adjustDown(int parent)
{
int child = parent * 2 + 1;
while (child < _c.size())
{
if (child + 1 < _c.size() && _comp(_c[child], _c[child+1]))
{
child++;
}
if (_comp(_c[parent], _c[child]))
{
swap(_c[parent], _c[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void adjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_comp(_c[parent],_c[child]))
{
swap(_c[parent], _c[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
public:
priority_queue()
{}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_c(first,last)
{
for (int i = _c.size() - 1 - 1; i >= 0; i++)
{
adjustDown(i);
}
}
bool empty() const
{
return _c.empty();
}
size_t size() const
{
return _c.size();
}
T& top() const
{
return (T&)_c.front();
}
void push(const T& x)
{
_c.push_back(x);
adjustUp(_c.size() - 1);
}
void pop()
{
swap(_c.front(), _c.back());
_c.pop_back();
adjustDown(0);
}
private:
Container _c;
Compare _comp;
};
};
私有成员
-
_c容器对象:缺省的容器类型是vector
-
_comp比较器对象
Container _c;
Compare _comp;
构造函数
-
由于我们模拟实现的优先级队列是一个容器适配器,私有成员中不含内置类型。利用系统生成的默认构造函数特性,会自动调用私有成员的构造函数,所以我们可以不写该优先级队列的构造函数的内容
priority_queue()
{}
析构函数
-
同理根据系统默认生成的析构函数特性,当对象销毁时会自动调用自定义类型的析构函数,我们也可以不写
向下调整函数
-
向下调整函数的参数是父节点的下标
-
首先我们通过父亲下标找到其左孩子
-
随后我们通过比较左右孩子大小来确定父亲要与哪个孩子进行比较、交换,至于是选择较大孩子还是较小孩子要根据比较器_comp的返回值来确定,如果我们想建小堆则选取较小的孩子;若我们要建大堆,则选取较大的孩子
-
交换父亲和孩子,并且更新父亲和孩子
-
由于向下调整次数不一定唯一,我们需要用到while结构,循环终止条件为:child下标越界、父子间的大小关系不满足比较器的要求。
void adjustDown(int parent)
{
int child = parent * 2 + 1;
while (child < _c.size())
{
if (child + 1 < _c.size() && _comp(_c[child], _c[child+1]))
{
child++;
}
if (_comp(_c[parent], _c[child]))
{
swap(_c[parent], _c[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
向上调整函数
-
向上调整函数的参数是孩子的下标
-
首先通过孩子的下标找到父亲的下标
-
当父子关系满足比较器_comp的要求时进行调整,交换父亲和孩子,并更新父亲和孩子的下标
-
同样,向上调整不止一次,需要用到while结构,循环终止条件为:孩子为根节点、父亲和孩子间的大小关系不满足比较器_comp要求
void adjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_comp(_c[parent],_c[child]))
{
swap(_c[parent], _c[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
迭代器构造函数
-
由于模拟实现的优先级队列是容器适配器,直接使用底层容器的迭代器构造就行了,读入要建堆的数据
-
都读入后,先找到最后一个叶子节点的父亲节点,随后传递该节点下标进向下调整函数,开始依次向下调整
-
以arr数组为例:
priority_queue(InputIterator first, InputIterator last)
:_c(first,last)
{
for (int i = _c.size() - 1 - 1; i >= 0; i--)
{
adjustDown(i);
}
}
empty函数
-
还是底层容器接口的复用,调用底层容器的empty函数
bool empty() const
{
return _c.empty();
}
size函数
-
底层容器接口的复用,调用底层容器的size函数
size_t size() const
{
return _c.size();
}
top函数
-
函数作用是返回优先级队列队头元素(也就是堆顶元素),而该元素正好是底层容器的首元素,复用front接口就行,记得要强转
T& top() const
{
return (T&)_c.front();
}
push函数的实现
-
调用底层容器的push_back函数实现插入功能,然后再进行向上调整堆
void push(const T& x)
{
_c.push_back(x);
adjustUp(_c.size() - 1);
}
pop函数的实现
-
首先交换队头元素和队尾元素(堆顶元素和堆尾元素)
-
将队尾元素删除
-
从堆顶开始向下调整堆
void pop()
{
swap(_c.front(), _c.back());
_c.pop_back();
adjustDown(0);
}