作者:@小萌新
专栏:@C++初阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:介绍优先级队列的使用和模拟实现
优先级队列的使用和模拟实现
- priority_queue的使用
- priority_queue的介绍
- priority_queue的定义
- priority_queue的使用
- priority_queue的题目
- priority_queue的模拟实现
- 堆的向上调整
- 堆的向下调整
- 仿函数的实现
- priority_queue模拟实现
- 总结
priority_queue的使用
priority_queue的介绍
优先级队列(priority queue)
是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有(1)查找(2)插入一个新元素(3)删除一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素。
对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。
这里还是用简单的语言来描述下
我们可以将优先级队列想象成一个堆(默认是大堆)
priority_queue的定义
它的定义包括下面的三个参数
priority_queue<int, vector<int>,less<int> > q1;
我们通过观察可以发现 这里的三个参数代表的分别是
存储的数据类型 存储的容器类型 比较的方式
其中存储容器的类型和比较方式都是可以省略的
(默认是 vector 和 less)
所以说可以衍生出下面的两种定义方式
priority_queue<int, vector<int>> q2;
priority_queue<int> q3;
实际上来说它们定义的优先级队列是相同的
tips : 这里的优先级队列设计的有点奇怪
less对应的是大堆 而greater则对应的是小堆
priority_queue的使用
接口函数如下
使用代码如下
void test_priority_queue()
{
priority_queue<int, vector<int>, less<int> > q1;
q1.push(1);
q1.push(4);
q1.push(9);
q1.push(3);
q1.push(7);
while (!q1.empty())
{
cout << q1.top() << endl;
q1.pop();
}
}
这段代码的意思呢 就是我们随机插入几个数字
然后一个个打印出来 看看打印出来的顺序是什么?
显示结果如下
我们可以发现 它们在优先级队列中实际上就是按照从大到小的顺序
那么如果我们想到将它变成从小到大呢?
是不是改变下比较的仿函数就可以了
我们可以发现 变成greater之后就会是从小到大排序了
priority_queue的题目
就像这个问题 我们就可以使用我们的优先级队列来解决(堆)
因为大堆的顶部一定是最大的数字 所以说我们只需要删除掉前面top(k -1)个元素 之后我们就能得到我们第k大的数了
代码表示如下
class Solution {
public:
int findKthLargest(vector<int>& nums, int k)
{
// 我们使用优先级队列来做
// 因为要求第k大的数字 所以说我们只要建立一个大堆
// 然后pop掉前面k-1个数字 最后的一个数字就是我们要求的了
priority_queue<int> q1;
for (auto x : nums)
{
q1.push(x);
}
// 之后删掉前面k-1个数 就是第k大个数在最前面了
while(k-1)
{
q1.pop();
k--;
}
return q1.top();
}
};
运行结果如下
priority_queue的模拟实现
我们前面已经说过了 priority_queue的底层其实就是堆
所以说要我们实现一个优先级队列和实现一个堆 其实是差不多的
注意! 博主下面的实现全部以大堆为例
堆的向上调整
主要用于我们建堆的时候
我们在堆的末尾插入一个元素
由于我们的堆是大堆的缘故 所以说父节点 一定是大于子节点的
这个时候我们就需要判断 我们的父节点是否确实大于我们的子节点
如果大于 我们就交换它们的位置 之后重复这个过程
void adjust_up(vector<int>& v ,int child)
{
// 首先找出孩子的父节点
int father = (child - 1) / 2;
// 当子节点不为根节点时就一直判断
while ( child > 0 )
{
if (v[child] > v[father])
{
swap(v[child], v[father]);
child = father;
father = (child - 1) / 2;
}
else
{
// 符合规则 直接跳出循环
break;
}
}
}
堆的向下调整
向下调整通常用于删除数据的时候
同样的 我们如果直接删除大堆顶部的数据有点不太好操作
这个时候我们就可以将大堆顶部和底部的数据交换 之后删除底部的数据
这个时候我们只需要让顶部的元素 向下进行调整就好了
代码表示如下
void adjust_down(vector<int>& v, int father)
{
int child = 2 * father + 1;//左孩子
// 开始循环 直到父亲没有孩子 或者符合小堆结构位置
while (child < v.size())
{
// 首先判断右孩子是否存在并且右孩子是否大于左孩子
// 因为要是比较大的那个做父亲
if (child + 1 < v.size() && v[child+1] > v[child])
{
child++;
}
// 之后就判断 孩子是否大于父亲 如果大于就交换并且继续比较 如果不大于就结束
if (v[child] > v[father])
{
swap(v[child], v[father]);
father = child;
child = 2 * father + 1;
}
else
{
break;
}
}
}
仿函数的实现
两个很简单的仿函数 operator()重载
这两个函数比较特殊 我们先看代码
struct Less
{
template<class T>
bool operator() (T& x, T& y)
{
return x < y;
}
};
struct Greater
{
template<class T>
bool operator() (T& x, T& y)
{
return x > y;
}
};
我们可以看到 它们实际上就是重载了一个括号运算符来判断大小而已
priority_queue模拟实现
namespace shy //防止命名冲突
{
//比较方式(使内部结构为大堆)
template<class T>
struct less
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
//比较方式(使内部结构为小堆)
template<class T>
struct greater
{
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
//优先级队列的模拟实现
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
//堆的向上调整
void AdjustUp(int child)
{
int parent = (child - 1) / 2; //通过child计算parent的下标
while (child > 0)//调整到根结点的位置截止
{
if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
{
//将父结点与孩子结点交换
swap(_con[child], _con[parent]);
//继续向上进行调整
child = parent;
parent = (child - 1) / 2;
}
else//已成堆
{
break;
}
}
}
//插入元素到队尾(并排序)
void push(const T& x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1); //将最后一个元素进行一次向上调整
}
//堆的向下调整
void AdjustDown(int n, int parent)
{
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n&&_comp(_con[child], _con[child + 1]))
{
child++;
}
if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
{
//将父结点与孩子结点交换
swap(_con[child], _con[parent]);
//继续向下进行调整
parent = child;
child = 2 * parent + 1;
}
else//已成堆
{
break;
}
}
}
//弹出队头元素(堆顶元素)
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(_con.size(), 0); //将第0个元素进行一次向下调整
}
//访问队头元素(堆顶元素)
T& top()
{
return _con[0];
}
const T& top() const
{
return _con[0];
}
//获取队列中有效元素个数
size_t size() const
{
return _con.size();
}
//判断队列是否为空
bool empty() const
{
return _con.empty();
}
private:
Container _con; //底层容器
Compare _comp; //比较方式
};
}
总结
优先级队列的模拟实现主要是运用到了堆的知识 对于目前的同学们来说还是比较简单的