目录
队列是一种先进先出(First in First out,FIFO)的数据类型。每次元素的入队都只能添加到队列尾部,出队时从队列头部开始出。
优先级队列(priority_queue)底层是数据类型中的堆。优先级队列每次出队的元素是队列中优先级最高的那个元素优先级队列
优先级队列的模拟实现
优先级队列的底层就是堆,所以需要用到堆,STL默认是大堆
如下是堆的示意图,堆的物理结构其实就是数组
1.成员变量
优先级队列是个适配器,因为堆的物理结构就是数组,所以这里我们借用了vector<T>模板,
less<T>是仿函数,用来确定队列中的元素组成是大堆还是小堆,在这里为了和STL匹配,我们用
less<T>表示大堆,若希望是小堆,则用greater<T>函数,下面会再说明。
template<class T,class container=vector<T>,class Compare=less<T>>
class priority_queue
{
public:
//向上调整算法
void AdjustUp(int child)
{
private:
container _con;
};
2.入队push
void push(const T& x)
{
_con.push_back(x);
//建成大堆
AdjustUp(_con.size() - 1);
}
注: 优先级队列也是一种队列,自然也要有入队push函数。
_con.push_back(x);这一部分我们在尾部插入了一个数据,但我们知道优先级队列的物理结构其实是堆,所以当在尾部插入一个数据的时候,我们需要向上调整,使其重新变为一个堆,示图如下:
在尾部插入一个数据,我们需要向上进行比较,所以用到向上调整
2.1向上调整法
//向上调整算法
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child)
{
if (_con[child]>_con[parent])
{
//swap模板函数
swap(_con[child], _con[parent]);
child = parent;
parent= (child - 1) / 2;
}
else
{
break;
}
}
}
注: 在满二叉树中(堆的物理结构就是一个满二叉树),父亲结点的下标=(child-1)/2;
因为STL中默认是大堆,即父亲结点大于孩子结点,所以在这一部分代码中 if (_con[child]>_con[parent]) 我们需要交换,使得父亲结点大于孩子结点。但若是我们希望是小堆,仅仅是 if (_con[child]<_con[parent]) ,我们也可以重新写一个函数,但为了避免冗长,我们引入模板clsss less<T>. less<T>是仿函数。
template<class T>
struct less
{
bool operator()(const T& x1, const T& x2)
{
return x1 < x2;
}
};
在这里我们重载了(), less的使用:
lt::less<int> fun;
cout<<fun(1,2)<<endl; fun 是个类,因为我们重载了(),fun(1,2) 就像一个函数,但实际上fun 是个类,所以在这里我们称其为仿函数。如果我们想建小堆,我们需要用到下面的仿函数。
template<class T>
struct greater
{
bool operator()(const T& x1, const T& x2)
{
return x1 > x2;
}
};
有了仿函数我们就可以用模板仿函数对向上调整算法代码进行改写
//向上调整算法
void AdjustUp(int child)
{
Compare com;//这里com就是仿函数其模板为class Compare=less<T>当不传参默认是com为
//less<int>
int parent = (child - 1) / 2;
while (child)
{
if (com(_con[parent],_con[child]))
{
//swap模板函数
swap(_con[child], _con[parent]);
child = parent;
parent= (child - 1) / 2;
}
else
{
break;
}
}
}
3.出队pop
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
//向下调整de时间复杂度(logN)
AdjustDown(0);
}
优先级队列的出队实际上就是删除堆的堆顶元素,若我们直接删除堆顶元素,那么堆的性质就会发生改变,因为堆是满二叉树如下图所示:
若直接删除堆顶元素则新的逻辑结构将不是堆(满二叉树)
我们首先将要删除的堆顶元素与堆中的最后一个元素交换,然后将换下来的最后一个元素删除,此时已经破坏了堆的性质,但是删除了最后一个元素后,根结点的左右子树还都满足着堆的性质,所以此时只需要对堆的根结点进行一次向下调整算法即可。
注:删除栈顶数组时候,栈顶元素发生改变,我们需要向下进行调整重新建大堆或者小堆
3.1向下调整法
//向下调整算法
void AdjustDown(int root)
{
int parent = root;
int child = 2 * parent + 1;
Compare com;
while (child < _con.size())
{
if (child+1<_con.size() && com(_con[child],_con[child+1]))
{
child++;
}
if (com(_con[parent],_con[child]))
{
//底层是引用,可以传值,传地址
swap(_con[parent],_con[child]);
parent = child;
child= 2 * parent + 1;
}
else
{
break;
}
}
}
注:在这里向下调整时根进行向下调整所以在这个函数中参数我们记为 in root.
4.获取队头元素
T& top()
{
return _con[0];
}
5.获取队列元素个数
size_t size()
{
return _con.size();
}
6.判断队列是否为空
bool empty()
{
return _con.empty();
}
7. 仿函数greater和less 的应用
void test_sort()
{
vector<int> v;
v.push_back(1);
v.push_back(5);
v.push_back(4);
v.push_back(8);
sort(v.begin(), v.end(), greater<int>());//匿名对象
for (auto e : v)
{
cout << e << "";
}
cout << endl;
}
注:这里用了sort 排序函数,以及greater<int>,则从大大小排序。测试结果如下:
8.优先级队列的应用
上述结果时间复杂度为o(NlogN),空间复杂度为o(N)
优化一次:时间复杂度为o(NlogN),空间复杂度为o(1)
再次优化: 时间复杂度为o(Nlogk),空间复杂度为o(k)
9.remark
在priority_queue中成员变量:container _con 是自定义类型,不需要我们构造拷贝构造,构造函数,赋值重载,析构函数,因为编译器会对自定义类型成员变量自动创建构造函数等,例如_con 为vector<int>,系统会自动调用vector 的构造函数,若若成员变量有内置类型,我们需要创建构造函数。
附录
#pragma once
#include<vector>
#include<algorithm>
namespace lt
{
// 仿函数
template<class T>
struct less
{
bool operator()(const T& x1, const T& x2)
{
return x1 < x2;
}
};
template<class T>
struct greater
{
bool operator()(const T& x1, const T& x2)
{
return x1 > x2;
}
};
//Compare=less<T> 是个仿函数,用来比较大小
template<class T,class container=vector<T>,class Compare=less<T>>
class priority_queue
{
public:
//向上调整算法
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child)
{
if (com(_con[parent],_con[child]))
{
//swap模板函数
swap(_con[child], _con[parent]);
child = parent;
parent= (child - 1) / 2;
}
else
{
break;
}
}
}
//向下调整算法
void AdjustDown(int root)
{
int parent = root;
int child = 2 * parent + 1;
Compare com;
while (child < _con.size())
{
if (child+1<_con.size() && com(_con[child],_con[child+1]))
{
child++;
}
if (com(_con[parent],_con[child]))
{
//底层是引用,可以传值,传地址
swap(_con[parent],_con[child]);
parent = child;
child= 2 * parent + 1;
}
else
{
break;
}
}
}
//默认是大堆
void push(const T& x)
{
_con.push_back(x);
//建成大堆
AdjustUp(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
//向下调整de时间复杂度(logN)
AdjustDown(0);
}
T& top()
{
return _con[0];
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
container _con;
};
void test_priority_queue()
{
priority_queue<int,vector<int>,greater<int>> pq;
pq.push(1);
pq.push(2);
pq.push(5);
pq.push(8);
while (!pq.empty())
{
cout << pq.top() << "";
pq.pop();
}
cout << endl;
}
void test_sort()
{
vector<int> v;
v.push_back(1);
v.push_back(5);
v.push_back(4);
v.push_back(8);
sort(v.begin(), v.end(), greater<int>());//匿名对象
for (auto e : v)
{
cout << e << "";
}
cout << endl;
}
}
#include<iostream>
using namespace std;
#include"priority_queue.h"
int main()
{
//lt::test_priority_queue();
lt::test_sort();
//lt::less<int> fun;
//cout<<fun(1,2)<<endl;
return 0;
}