目录
priority_queue的介绍

标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
上图是在C++官方文档中对优先队列的介绍,其中提到使其第一个元素总是其所包含元素中最大的一个,这是不是很像我们学过的大堆,大堆中的根节点所包含的元素不正是堆中最大的元素吗?
cpp
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;
我们可以看到,优先队列默认使用vector存储数据,在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。
其中Compare是 比较准则,它是一个仿函数类型 ,用于决定元素的优先级顺序。默认std::less<T> 意味着大堆(最大值优先);如果传入 std::greater<T>,就会变成小堆(最小值优先)。
如果没有学习过堆或者对该部分内容有遗忘,可以看看我写过的下面两篇文章:
仿函数的介绍
什么是仿函数?
仿函数,或者叫函数对象,是指任何一个可以像函数一样被调用的对象。
看下面的代码:
cpp
template<class T>
class Less
{
public:
bool operator()(const T& x1, const T& x2)
{
return x1 < x2;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& x1, const T& x2)
{
return x1 > x2;
}
};
我们通过对()进行重载使得该类实例化后的对象就像一个函数,但它有函数的本质所不具备的特性:
仿函数通过成员变量维护内部状态,实现有状态(保留了之前的调用信息)的可调用对象,且多个仿函数实例可以各自拥有独立状态,互不干扰。它可以被编译器内联,还可以作为模板参数传递。
一般不需要我们自己来实现,可以直接使用库里面的,头文件<functional>

需要我们自己实现仿函数的情况:
- 类类型不支持比较大小
- 支持比较大小,但是比较的逻辑不是我们想要的
仿函数的运用
我们在冒泡排序中使用仿函数,可以根据我们的需要决定实现正序、倒序,而不对代码进行直接的更改:
cpp
#include<vector>
using namespace std;
template<class T>
class Less
{
public:
bool operator()(const T& x1, const T& x2)
{
return x1 < x2;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& x1, const T& x2)
{
return x1 > x2;
}
};
template<class Compare>
void BubbleSort(int* a, int n, Compare com)
{
for (int i = 0; i < n; i++)
{
int flag = 0;
for (int j = 1; j < n - i; j++)
{
//if (a[j - 1] > a[j])
if (com(a[j], a[j - 1]))
{
swap(a[j - 1], a[j]);
flag = 1;
}
}
if (flag == 0)
{
break;
}
}
}
int main()
{
Less<int> less;
Greater<int> greater;
int a[] = { 2,6,1,8,4,9,7 };
BubbleSort(a, 7, less);
BubbleSort(a, 7, greater);
//传匿名对象
BubbleSort(a, 7, Less<int>());
BubbleSort(a, 7, Greater<int>());
return 0;
}
priority_queue的功能展示
主要功能如下:

cpp
#include<queue>
#include<iostream>
using namespace std;
void test()
{
priority_queue<int> pq;
for (int i = 0;i < 5;i++)
{
pq.push(i);
}
if (pq.empty())
{
cout << "空" << endl;
}
const int size = pq.size();
for (int i = 0;i < size;i++)
{
cout << pq.top() << ' ';
pq.pop();
}
}
int main()
{
test();
return 0;
}
功能是比较简单的,同时我们也有一定的基础,所以在这里对功能仅做简单展示。
priority_queue的模拟实现
当我们将元素依次放在数组里,会有如下规则:
下标为 i 的节点的父节点下标为 (i - 1) / 2,左孩子为 2 * i + 1,右孩子为 2 * i + 2
那我们就可以利用这个规则、堆的向上调整以及向下调整算法来完成优先队列的模拟实现,我们先完成向上调整算法以及向下调整算法。
先给出大概的框架:
cpp
namespace mine
{
template<class T,class Container=std::vector<T>,class compare=Less<T>>
class priority_queue
{
public:
private:
Container _con;
};
}
向上调整算法以及向下调整算法
cpp
//用于push
void AdjustUp(int child)//我们默认建大堆
{
int parent = (child - 1) / 2;
while (child > 0)//当child=0,就一定是最大了,也就调整成堆了
{
//_con[child]>_con[parent]
if (compare()(_con[parent], _con[child]))
//我们这里使用的是less,就是<,所以需要调换一下顺序
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;//只要有一次满足大堆,那么就实现了
}
}
}
void AdjustDown(int parent)
{
//对于父节点来说,当我们要构造大堆时,我们应该与大的那个孩子进行交换
int child = parent * 2 + 1;//我们先认为左孩子是大的哪一个
while (child < _con.size())
//因为每次都要与孩子比较,当孩子到了叶子节点后,再调整一次就可以了
{
//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
if (child + 1 < _con.size() && compare()(_con[child], _con[child + 1]))
{
child++;
}
//if (_con[parent] < _con[child])
if(compare()(_con[parent],_con[child]))
{
std::swap(_con[parent],_con[child]);
parent=child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
在代码中都包含详细的解释,请读者耐心观看。
常用功能实现
cpp
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
bool empty() const
{
return _con.size() == 0;
}
size_t size() const
{
return _con.size();
}
const T& Top() const
{
return _con.front();
}
void swap(priority_queue& pq)
{
_con.swap(pq._con);
}
一些内容的补充:
什么是适配器
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设 计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

STL标准库中stack和queue****的底层结构
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为 容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认 使用deque,比如:



deque****的原理介绍
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端 进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组