本篇将讲解 priority_queue 的实现,叫做优先级队列,是包含在 <queue> 头文件中的一种数据结构,虽然他叫做队列,但是该数据结构并不服从先进先出的特点,而是和我们的堆很相似。本篇中将多出一个仿函数的调用。目录如下:
目录
1. priority_queue 的介绍
优先级队列是一种容器适配器,按照严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。所以对于这样的一个数据结构其实非常类似于大堆。需要注意的是,第一个元素总是所包含元素中最大的,说明我们在插入元素和删除元素的时候,我们都需要将其
优先级队列被实现为容器适配器(容器适配器就是将特定容器封装作为其底层容器类),也就是可以在优先级队列中存储各种各样类型的数据元素。
对于底层容器的要求,底层容器可以是任何标准容器的模板,也可以是其他特定设计的容器类,容器应该可以通过随机访问迭代器访问,并且支持一下操作:
empty():检测容器是否为空 size():返回容器的元素个数 front():返回容器中第一个元素的引用 push_back():在容器尾部插入元素 pop_back():删除容器尾部元素
标准容器类 vector deque 满足这些要求,默认情况下,如果没有指定 priority_queue 类实例化容器类,使用 vector。
2. priority_queue 的实现
在上文中也已经提到,关于优先级队列的性质,和堆非常像,所以我们实现优先级队列相当于实现一个堆,所以实现向上调整算法和向下调整算法是优先级队列的难点。
注:对于一下算法的实现,源自更加详细的文章:堆/堆排序(C/C++)-CSDN博客,以下讲解的默认为大堆,双亲结点大于孩子结点。
我们先实现向上调整算法,对于向上调整算法,其主要作用是在元素进入优先级队列的时候,对优先级队列中的元素进行调整,使其保持优先级队列的属性,但新元素进入优先级队列的时候,我们需要将其与双亲结点相比较,若新入的结点大于双亲结点,那让新节点与双亲结点交换位置(默认是大堆),然后继续向上与双亲结点相比较,直到进入的结点的到达堆顶或者不大于双亲结点的时候,则退出,实现如下:
void AdjustUp(size_t child) { size_t parent = (child - 1) / 2; while (child > 0) { // 先建立大堆 if (_con[parent] < _con[child]) { std::swap(_con[parent], _con[child]); child = parent; parent = (child - 1) / 2; } else { break; } } }
接着我们实现向下调整算法,对于向下调整算法的实现,其主要作用为:当我们想要弹出元素的时候,弹出的元素是整个序列中的第一个元素,也就是堆顶的元素,当弹出之后,我们需要重新找到一个堆顶元素,所以这个时候,我们需要将堆尾的元素放在堆顶,然后从堆顶还是和他们的孩子结点相比较,若孩子结点大于当前结点,则与孩子结点相交换,直至到叶子结点为止。实现如下:
void AdjustDown(size_t parent) { size_t child = parent * 2 + 1; while (child < size()) { // 现在是建大堆,需要找到大的 if (child + 1 < size() && _con[child] < _con[child + 1]) child++; if (_con[parent] < _con[child]) { std::swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else { break; } } }
对于优先级队列的其他类函数的实现,其主要还是依靠本身提供的容器适配器,由于实现较为简单,直接给出代码:
// 构造函数 priority_queue(const Container con = Container()) :_con(con) {} bool empty() { return _con.empty(); } size_t size() { return _con.size(); } const T& top() const { return _con.front(); } void swap(priority_queue& pq) { _con.swap(pq._con); } void push(const T& e) { _con.push_back(e); AdjustUp(size() - 1); } void pop() { std::swap(_con[0], _con[size() - 1]); _con.pop_back(); AdjustDown((int)0); }
3. 仿函数下的 priority_queue
对于优先级队列来说,上文给出的也只是大堆优先级队列,但是若我们想要实现小堆的优先级队列呢?肯定还是需要依靠我们的模板,我们可以使用模板来构造大堆和小堆。
这个模板的实现主要依靠仿函数,也就是我们重新写出一个类,让这个类在对应函数中实例化一个对象,然后让该对象调用对应的重载比较函数,用在我们的向上调整算法和向下调整算法中,因为只需要将算法中的不等号更改方向,就可以决定大堆还是小堆。
默认情况下,实现的为大堆。大堆传入的仿函数为小于仿函数,小堆传入的仿函数为大于仿函数。两个仿函数的实现如下:
template<class T> class Greater { public: bool operator()(const T& x, const T& y) { return x > y; } }; template<class T> class Less { public: bool operator()(const T& x, const T& y) { return x < y; } };
在向上调整算法和向下调整算法中的使用:
void AdjustUp(size_t child) { size_t parent = (child - 1) / 2; Compare com; bool either = com(_con[parent], _con[child]); while (child > 0) { // 先建立大堆 //if (_con[parent] < _con[child]) if(com(_con[parent], _con[child])){ std::swap(_con[parent], _con[child]); child = parent; parent = (child - 1) / 2; } else { break; } } } void AdjustDown(size_t parent) { Compare com; size_t child = parent * 2 + 1; while (child < size()) { // 现在是建大堆,需要找到大的 if (child + 1 < size() && com(_con[child], _con[child + 1])) child++; if (com(_con[parent], _con[child])) { std::swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else { break; } } }
如上,我们只要在函数中实例化一个对象,然后调用该对象的重载比较函数即可。若还存在异或,可以看看下面的完整代码。
4. All Code
namespace MyPriorityQueue { template<class T> class Greater { public: bool operator()(const T& x, const T& y) { return x > y; } }; template<class T> class Less { public: bool operator()(const T& x, const T& y) { return x < y; } }; template<class T, class Container = deque<T>, class Compare = Less<T>> class priority_queue { public: priority_queue(const Container con = Container()) :_con(con) {} bool empty() { return _con.empty(); } size_t size() { return _con.size(); } const T& top() const { return _con.front(); } void swap(priority_queue& pq) { _con.swap(pq._con); } // 向上调整算法 void AdjustUp(size_t child) { size_t parent = (child - 1) / 2; Compare com; bool either = com(_con[parent], _con[child]); while (child > 0) { // 先建立大堆 //if (_con[parent] < _con[child]) if(com(_con[parent], _con[child])){ std::swap(_con[parent], _con[child]); child = parent; parent = (child - 1) / 2; } else { break; } } } void push(const T& e) { _con.push_back(e); AdjustUp(size() - 1); } void AdjustDown(size_t parent) { Compare com; size_t child = parent * 2 + 1; while (child < size()) { // 现在是建大堆,需要找到大的 if (child + 1 < size() && com(_con[child], _con[child + 1])) child++; if (com(_con[parent], _con[child])) { std::swap(_con[parent], _con[child]); parent = child; child = parent * 2 + 1; } else { break; } } } void pop() { std::swap(_con[0], _con[size() - 1]); _con.pop_back(); AdjustDown((int)0); } private: Container _con; }; }