1 .了解优先级队列
优先级队列是一种容器适配器,根据一些严格的弱排序标准,专门设计使其第一个元素始终是它所包含的元素中最大的元素。
此上下文类似于堆,其中可以随时插入元素,并且只能检索最大堆元素(优先级队列中顶部的元素)。
优先级队列是作为容器适配器实现的,容器适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素是从特定容器的“背面”弹出的,该容器称为优先级队列的顶部。
基础容器可以是任何标准容器类模板,也可以是其他一些专门设计的容器类。容器应通过随机访问迭代器访问,并支持以下操作:
- empty()
- size()
- front()
- push_back()
- pop_back()
标准容器类并满足这些要求。默认情况下,如果未为特定类实例指定容器类,则使用标准容器。
需要支持随机访问迭代器,以便始终在内部保持堆结构。这是由容器适配器通过自动调用算法函数自动完成的,并在需要时自动完成。vector、deque、priority_queue、vector、make_heappush_heap、pop_heap
2.优先级队列的相关接口
优先级队列的接口有如下几种。对于优先级队列我们默认是它的大的数优先级高。其底层是一个堆。也就是说,我们默认是大堆,所以大的数优先级高。如果是一个小堆,那么就是小的优先级高
1.常用接口函数
我们来随便使用一下这些接口吧:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
void test_priority_queue()
{
priority_queue<int> pq;
pq.push(123);
pq.push(1045);
pq.push(2);
pq.push(3);
pq.push(4);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
int main()
{
test_priority_queue();
}
运行结果:
可以看到,默认是一个大堆,但是我们会注意到,它库里面默认传的是less,但是却是一个大堆,这里需要额外注意一下。
如果想要是一个小堆的话,我们需要将这个less替换为greater:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
void test_priority_queue()
{
priority_queue<int,vector<int>,greater<int>> pq;
pq.push(123);
pq.push(1045);
pq.push(2);
pq.push(3);
pq.push(4);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
int main()
{
test_priority_queue();
}
运行结果:
在这里我们传的less,greater这些也称之为仿函数。也就是说,通过仿函数控制实现大小堆.除此之外,这里除了可以传vector以外,还可以传递deque,但是由于堆需要大量访问[]运算符,所以deque的效率不高。
2.构造函数
如下所示,可以无参构造,也可以用迭代器区间进行初始化。
3.优先队列的模拟实现
优先级队列,主要还是堆的逻辑的实现。即堆的构造,向上调整和向下调整。
这些我们在数据结构讲过了,我直接上源码了
template<class T, class Container = vector<T>>
class priority_queue
{
private:
void AdjustDown(int parent)
{
int child = parent * 2 + 1;
while (child<_con.size())
{
if (child + 1 < _con.size() && _con[child] < _con[child + 1])
{
child++;
}
if (_con[child] > _con[parent])
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[child] > _con[parent])
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
public:
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
first++;
}
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(i);
}
}
priority_queue()
{}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
const T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
既然这些容器都学完了,这里附上我的一道错题:
假设cont是一个Container 的示例,里面包含数个元素,那么当CONTAINER为:
1.vector 2.list 3.deque 会导致下面的代码片段崩溃的Container 类型是( )
int main()
{
Container cont = { 1, 2, 3, 4, 5};
Container::iterator iter, tempIt;
for (iter = cont.begin(); iter != cont.end();)
{
tempIt = iter;
++iter;
cont.erase(tempIt);
}
}
A.1, 2
B.2, 3
C.1, 3
D.1, 2, 3
答案选择C
解析:
各个容器的erase(pos)实现:
1. vector,erase(pos),直接把pos+1到finish的数据拷贝到以pos为起点的区间上,也就是vector的长度会逐渐变短,同时iter会逐渐往后移动,直到iter == cont.end(),由于容器中end()返回的迭代器是最后一个元素的下一个(这个地方没有任何值),现在考虑这个状态前一个状态,此时要删除的点是iter, tempIt = iter, ++iter会指向此时的end(),但是执行erase(tempIt)之后,end()向前移动了!!!问题来了,此时iter空了!!!不崩溃才怪。
2. list,erase(pos),干的事情很简单,删除自己,前后的节点连接起来就完了,所以iter自增的过程不会指空,不会崩溃喽。
3.deque,erase(pos),与vector的erase(pos)有些类似,基于结构的不同导致中间有些步骤不太一致。先说说deque的结构(这个结构本身比较复杂,拣重要说吧,具体看STL源码),它是一个双向开口的连续线性空间,实质是分段连续的,由中控器map维持其整体连续的假象。其实题中只要知道它是双向开口的就够了(可以在头部或尾部增加、删除)。在题中有erase(pos),deque是这样处理的:如果pos之前的元素个数比较少,那么把start到pos-1的数据移到起始地址为start+1的区间内;否则把pos后面的数据移到起始地址为pos的区间内。在题中iter一直往后移动,总会出现后面数据比前面少的时候,这时候问题就和1一样了,必须崩溃!
解析思路来源:会导致上面的代码片段崩溃的CONTAINER类型是?_完美世界笔试题_牛客网
来源:牛客网
4.仿函数
1.仿函数介绍
我们知道对于优先级队列可以用仿函数改变其是大堆还是小堆。根据底层逻辑可知,仿函数应该就是改变了大小比较。才改变的行为。我们可以写一个简单的仿函数类
如下所示就是一个最简单的仿函数
class less
{
public:
bool operator()(int x, int y)
{
return x < y;
}
};
这样我们就可以类似于一个函数一样进行比较大小了,仿函数即函数对象,可以让类对象像函数一样使用
有了仿函数,我们就可以在前面的优先级队列中使用仿函数来切换大堆小堆了。在C语言中,我们想要实现这个功能只有使用函数指针。而这个仿函数就刚好可以替换掉函数指针。因为函数指针的弊端太明显了,它太过于复杂了,可读性不好。
2.优先级队列添加仿函数
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
private:
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]))
{
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]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
public:
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
first++;
}
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(i);
}
}
priority_queue()
{}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
const T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
template<class T>
class less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<class T>
class greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
3.需要自己写仿函数的情形
我们的上面的仿函数是模拟库里面的行为,上面的仿函数在库里面早已给出,我们无需自己动手写。但是有时候我们也需要自己去写一个仿函数。
如我们存储的是一个指针,而不是一个对象,等情况
struct LessTime
{
bool operator()(Time* x, Time* y)
{
return *x < *y;
}
};
5.优先级队列完整代码
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
private:
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]))
{
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]))
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
public:
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
first++;
}
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(i);
}
}
priority_queue()
{}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
const T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
template<class T>
class less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
template<class T>
class greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};