文章目录
- 前言
- 一、priority_queue的底层实现
- 二、使用仿函数控制priority_queue的底层
- 总结
前言
本文章讲解C++STL的容器适配器:priority_queue的实现,并实现仿函数控制priority_queue底层。
一、priority_queue的底层实现
priority_queue叫做优先级队列,它的底层结构是堆,在库中,默认生成的是大堆
在库的实现中,使用vector作为该优先级队列的适配容器。
由于priority_queue也是一个适配器,所以它的接口函数也可以对其他容器的函数进行封装使用。
下面来对priority_queue进行模拟实现。
#pragma once
//优先级队列底层是堆,heap
namespace bit
{
//仿函数
template<class T>
class Less
{
public:
bool operator()(const T& t1, const T& t2)
{
return t1 < t2;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& t1, const T& t2)
{
return t1 > t2;
}
};
//类名不是类型
template<class T, class Container = vector<T>, class Compare = Less<T> >
//默认大堆
class PriorityQueue
{
//com是一个仿函数
Compare com;
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
while (child > 0)
{
//可能右孩子存在且大于左孩子
if (child + 1 < _con.size() && com(_con[child],_con[child + 1]))
{
++child;
}
//如果孩子存在且孩子大于父亲,交换
if (child < _con.size() && com(_con[parent],_con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void AdjustUp(int child)
{
Compare com;
//类名实例化类对象,该类型是一个仿函数,实例化的com可以调用仿函数的比较方法
//记录父亲的下标
int parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
swap(_con[child], _con[parent]);
}
child = parent;
parent = (child - 1) / 2;
}
}
public:
PriorityQueue()
{}
//默认建大堆
template<class InputIterator>
PriorityQueue(InputIterator first, InputIterator end)
{
//插入数据
while (first != end)
{
_con.push_back(*first);
++first;
}
//向下调整建堆
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(i);
}
}
void push(const T& val)
{
//插入元素
_con.push_back(val);
//然后向上调整
AdjustUp(_con.size() - 1);
}
void pop()
{
//1.堆顶和堆尾交换
swap(_con[0], _con[_con.size() - 1]);
//2.删除堆尾
_con.pop_back();
//3.向下调整,恢复堆
AdjustDown(0);
}
T& top()
{
return _con[0];
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
注解:
- 1.在进行构造时,我们使用迭代区间进行构造
-
- (1)向空间中插入数据
-
- (2)向下调整建堆,建N个数据,从第一个非叶子节点开始进行建堆,每次都向下调整,时间复杂度O(N)。
push函数的实现:
向堆尾插入一个元素,插入的元素可能会改变堆的结构,所以我们需要将该元素向上调整,以维护堆的特性。
pop函数的实现:
删除堆顶元素,首先将堆顶元素和堆的最后一个元素进行交换,然后进行尾删,删除后原来的堆尾的元素现在在堆顶,我们需要将其进行向下调整以维持堆的状态。
empty函数:
判断堆是否为空即可。
size函数:
计算堆的元素个数。
top函数:
取堆顶元素,也就是取第一个元素。
priority_queue容器的作用:有需要使用堆的地方就可以使用该容器。
二、使用仿函数控制priority_queue的底层
什么是仿函数?
仿函数:仿造的函数,它并不是一个真正意义上的函数。能够重载operator(),内部实现自己想要的方法,然后实例化出一个方法对象,调用该对象作为参数,此时就可以调用该方法类对象内部实现的operator()方法了。
由于库中的优先级队列的底层结构是堆,且默认是大堆,我们在模拟实现和使用时,如果遇到需要使用小堆的场景,需要改变的东西很多,比如向上调整算法和向下调整算法的比较方法。
现在我们需要指定一个方法,这个方法可以由我们控制,从而实现大小堆。
仿函数:
//仿函数
template<class T>
class Less
{
public:
bool operator()(const T& t1, const T& t2)
{
return t1 < t2;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& t1, const T& t2)
{
return t1 > t2;
}
};
这里实现了两个类,并重载了一个比较方法。
在priority_queue模板的实现中,我们可以多传递一个模板参数:class Compare,也就是多传递一个比较方法。然后我们自己写一个比较方法传递过去,然后在priority_queue的实现中实例化一个方法对象来控制堆的生成。
总结
本文章讲解了仿函数控制priority_queue容器的底层实现的过程以及priority_queue的底层实现。
只要有堆的地方就可以使用priority_queue容器。