【C++】STL容器--priority_queue的使用与模拟实现
目录
- 前言
- 一、priority_queue的介绍
- 二、priority_queue的模拟实现
- 构造函数
- push
- pop
- top
- empty/size
- 三、仿函数
- 仿函数实现两个数的比较
- 仿函数比较指针指向的对象
- 四、priority_queue的改进
- 五、完整代码
前言
前面介绍了【C++】STL容器-stack和queue的使用与模拟实现详情请点击查看,今天继续介绍另外一个STL容器–priority_queue,同时讲解仿函数
一、priority_queue的介绍
- 优先级队列是一种容器适配器,其结构类似于堆,默认实现是大堆,即第一个元素总是当前所有元素中最大的,有了priority_queue优先级队列以后当我们想要使用堆的时候我们就不需要再自己造轮子,直接调用priority_queue优先级队列即可
- 优先级队列中的结构是大堆或小堆是通过仿函数进行控制,这个知识点在文章后面位置将进行讲解
- 优先级队列被实现为容器适配器,容器适配器即对特定容器类进行封装作为其底层容器类。并且提供一套特定的成员函数来访问其元素
- 由于优先级队列的结构类似于堆,建堆和调整的过程需要大量使用下标进行随机访问,所以这个底层容器类应该支持随机访问,并且支持以下操作:
- empty
- size
- front
- push_back
- pop_back
- vector和deque都支持这些接口,但是由于deque的随机访问没有vector极致deque和vector的对比点击查看,在建堆以及调整的过程需要大量使用下标进行随机访问,所以默认情况下采用vector进行实例化
二、priority_queue的模拟实现

