一、概念
优先级队列是一种容器适配器,它的英文名称是priority_queue,因为它底层默认是通过vector实现的,相当于对其他容器的接口进行了包装,所以它不属于容器本身。因为在vector上使用了堆算法将元素构成了堆的结构,因此priorty_queue就是堆,默认情况下是大堆。
二、优先级队列的模拟
2.1 堆的建立
堆的建立采用的是自上而下的调整,英文名称为sift_down。假设我们要调整的堆为最小堆,它的主要原理就是以完全二叉树的待调整的子树的根结点作为基准结点,将它的根节点和左右子树中比较小的结点进行比较,如果根结点大于左右子树中比较小的结点,就采用向上覆盖的方法,将子节点覆盖根节点,然后继续向下一层比较,直到遍历结束。最终位于堆顶的元素是最小的结点。
代码如下:
void _adjustdown(int start)
{
int i = start, j = 2 * i + 1;
//子节点,父节点;
int n = c.size();
T tmp = c[i];//利用tmp来存储最开始的结点值。
while (j < n) //检查是否到最后位置,左子树是否存在
{
//j+1小于n,代表右侧兄弟结点存在的前提下
//comp这里为仿函数,直接进行比较也可以。
if (j + 1 < n&& comp(c[j],c[j+1]))
j++;
if (comp(tmp,c[j]))
{
c[i] = c[j];
i = j;
j = 2 * i + 1;
}
else
break;
}
c[i] = tmp; //退出while循环之后,最终的根节点的值即为最初保存的tmp的值。
}
2.2 堆的插入
堆的插入采取的是向上调整法(siftup算法),因为新节点的插入都是在已经建成的最小堆的后面,所以要采用自下而上的方法,子节点和父节点进行比较,如果父节点小于子节点的话,就将相对应的父节点向下覆盖。
(自上而下调整,关键码上浮)
(自下而上调整,关键码下浮)
这样做的话,提升了效率,不需要每次都交换两个结点。
void _adjustup(int start)
{
//子节点
int j = start;
//父结点
int i = (j - 1) / 2;
T tmp = c[j];
while (j > 0)
{
//当父节点小于子节点的时候,进行交换
if (comp(c[i],tmp))
{
c[j] = c[i];
j = i;
i = (j - 1) / 2;
}
else
break;
}
c[j] = tmp;
}
2.3总体的模拟
1.仿函数从表面看起来是一个对象,内部的重载了operator()运算符,包含了一个函数,相当于将函数封装起来了,因此对象可以像函数一样传入参数进行调用。
2.采用无名对象进行赋值:例如pr 是一个无名对象,构造函数在进行初始化 comp(pr)的时候,本来利用pr来拷贝构造comp,由于是无名的对象,编译器进行了优化,即不会进行拷贝构造了,从而提升了效率。
#include <iostream>
#include <vector>
using namespace std;
#include <functional> //包含less和greater的头文件
namespace tianxin
{
//bit::priority_queue<int>
template < class T, class Container = vector<T>,
class Compare=less<T>>
class priority_queue
{
public:
priority_queue()
{}
//仿函数默认为less<T>,对应的是大堆;
//调用的时候可以传递greater<T>,对应的是小堆。
priority_queue(const T *first, const T *last, const Compare& pr = Compare()) :
c(first, last), comp(pr)
{
//最后一个分支的结点;
//int curpos = c.size() - 2 / 2 = c.size() / 2 - 1;
int curpos = c.size() / 2 - 1;
while (curpos >= 0)
{
_adjustdown(curpos);//从最后一个含有分支的结点开始调整
curpos--;
}
}
bool empty() const
{
return c.empty();
}
size_t size() const
{
return c.size();
}
T& top()const
{
return c.front();
}
void push(const T& x)
{
c.push_back(x);
_adjustup(c.size() - 1);
}
void pop()
{
std::swap(*c.begin(), *--c.end());
c.pop_back();//删除最后一个元素;
_adjustdown(0);
}
void Show()const
{
for (int i = 0; i < c.size(); ++i)
cout << c[i] << " ";
cout << endl;
}
protected:
void _adjustdown(int start)
{
int i = start, j = 2 * i + 1;
//子节点,父节点;
int n = c.size();
T tmp = c[i];
while (j < n) //检查是否到最后位置,左子树是否存在
{
//j+1小于n,代表右侧兄弟结点存在的前提下,比较,如果j<j+1;
if (j + 1 < n&& comp(c[j],c[j+1]))
j++;
if (comp(tmp,c[j]))
{
c[i] = c[j];
i = j;
j = 2 * i + 1;
}
else
break;
}
c[i] = tmp;
}
void _adjustup(int start)
{
//子节点
int j = start;
//父结点
int i = (j - 1) / 2;
T tmp = c[j];
while (j > 0)
{
//当父节点小于子节点的时候,进行交换
if (comp(c[i],tmp))
{
c[j] = c[i];
j = i;
i = (j - 1) / 2;
}
else
break;
}
c[j] = tmp;
}
private:
Container c;
Compare comp;
};
};
测试代码如下
void main()
{
int ar[] = { 53, 17, 78, 9, 45,30,20,84};
int n = sizeof(ar) / sizeof(ar[0]);
tianxin::priority_queue<int, vector<int>, greater<int>>
pq(ar, ar + n, greater<int>());
//tianxin::priority_queue<int, vector<int>> pq(ar, ar + n);
//pq.pop();
pq.push(80);
pq.Show();
}