这里写目录标题
1.什么是priority_queue(优先级队列)
顾名思义,优先级队列就是一个出队有顺序的队列,每次出队列的数据都是当前队列中最大或者最小的数据。
优先级队列底层存储数据的容器默认是vector,为了实现该队列的有序性,priority_queue底层采用了堆排序,将vector中的数据采用大堆或者小堆排序的方式,实现了优先级队列。(默认是大堆,即堆顶是最大的数据)
2.priority_queue用法
构造函数
priority_queue()无参构造函数
priority_queue(InputIterator first,InputIterator last)用迭代器区间构造
容量相关函数
empty()判断优先级队列是否为空
size()返回优先级队列中元素个数
插入删除
push(x)在优先级队列中插入数据x
pop()删除优先级队列中最大(最小元素),即堆顶元素
top()返回优先级队列中最大(最小元素),即堆顶元素
3.模拟实现priority_queue
3.1 priority_queue的核心——堆排序
priority_queue代码的核心部分就是堆排序,也就是如何保证每次出队的数据都是当前队列中最大(最小)的数据。下面分析如何对一组数据进行堆排序:
我们给出一个数组,该数组逻辑上可以看作是一颗完全二叉树,我们以实现一个大堆(根节点最大,堆中某个节点的值小于它父节点的值)为例:
vector a = { 1 , 5 , 3 , 0 , 6 , 3 , 4};
要想将上面的堆变为大堆,我们需要将每个字树都变成大堆。
- 首先我们要找到最小的子树:
由图不难得出最小的子树以下标为2的节点为根节点的树,即他是最后一个叶子节点的父节点,最后一个节点表示为a.size()-1;则最小子树根节点表示为(a.size()-1-1)/2。- 第二步我们要将这个子树变为大根堆:
- 第三步我们把每一个子树都更新成大根堆:
最后得到的就是一个大根堆。
解释了大根堆的更新过程,下面我们来代码实现:
void AdjustDown(int parent) //以当前节点为根节点向下调整
{
size_t child = parent * 2 + 1;
while (child < _c.size())
{
if (child + 1 < _c.size() && _c[child] < _c[child + 1])
{
child++;
}
if (_c[parent] < _c[child])
{
swap(_c[parent], _c[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
int main()
{
int arr[]={1, 5, 3, 0, 6, 3, 4};
vector<int> a(arr, arr + sizeof(arr)/sizeof(int));
for(size_t i = (a.size() - 1 - 1) / 2); i >= 0; i--)
{
AdjustDown(i); //更新子树的根节点
}
}
3.2 完整代码
#include <vector>
template<class T>
class Less
{
public:
bool operator()(const T& a, const T& b)
{
return a < b;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& a, const T& b)
{
return a > b;
}
};
namespace zhy{
template<class T , class Container=vector<T> , class Compare=Less<T>>
//第三个模板参数是用来控制堆是大堆还是小堆的
class priority_queue
{
public:
priority_queue()
{
}
template<class InputIterator>
priority_queue(InputIterator begin, InputIterator end)
:_c(begin,end) //用迭代器区间初始化容器
{
//建堆
for (size_t i = (_c.size() - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(i);
}
}
bool empty() const
{
return _c.empty();
}
size_t size() const
{
return _c.size();
}
T& top()
{
return _c.front();
}
const T& top() const
{
return _c.front();
}
void AdjustUp(int child) //从当前节点向上调整
{
Compare com;
int parent = (child - 1) / 2;
while (parent >= 0)
{
if (com(_c[parent] , _c[child])) //com.operator()(_c[parent],_c[child]),默认为return _c[parent]<_c[child]
{
swap(_c[parent], _c[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& value)
{
_c.push_back(value); //尾插进数组
AdjustUp(_c.size() - 1); //调整为大堆
}
void AdjustDown(int parent) //从当前节点向下调整
{
Compare com; //定义一个函数对象,即仿函数
size_t child = parent * 2 + 1;
while (child < _c.size())
{
if (child + 1 < _c.size() && com(_c[child] , _c[child + 1]))
{
child++;
}
if (com(_c[parent] , _c[child]))
{
swap(_c[parent], _c[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void pop()
{
swap(_c[0], _c[_c.size() - 1]);
_c.pop_back();
AdjustDown(0);
}
private:
Container _c;
};
}
4 仿函数
4.1 什么是仿函数?
仿函数是一个重载了 operator() 运算符的一个类,这个类实例化出的对象具有函数的功能。
下例定义了一个仿函数:
template<class T>
class Compare
{
public:
void operator()(const T& a, const T& b)
{
if (a > b)
cout << a << ">" << b << endl;
else if (a < b)
cout << a << "<" << b << endl;
else
cout << a << "=" << b << endl;
}
};
该仿函数的功能就是当对象调用()重载函数后,比较传入两个值的大小,并打印出来。
4.2 为什么要用仿函数?
实现了一些功能的代码经常在不同的成员函数中用到,但是不好将这些代码实现成一个成员函数。写一个公共的函数可以实现功能但是会使用到全局变量,增加维护难度。仿函数很好地解决了这个问题,它写了一个简单的类,将需要复用的代码写在了operator()重载函数中,外部函数需要使用时只要用这个类实例化出一个对象,就可以像使用函数一样来使用这个对象,完成对应功能。
4.3 仿函数的使用
上面模拟实现priority_queue的代码中就使用了仿函数,是将仿函数与模板结合,通过传入不同的模板参数来控制堆的排序方式。下面在举一个简单使用仿函数的例子:
template<class T>
class Compare
{
public:
void operator()(const T& a, const T& b)
{
if (a > b)
cout << a << ">" << b << endl;
else if (a < b)
cout << a << "<" << b << endl;
else
cout << a << "=" << b << endl;
}
};
int main()
{
Compare<int> com;
com(2, 3);
com(1, 1);
com(9, 1);
}
运行结果如下:
这里就把类实例化出的对象当作函数一样来使用了。