priority_queue模拟实现+仿函数+反向迭代器
- 1.priority_quyue
- 1.1priority_queue的使用
- 1.2priority_queue模拟实现
- 1.2.1无参构造+一段区间构造
- 1.2.2push
- 1.2.3pop
- 1.2.4empty
- 1.2.5size
- 1.2.6top
- 2.仿函数
- 2.1什么是仿函数
- 2.2增加仿函数的priority_queue模拟实现完整代码
- 3.反向迭代器
- 3.1list反向迭代器
- 3.2vector反向迭代器
自我名言:只有努力,才能追逐梦想,只有努力,才不会欺骗自己。
喜欢的点赞,收藏,关注一下把!
1.priority_quyue
priority_queue文档介绍
- 优先队列也和栈,队列一样是一种容器适配器,容器适配器即将特定容器类封装作为其底层容器类。
- 优先级底层是堆,而堆又是一个完全二叉树,因此可以选用vector和dequeue来作为特定的容器,dequeue头插头删效率高,但是随机访问效率低一些。因此选择vector作为特定的容器更为合适。
- 优先级队列默认是一个大堆。
- 优先级队列规定优先级高的先出。
priority_queue默认是一个大堆,这是由第三个参数less(仿函数)所控制的,虽然less明面意思是小于,但是默认是一个大堆,默认大的优先级较高,大的先出。greater(仿函数),明面意思是大于,但是默认是一个小堆,默认小的优先级比较高,小的先出。
这里先不介绍仿函数,直接用就可以了,下面再详细讲。
1.1priority_queue的使用
函数说明 | 接口说明 |
---|---|
priority_queue()/priority_queue(first,last) | 构造一个空的优先级队列 |
empty() | 检测优先级队列是否为空,是返回true,否则返回false |
push(x) | 在优先级队列中插入元素x |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
top() | 返回优先级队列中最大(最小元素),即堆顶元素 |
注意缺省值不能隔着传。
写一道题来熟悉接口。
215. 数组中的第K个最大元素
在数据结构的学过堆,并且这里问题提供两个思路
思路一:
堆排序
时间复杂度:O(N*log2N)
思路二:
拿N个元素建立一个大堆,然后popK-1次,这样top拿的就是第K个最大的元素。
时间复杂度:建堆O(N),调整堆O(Klog2N) ----> O(N+Klog2N)
思路三:
拿K个元素建立一个小堆,再将N-K个元素与堆顶元素比较,大于堆顶元素就入堆,最后堆顶元素就是就是第K个大的元素。
时间复杂度:O(K+(N-K)*log2K) ----->O(N)
这里给出第三个思路的代码。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//前k个建小堆
priority_queue<int,vector<int>,greater<int>> pq(nums.begin(),nums.begin()+k);
//N-k与堆顶元素比较,大于堆顶就入堆,再次调整成小堆
for(size_t i=k;i<nums.size();++i)
{
if(nums[i]>pq.top())
{
pq.pop();
pq.push(nums[i]);
}
}
return pq.top();
}
};
1.2priority_queue模拟实现
关于这的一些向上调整算法和向下调整算法再堆的实现,画图和代码分析建堆,堆排序,时间复杂度以及TOP-K问题里都有详细介绍,这里不在细说了。
1.2.1无参构造+一段区间构造
//大堆
template<class T, class Container = vector<T>>
class priority_queue
{
public:
priority_queue()
{}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
//向下调整法建堆
for (int i =( _con.size() - 1 - 1) / 2; i >= 0; --i)
{
adjustdown(i);
}
}
void adjustdown(int parent)
{
int minchild = parent * 2 + 1;
while (minchild < _con.size())
{
if (minchild + 1 < _con.size() && _con[minchild + 1] > _con[minchild])
{
minchild++;
}
if (_con[minchild] > _con[parent])
{
swap(_con[minchild], _con[parent]);
parent = minchild;
minchild = parent * 2 + 1;
}
else
{
break;
}
}
}
private:
Container _con;
};
1.2.2push
//向上调整法
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;
}
}
}
void push(const T& val)
{
_con.push_back(val);
}
1.2.3pop
void pop()
{
assert(!empty());
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjustdown(0);
}
1.2.4empty
bool empty()
{
return _con.empty();
}
1.2.5size
size_t size()
{
return _con.size();
}
1.2.6top
const T& top()
{
assert(!empty());
return _con[0];
}
这里还是和库里有些区别,我们写第三个参数,仿函数,下面我们来介绍它,并且在完善我们的代码。
2.仿函数
2.1什么是仿函数
仿函数其实是一个类的对象,也可以说是函数对象,因为这个类的对象可以像函数一样使用。
仿函数类别有很多种,今天主要介绍less和greater(比大小仿函数 )。
还记得C语言给我们提供了一个qsort函数,这个函数用到了函数指针。
这个函数指针指向一个比大小的函数。
C++既然包容C了,为什么不直接用这个函数指针呢?
C++主要觉得函数指针太麻烦了。因此引入了仿函数。
拿冒泡排序为例。
下面是普通的冒泡排序,如果排升序>,降序<,但是这都不能由使用者来决定,只能由写这个排序的人才能决定。这样给用户的体验感不好,自己不能控制。如果自己能够控制排序的方式就好了,所以这里可以写个比大小的仿函数。
void Bubblesort(int* a,int n)
{
for (int i = 0; i < n - 1; ++i)
{
int flag = 1;
for (int j = 0; j < n - i - 1; ++j)
{
if (a[j] > a[j+1])
{
swap(a[j], a[j + 1]);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
仿函数
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;
}
};
这样看起来为什么一个类的对象可以叫做函数对象了,这个对象可以像函数一样调用。
less<int> lessfunc;
greater<int> greaterfunc
lessfunc(0, 1);
greaterfunc(0,1)
//这里是运算符重载 lessfunc.operator()(0,1)
现在将冒泡排序写成一个函数模板,这样就可以自己控制升序还是降序了。
template<class T,class Compare>
void Bubblesort(T* a,int n,Compare _com)
{
for (int i = 0; i < n - 1; ++i)
{
int flag = 1;
for (int j = 0; j < n - i - 1; ++j)
{
//升序
//if (a[j] > a[j+1])
if (_com(a[j],a[j+1]))
{
swap(a[j], a[j + 1]);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int a[] = { 4,1,2,5,7,3,9,0,6,8 };
Bubblesort(a, sizeof(a) / sizeof(a[0]), bit::greater<int>());
return 0;
}
2.2增加仿函数的priority_queue模拟实现完整代码
#include<iostream>
#include<queue>
#include<vector>
#include<assert.h>
#include<algorithm>
using namespace std;
namespace bit
{
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;
}
};
//大堆
//template<class T, class Container = vector<T>>
template<class T, class Container = vector<T>,class Compare=less<T>>
class priority_queue
{
public:
priority_queue()
{}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
//向下调整法建堆
for (int i =( _con.size() - 1 - 1) / 2; i >= 0; --i)
{
adjustdown(i);
}
}
void adjustdown(int parent)
{
Compare _com;
int minchild = parent * 2 + 1;
while (minchild < _con.size())
{
//if (minchild + 1 < _con.size() && _con[minchild + 1] > _con[minchild])
if (minchild + 1 < _con.size() && _com(_con[minchild],_con[minchild+1]))
{
minchild++;
}
//if (_con[minchild] > _con[parent])
if (_com(_con[parent],_con[minchild]))
{
swap(_con[minchild], _con[parent]);
parent = minchild;
minchild = parent * 2 + 1;
}
else
{
break;
}
}
}
void adjustup(int child)
{
Compare _com;
int parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[child] > _con[parent])
if (_com(_con[parent],_con[child]))
{
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);
}
void pop()
{
assert(!empty());
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjustdown(0);
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
const T& top()
{
assert(!empty());
return _con[0];
}
private:
Container _con;
};
}
3.反向迭代器
在前面vector和list模拟实现,我们只实现了正向迭代器和const正向迭代器,没有去实现反向迭代器。
这是因为反向迭代器也是一个容器适配器。
给我一个正向迭代器我可以适配出反向迭代器。
给我一个const正向迭代器我可以适配出const反向迭代器。
也就是我们复用现有的来帮我们实现所需要的。
3.1list反向迭代器
复用iterator创造reverse_iterator,因此把iterator传过去。
template<class iterator,class Ref ,class Ptr>
class Reverseiterator
{
public:
//构造
Reverseiterator(iterator It)
:_it(It)
{}
private:
iterator _it;
};
//++rit
self& operator++()
{
--it;
return *this;
}
//rit++
self operator++(int)
{
iterator tmp(*this);
--it;
return tmp;
}
//--rit
self& opeartor--()
{
++it;
return *this;
}
//rit--
self operator--(int)
{
iterator tmp(*this);
++it;
return tmp;
}
Ref operator*()
{
iterator tmp = _it;
//先--,在解引用
return *(--tmp);
}
//针对自定义类型
Ptr operator->()
{
return &(operator*());
}
完整代码
template<class iterator,class Ref ,class Ptr>
class Reverseiterator
{
typedef Reverseiterator<iterator, Ref, Ptr> self;
public:
Reverseiterator(iterator It)
:_it(It)
{}
Ref operator*()
{
iterator tmp = _it;
return *(--tmp);
}
Ptr operator->()
{
return &(operator*());
}
//++rit
self& operator++()
{
--_it;
return *this;
}
//rit++
self operator++(int)
{
iterator tmp(*this);
--_it;
return tmp;
}
//--rit
self& operator--()
{
++_it;
return *this;
}
//rit--
self operator--(int)
{
iterator tmp(*this);
++_it;
return tmp;
}
bool operator!=(const self& s)
{
return _it != s._it;
}
private:
iterator _it;
};
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
3.2vector反向迭代器
我们的思路和list的反向迭代器一样,把上面代码复用给vector,就实现了vector的反向迭代器。