目录
一. 介绍
priority_queue(优先队列)是一种容器适配器,它提供了一种自动排序的数据结构,其中插入的元素按照优先级进行排序。以下是关于priority_queue的介绍:
- 特点:priority_queue是一种具有特定功能的容器,它具有自动排序的特性。每次插入新元素时,会根据元素的优先级自动将其放置在合适的位置,以确保队列中的元素始终按照优先级进行排列。
- 使用容器:priority_queue的实现通过在内部使用其他容器来存储元素。默认情况下,STL中的priority_queue使用vector作为底层容器,并使用堆结构(通常是最大堆)来进行元素的排序。也可以通过指定其他容器类型和排序准则来创建自定义底层容器和排序方式的priority_queue。
- 基本操作:除了插入元素外,priority_queue还提供了其他一些常用的操作,如访问队首元素(top)、判断队列是否为空(empty)以及获取队列的大小(size)。注意,priority_queue没有提供直接访问和修改中间位置元素的功能。
- 排序规则:默认情况下,priority_queue按照元素的大到小进行排序,即根据元素的比较操作符(默认是小于号<)来决定元素的优先级。可以通过自定义元素的比较操作符或传入自定义的比较准则来改变排序规则。
- 适用性:priority_queue通常用于需要维护元素按照优先级进行排序的场景,如任务调度、事件处理、负载均衡等。通过提供插入、访问队首元素等操作,priority_queue为高优先级元素提供了快速访问的方式。
总体上,priority_queue是一种功能强大的容器适配器,可用于实现自动排序的数据结构。通过提供插入、访问队首元素等操作,priority_queue提供了一种方便且高效的方式来管理带有优先级的数据。
这里的 priority_queue 也是一个适配器,但是它并不像 queue 和 stack 一样只是简单的封装,他还是加了一些底层的东西,来达到它自己的功能。
二. 模拟实现
priority_queue 的底层就是我们之前学习过的堆,所以我们只需要堆一个数组里面进行建堆操作就好了,下面我们来看一下如何操作
今天的重点是仿函数~
1. 参数
我们前面说了, priority_queue 是一个适配器,所以我们之前的堆的底层就是一个连续的数组空间,只不过逻辑结构是一棵树,我们既然是适配器,那么我们就让他的 Container 就是默认 vector 就可以了,然后我们的参数也就只有一个 vector<T> 类型的一个对象。
template<class T, class Container = vector<T>>
class priority_queue
{
private:
private:
Container _con;
};
2. 默认构造函数
实际上我们是没有必要给默认构造函数的,因为我们的参数是一个自定义类型,但是我们有时候是需要定义一个对象,然后进行 push 操作,但是我们这个 priority_queue 还需要一个迭代器初始化的一个构造函数,所以我们就必须写一个默认的构造函数,而我们写的主打歌默认的构造函数也是什么都是不需要干的。
priority_queue() {};
3. 迭代器的构造函数
为了方便我们创建一个对象,我们还需要一个迭代器初始化的一个构造函数,而这个构造函数也是不难的,我们只需要讲里面的数据 push_back 到 vector 的对象里面,然后对 vector 里面的数据进行建对操作,我们就讲该对象定义好了。
如果忘记如何建堆,可以看一下我们之前讲的 堆,里面还是比较详细的
void AdjustDown(int parent)
{
Compare com;
int child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && _con[child]< _con[child + 1])
++child;
if (_con[parent] < _con[child])
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else break;
}
}
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
++first;
}
// 建堆
int parent = (_con.size() - 2) / 2;
while (parent >= 0)
{
AdjustDown(parent--);
}
}
我们为了可以快速的测试起来,我们下面就写一个 pop 和 top 这样我们就可以 top 打印,然后 pos 删除了
4. top
top 就是取堆顶数据,我们就是直接返回 vector 里面的 0 号位置的元素
const T& top() const
{
return _con[0];
}
这里加 const 是为了可以让 const 的对象也可以使用,并且该函数是 const 堆不是 const 的对象也是没有影响的。
5. pop
pop 就是删除堆顶元素,我们要是还能想一下之前的堆,如果我们直接删除的话,那么我们的堆就乱了,所以我们不能直接删除,我们需要讲堆顶的元素换在最后的位置,然后子进行删,然后在堆堆顶位置进行向下调整。
我们的向下调整在上面已经说过了,这里也就不放出来了。
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
6. push
push 就是插入,我们可以将新数据插入到最后一个位置,然后对最后一个位置进行向上调整算法,我们的向上调整算法不知道各位还记不记得。
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[parent]< _con[child])
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
8. empty
就是查看是否为空
bool empty() const
{
return _con.empty();
}
9. size
size_t size()
{
return _con.size();
}
三. 仿函数
1. 仿函数编写
我们前面看到我们的 AdjustUp 和 AdjustDown 都是我们将比较大小写死了,但是实际上我们的 priority_queue 是既可以是大堆也可以是小堆,那么我们该怎么做?
这里介绍一下我们一个新名词 仿函数, 我们听到这个名字我们也是有那么一感觉的,仿函数,不是函数,但是像函数,那么我们就看一下。
我们知道我们想对一个 priority_queue 进行大堆的话,那么我们就传一个 less ,less 是我们库函数里面的比较大小的一个仿函数,我们该怎么使用呢? 我们下面还是先看一下使用
void test_priority_queue2()
{
vector<int> v{ 3,7,1,3,0,3,4,6,7,2,8 };
lxy::priority_queue<int, vector<int>, std::greater<int>> pq(v.begin(), v.end());
pq.push(1);
pq.push(5);
pq.push(8);
pq.push(9);
pq.push(2);
pq.push(4);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
}
我们的 priority_queue 默认是大堆,也就是默认是 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;
}
};
我们的仿函数实际上就是个 类 然后我们的这个类里面只有一个函数,就是我们重载一个括号,所以我们就可以使用该类的对象来调用括号重载,所以这样我们就看起来像是使用一个函数一样,然后我们将这个类型传入到我们的模板中,我们的模板中会实例化一个该类的对象然后调用它的重载括号,我们就实现了我们传入less 就是 less 传入 greater 就是 greater。
2. 仿函数使用
我们下面就看一下如何使用,我们的代码里面只有 AdjustUp 和 AdjustDown 使用了比较,所以我们只需要堆这两个函数里面使用仿函数比较就可以了,其实这写出来也就差不多明白了
void AdjustDown(int parent)
{
Compare com;
int child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
++child;
if (com(_con[parent], _con[child]))
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else break;
}
}
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
这个就是今天的重点仿函数,这个还是挺重要的,我们有事时也是需要自己写一个的,所以还是需要好好了解一下~
源码和测试的代码在 码云 里面。