- 我们实现priority_queue的时候要使用类模板来进行实现,T作为priority_queue存储的数据类型,模板参数列表中的模板参数还可以有缺省值,这个缺省值与函数的缺省值(函数的缺省值的是数据)不同的是模板参数的缺省值是类型,默认情况下我们采用vector作为stack的底层容器,即vector这个类型作为Container这个模板参数的缺省值
- 还有一个模板参数Compare其缺省值是less,这个是仿函数,为了更好的讲解和理解会先进行忽略这个模板参数Compare及其仿函数,并且在讲完主体实现后,在主体实现的基础上进行优化引入添加仿函数进行控制大堆和小堆
- 同样的会将向上调整算法和向下调整算法(默认向上调整算法和向下调整算法的实现的堆是大堆的形式)的实现使用私有访问限定符封装起来,因为这是内部接口,不需要对外提供给用户
namespace gy
{template<class T, class Container = vector<T>>class priority_queue{private:void AdjustDown(int parent){int child = parent * 2 + 1;//默认大堆,父亲 >= 孩子while (child + 1 < _con.size() && _con[child + 1] > _con[child])child++;while (child < _con.size()){if (_con[child] > _con[parent]){swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}elsebreak;}}void AdjustUp(int child){int parent = (child - 1) / 2;while (child >= 0){if (_con[child] > _con[parent]){swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}elsebreak;}}public:priority_queue();template<typename InputIterator>priority_queue(InputIterator begin, InputIterator end);void push(const T& x);void pop();const T& top() const;bool empty() const;size_t size() const;private:Container _con;};
}
构造函数

- 关于函数参数列表中的comp和ctnr这里我们默认不传入,所以第一个就是一个无参的构造,第二个就是使用一段迭代器区间进行构造
- 这里的无参的构造虽然什么都没有做,但是我们不可以不写,因为当我们显示写了使用迭代器区间进行构造函数的时候编译器就不会生成默认的构造函数了,此时当用户使用无参的构造函数去构造priority_queue优先级队列,那么就会没有构造函数去调用,因为迭代器区间的构造函数类型不匹配,且此时编译器没有生成默认的构造函数,所以此时需要我们显示写一个什么都不做的无参的构造函数
priority_queue()
{}
- 由于priority_queue优先级队列中底层是vector实现的,可以存储不同类型的数据,所以同样也可以使用不同类型的迭代器进行构造,所以这里使用迭代器区间进行构造的构造函数我们应该将其设计成一个模板
- priority_queue优先级队列的结构默认是大堆,那么应该将_con管理的无序数据使用向下调整建堆从最后一个父亲节点开始进行调整,直到调整完根节点之后结束,这样就完成了大堆的建立,也就完成了使用迭代器区间构造大堆构造的优先级队列
template<typename InputIterator>
priority_queue(InputIterator begin, InputIterator end)
{while (begin() != end()){_con.push_back(*begin());begin++;}//向下调整建大堆for (int i = (_con.size() - 2) / 2; i >= 0; i--){AdjustDown(i);}
}
push

- 插入一个数据,但是插入后还需要向上调整,维持大堆的结构
void push(const T& x)
{_con.push_back(x);AdjustUp(_con.size() - 1);
}
pop

- 在priority_queue中,删除指删除头部数据,即priority_queue优先级队列结构的堆顶数据,这时候我们将首元素和尾元素进行交换,再进行尾删,再将堆顶数据使用向下调整算法调整
void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}
top

- 返回堆顶元素的引用,即_con[0],由于我们只是读取堆顶元素。并不是要修改它,因此使用const修饰
- 同时使用const修饰this指针指向的对象,让const对象和普通对象都可以进行调用
const T& top() const{return _con[0];}
empty/size
- empty是进行判断容器中的数据是否为空,这里我们调用empty即可实现
- size是进行获取容器中的数据个数,这里我们调用size即可实现
- 仅仅是判断是否为空和获取数据个数,并不对数据进行修改,所以这里我们加const进行修饰this指针指向的对象,让普通对象和const对象都可以进行调用
bool empty() const{return _con.empty();}size_t size() const{return _con.size();}
void test_priority_queue()
{gy::priority_queue<int> pq;pq.push(1);pq.push(12);pq.push(9);pq.push(20);pq.push(-9);cout << pq.size() << endl;cout << pq.empty() << endl;while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}
测试结果如下
三、仿函数
仿函数其本质就是一个类,这个类重载了operator(),使用这个类实例化出的对象可以像函数调用一样去使用
仿函数实现两个数的比较
下面我编写一个Less类,比较两个int类型是否是小于关系,仿函数调用形式和函数调用类似,实际上是我们重载了运算符括号(),才可以这样进行调用
template <class T>
class Less
{
public:bool operator()(const T& x, const T& y){return x < y;}};
int main()
{Less<int> com;int a = 1, b = 5;cout << com(a, b) << endl;cout << com.operator()(a, b) << endl;return 0;
}

- 我们通常将这个仿函数的类定义为模板类,重载小于操作符<或大于操作符>,这样内置类型和自定义类型都可以使用仿函数实例化出的对象像函数调用一样去进行调用
- 用于比较的对象不一定是内置类型的,如果是自定义类型进行传值传参的消耗大,所以我们采用引用传参,这里仅仅是进行比较,不进行修改参数,所以函数的参数列表中的参数我们的类型我们设定为const T&
- 由于不对数据进行修改,所以this指针指向的对象我们加const进行修饰,这样const对象和普通对象都可以使用仿函数进行比较
template <class T>
class Less
{
public:bool operator()(const T& x, const T& y)const{return x < y;}
};template<typename T>
class Greater
{
public:bool operator()(const T& x, const T& y) const{return x > y;}
};
int main()
{Less<int> com1;Greater<int> com2;int a = 1, b = 5;cout << com1(a, b) << endl;cout << com1.operator()(a, b) << endl;cout << com2(a, b) << endl;cout << com2.operator()(a, b) << endl;return 0;
}

仿函数比较指针指向的对象
- 对于指针指向对象的比较,如果我们使用上面的Less和Greater来实现比较是不可以的,因为如果你传入的是指针,实际比较的两个地址的大小,并不是指向数据大小的比较
template <class T>
class LessP
{
public:bool operator()(const T& x, const T& y)const{return *x < *y;}
};
int main()
{LessP<int*> com; // 传入的类型是int*int a = 1, b = 5;int* p1 = &a;int* p2 = &b;cout << com(p1, p2) << endl;return 0;
}

四、priority_queue的改进
- priority_queue优先级队列实现的结构是大堆还是小堆的结构取决于向上调整算法和向下调整算法的比较关系,即父亲和孩子的大小比较关系进而控制的堆结构
- 那么我们可以使用仿函数去实例化出对象,通过这个对象控制比较关系,并且C++STL中,less控制实现的大堆,greater实现小堆结构,显示传入greater就可以控制比较关系实现小堆
//priority_queue.h
template <class T>
class Less
{
public:bool operator()(const T& x, const T& y)const{return x < y;}
};template<typename T>
class Greater
{
public:bool operator()(const T& x, const T& y) const{return x > y;}
};
namespace gy
{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;//less:大堆,父亲 >= 孩子while (child + 1 < _con.size() && com(_con[child], _con[child + 1]))child++;while (child < _con.size()){/*if (_con[child] > _con[parent])*/if (com(_con[parent], _con[child])){swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}elsebreak;}}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[parent], _con[child]);child = parent;parent = (child - 1) / 2;}elsebreak;}}public://.....private:Container _con;};
}

五、完整代码
完整代码,点击查看


