目录
0. 引言
优先队列 (priority_queue) 是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。优先队列和堆本质是一样的,以数组形式存储的完全二叉树。
1. priority_queue 介绍
1.1 构造函数
我们可以看到有两种构造方式,一个是构造一个空对象,另一个是通过迭代器的区间来构造,默认的构造出的是大堆。
priority_queue<int> pq1; //直接构造空对象
接下来我们分别以大堆和小堆的方式来构造对象:
vector<int> v1 = {3,2,7,6,0,4,1,9,8,5};
priority_queue<int, vector<int>, less<int>> pq1(v1.begin(), v1.end());
//less-大堆
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
priority_queue<int, vector<int>, greater<int>> pq2(v1.begin(), v1.end());
//greater-小堆
while (!pq2.empty())
{
cout << pq2.top() << " ";
pq2.pop();
}
cout << endl;
priority_queue<int> pq3(v1.begin(), v1.end());
//默认大堆
while (!pq3.empty())
{
cout << pq3.top() << " ";
pq3.pop();
}
cout << endl;
因此我们得出: less - 大堆, greater - 小堆。
1.2 priority_queue 接口函数使用
接口函数主要包括以下:
函数 | 说明 |
empty | 检测优先级队列是否为空,是返回true,否则返回 false |
top | 返回优先级队列中最大(最小元素),即堆顶元 |
push | 在优先级队列中插入元素x |
pop | 删除优先级队列中最大(最小)元素,即堆顶元素 |
1.3 仿函数
仿函数又名函数对象 function objects 仿函数的主要作用是借助类和运算符重载,做到同一格式兼容所有函数。由于模板将 less 用作大堆,而 greater 用做小堆,是在有点别扭,如果是我们自己实现仿函数的化,肯定会按照习惯来写,less 表示小堆,greater 表示大堆。例如:
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;
}
};
1.4 题目练习
优先级队列适合来进行TOPK 以及 排序问题,因为其底层是和堆一模一样的。现在我们一起来看下面这道题:
这题如果不关心时间复杂度,直接利用 sort 排序将会很简单:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k)
{
sort(nums.begin(),nums.end());
return nums[nums.size()-k];
}
};
当我们使用优先级队列时,时间复杂度会更好:
//大堆
class Solution {
public:
int findKthLargest(vector<int>& nums, int k)
{
priority_queue<int> pq1(nums.begin(), nums.end());
for(int i = 0;i < k-1; i++)
{
pq1.pop();
}
return pq1.top();
}
};
//小堆
class Solution {
public:
int findKthLargest(vector<int>& nums, int k)
{
priority_queue<int,vector<int>, greater<int>> pq1(nums.begin(), nums.begin()+k);
for(int i = k ;i < nums.size(); i++)
{
if(nums[i] > pq1.top())
{
pq1.pop();
pq1.push(nums[i]);
}
}
return pq1.top();
}
};
2. priority_queue 模拟实现
优先级队列的模拟实现,难点在于堆的向上和向下调整。我们首先展现整体代码,然后再逐一分进行分析。
2.1 整体代码
我们单独创建了一个 PriorityQueue.h 文件。
#pragma once
#include<vector>
#include<iostream>
using namespace std;
namespace LHY
{
template<class T, class Container = vector<T>>
class priority_queue
{
public:
void adjust_up(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[parent] < _con[child])
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(int parent)
{
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size()
&& _con[child] < _con[child + 1])
{
++child;
}
if (_con[parent] < _con[child])
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top()
{
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
从中我们可以看到私有成员变量为底层容器对象。即 Container _con; class Container = vector<T> 。
2.2 基本函数
基本函数为:
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top()
{
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
我们可以清楚的看到,优先级队列的插入和删除,返回大小以及判空直接调用底层容器接口即可。而因为需要满足堆的性质,push 和 pop 则需要向上和向下调整堆。此处我们默认建立的是大堆,当push一个数比其父节点大,则需要交换位置,即向上调整。当 pop 操作则是先首尾交换,再删除尾元素,此时不满足堆条件,顶部元素则需要向下调整。如下图所示:
2.3 模拟实现
void test_priority_queue()
{
priority_queue<int> pq;
pq.push(1);
pq.push(0);
pq.push(5);
pq.push(2);
pq.push(1);
pq.push(7);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
2.4 仿函数切换大小堆
我们在 1.3 介绍了仿函数及其实现,那么我们能不能应用到上面的代码中呢?答案是当然可以。
代码如下:
namespace LHY
{
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 Comapre = less<T>>
class priority_queue
{
public:
void adjust_up(int child)
{
Comapre com;
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[parent] < _con[child])
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(int parent)
{
size_t child = parent * 2 + 1;
Comapre com;
while (child < _con.size())
{
/*if (child + 1 < _con.size()
&& _con[child] < _con[child + 1])*/
if (child + 1 < _con.size()
&& com(_con[child], _con[child + 1]))
{
++child;
}
/*if (_con[parent] < _con[child])*/
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top()
{
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
template<class T, class Container = vector<T>,class Comapre = less<T>>
if (com(_con[parent], _con[child]))等等的变化使得我们可以改变大小堆。例如:
2.5 日期类
假设我们现在的数据不是 vector<int> 而是 日期类对象,会发生什么情况呢?我们先看日期类代码:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
class PDateLess
{
public:
bool operator()(const Date* p1, const Date* p2)
{
return *p1 < *p2;
}
};
class PDateGreater
{
public:
bool operator()(const Date* p1, const Date* p2)
{
return *p1 > *p2;
}
};
这时候我们创建数据为Date 的优先级队列(大堆),取堆顶元素(判断是否能对自定义类型进行正确调整)
此时没有问题, 因为在实际比较时,调用的是 Date 自己的比较逻辑。
那么如果是Date* 呢?我们再来看:
我们发现多次运行结果不一样 ,因为此时调用的是指针的比较逻辑(地址是随机的,因此结果也是随机的)。
那么,我们要如何去解放呢?可以专门创建一个为Date* 比较的仿函数。
class PDateLess
{
public:
bool operator()(const Date* p1, const Date* p2)
{
return *p1 < *p2;
}
};
class PDateGreater
{
public:
bool operator()(const Date* p1, const Date* p2)
{
return *p1 > *p2;
}
};
这样便没有问题了:
标签:优先级,parent,STL,C++,return,child,const,size,con From: https://blog.csdn.net/dgfghchhgc/article/details/137054456