优先级队列,仿函数,反向迭代器
- 优先级队列
- 认识优先级队列
- 模拟实现优先级队列
- 浅谈仿函数
- 仿函数的大致了解
- 仿函数的实现
- 反向迭代器
- 什么是反向迭代器?
- 反向迭代器的实现
- 结语
优先级队列
认识优先级队列
优先级队列(priority_queue)不是队列。优先级队列是一种容器适配器,与栈(stack),队列(queue)具有类似的功能。
先来了解一下优先级队列有哪些功能。看下图:
优先级队列底层是一个堆(默认是大堆),第二个参数默认给的是vector,不适合list,第三个参数是仿函数(函数对象)。
模拟实现优先级队列
大部分成员函数可以通过调用vector的成员函数来实现。
#pragma once
namespace bit {
//目前建堆默认大堆,如果想建小堆怎么办?
template<class T, class Container = vector<T>>
class priority_queue {
private://不想让别人知道这两个函数,可以设置为私有
void AdjustUp(int child)
{
//向上调整需要和兄弟比较吗?
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[child] > _con[parent])
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向下调整删除数据调用
void AdjustDown(int parent)
{
Compare com;
int child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && _con[child + 1] > _con[child])
{
++child;
}
if (com(_con[child], _con[parent]))
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
public:
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
//传过来的迭代器可能不是有序的,所以要排序建堆。
while (first != last)
{
push(*first);
++first;
}
}
priority_queue()//会调用vector的默认构造函数
{}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
const T& top() const
{
return _con[0];
}
bool empty() const
{
return _con.empty();
}
void pop()
{
//为什么swap不交换
swap(_con[0], _con[_con.size() - 1]);
//_con.size() - 1;//为什么这个程序不执行?执行了,但是size没变,只是_con.size() - 1
_con.pop_back();
AdjustDown(0);
}
void swap(priority_queue& pq)
{
std::swap(_con, pq._con);
}
size_t size() const
{
return _con.size();
}
private:
Container _con;
};
}
如果想建小堆,但不增加代码量的冗余,有办法吗?
浅谈仿函数
仿函数的大致了解
怎么建小堆?可以增加一个参数函数指针,这个函数来控制大堆还是小堆。但是函数指针声明比较复杂,有一些声明很长,导致通用性变差。所以,仿函数就来了。
写一个类,类中写相应的方法,这个类作为参数,那么就可以调用该类中的方法了。
仿函数(函数对象)不是函数调用,只是具有函数的功能。
如下代码是对上面代码的补充与修改
仿函数的实现
#pragma once
namespace bit {
template<class T, class Container = vector<T>, class Compare = Greater<int>>
class priority_queue {
private://不想让别人知道这两个函数
void AdjustUp(int child)
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[child], _con[parent]))
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int parent)
{
Compare com;
int child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child + 1], _con[child]))
{
++child;
}
if (com(_con[child], _con[parent]))
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
public:
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
//传过来的迭代器可能不是有序的,所以要排序建堆。
while (first != last)
{
push(*first);
++first;
}
}
priority_queue()//会调用默认的构造函数
{}
void push(const T& val)
{
_con.push_back(val);
AdjustUp(_con.size() - 1);
}
const T& top() const
{
return _con[0];
}
bool empty() const
{
return _con.empty();
}
void pop()
{
//为什么swap不交换
swap(_con[0], _con[_con.size() - 1]);
//_con.size() - 1;//为什么这个程序不执行?执行了,但是size没变,只是_con.size() - 1
_con.pop_back();
AdjustDown(0);
}
void swap(priority_queue& pq)
{
std::swap(_con, pq._con);
}
size_t size() const
{
return _con.size();
}
private:
Container _con;
};
}
template<class T>
struct Less
{
public:
bool operator()(T& x, T& y)
{
return x > y;
}
};
template<class T>
struct Greater
{
public:
bool operator()(T& x, T& y)
{
return x < y;
}
};
仿函数很方便,类中写许多的函数,资源的管理性不错,参数就可以只传一个类,封装性很强,灵活性很高。
反向迭代器
什么是反向迭代器?
**反向迭代器(reverse_iterator)**就是迭代器的相反。rbegin就是end,rend就是begin。
图中表示的rbegin,rend就是反向迭代器。
那如何实现反向迭代器呢?
反向迭代器的实现
仔细观察上面那张图,begin与rbegin,end与rend都是对称的。
·反向迭代器初始化可以调用正向迭代器的初始化
·反向迭代器++就是正向迭代器 的–,–就是正向迭代器的++。
先来看看库里面的反向迭代器是如何实现的。
反向迭代器中有一个正向迭代器成员。++和–操作没有问题,都是调用正向迭代器的++和–函数,但是解引用(*)函数中为什么要–?
我们根据如下代码进行分析:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int>::reverse_iterator rit = v.rbegin();
for (rit != v.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
)rbegin 是由 end 初始化的,所以指向最后一个元素的下一个位置。所以解引用时,rbegin 要到前一个位置,返回前一个位置的元素,rbegin不变,rbegin 和 rend 相等时循环结束。
反向迭代器的实现:
//反向迭代器可以封装一个正向迭代器,各种操作可以调用正向迭代器的函数
template<class Iterator, class Ref, class Ptr>
class ReverseIterator {
private:
Iterator _it;
public:
typedef ReverseIterator<Iterator, Ref, Ptr> Self;
ReverseIterator()//调用正向迭代器的构造函数
{}
ReverseIterator(const Iterator& it)
:_it(it)//调用正向迭代器的拷贝构造函数
{}
Ref operator*()//解引用指向前一个位置的元素,本身不变。
{
Iterator tmp = _it;
return *(--tmp);
}
Self& operator++()//调用正向迭代器--函数
{
--_it;
return *this;
}
Self& operator--()//调用正向迭代器++函数
{
++_it;
return *this;
}
bool operator!=(const Self& s)//调用正向迭代器的!=函数
{
return _it != s._it;
}
};
结语
优先级队列(priority_queue)实现较为简单,与数据结构中的堆很相似。实现优先级队列与实现栈和队列的区别是:栈和队列可以直接复用其他容器的函数,优先级队列则需要自己加一些东西进去加工,如向下调整,仿函数等。
仿函数替换成函数指针。函数指针很麻烦,写个函数声明很长,读懂也容易绕晕。仿函数就是一个类,类中可以写许多函数,封装的很好,使用时很方便。
反向迭代器(reverse_iterator)就是理解解引用(*)的过程,写起来就是封装一个正向迭代器。