C++中priority_queue模拟实现
作者:Flyfish25
前言
作者今天上午模拟实现了C++stl的queue,晚上实现priority_queue后,写下了这篇博客
prority_queue也是一个容器适配器,不过它的底层是用数组来实现的,即默认为vector容器
它还多了第三个模板参数,一个用于改变比较方式的类,通过设置不同的类,能实现大根堆或者小根堆
1. 仿函数的使用
template <class T>
struct less
{
bool operator()(const T &x, const T &y) const
{
return x < y;
}
}; template <class T, class Container = std::vector<T>, class Compare = less<T>>
class MyPriorityQueue
{
public:比如这样实例化一个对象,MyPriorityQueue<int> q; 第二个和第三个模板参数以及默认给出,那么q对象中有一个成员Compare _com的类型就为less<T>类型,_com是一个less<T>类型的对象
比较两个int类型的变量时,直接_com(a, b)就会调用运算符重载函数去比较
2. 向上调整算法时,父节点左右孩子也作比较
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && _com(_con[child], _con[child + 1]))
{
child++;
}
if (_com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}以大根堆为例,首先要保证堆的父结点大于子结点,作者先对两个子节点进行比较,挑出一个更大的,这样保证了在父子结点交换后,被交换上去的子节点一定比另一个子节点大
3. 为什么仿函数的运算符重载函数要设置为const成员函数
template <class T>
struct less
{
bool operator()(const T &x, const T &y) const
{
return x < y;
}
};比如实例化一个const MyPriorityQueue<int> q;对象,那么q对象的成员变量不能被修改
即Compare _com其实是const Compare _com,那么一个const对象在调用它的成员函数时,无法调用非const成员函数
而operator本身作为比较逻辑,是不会修改对象的,所以要加上const修饰this指针
4. top()和pop()需要对元素个数做检查
void pop()
{
assert(size() > 0);
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
if (size() > 1)
{
AdjustDown(_con.size(), 0);
}
}
T &top()
{
assert(size() > 0);
return _con[0];
}
const T &top() const
{
assert(size() > 0);
return _con[0];
}为了防止删除空vector或者获取空vector里的数据,从而造成非法访问
需要严格保证进行top获取堆顶元素或者删除堆顶元素的时候,保证堆非空
5. explicit关键字 修饰函数的作用
explicit MyPriorityQueue(const Compare &com = Compare())
: _com(com)
{
}explicit防止了实参的隐式类型转换
因为隐式类型转换会带来代码可读性的问题
以及误写出代码时,不好排查
6. MyPriorityQueue仿函数类对象 构造函数存在的意义
explicit MyPriorityQueue(const Compare &com = Compare())
: _com(com)
{
}用仿函数类对象 来构造 MyPriorityQueue,是为了适配各种仿函数类,因为除了作者写的greater<int>和less<int>以外,还会有带状态变量的仿函数类
template<class T>
struct MyComp
{
int flag;
// 构造时传入 flag
MyComp(int f) : flag(f) {}
bool operator()(const T &a, const T &b) const {
if (flag == 1)
return a < b; // 大堆
else
return a > b; // 小堆
}
};比如MyPriorityQueue<int, std::vector<int>, MyComp> q1(MyComp(1));
q1是大根堆,因为构造MyComp时状态为1,比较时会走flag==1的逻辑
MyPriorityQueue<int, std::vector<int>, MyComp> q2(MyComp(2));
q2是小根堆,因为构造MyComp时状态不为1,比较时会走else的逻辑
7. 命名空间防止与stl里面的函数或类发生冲突
自定义的less模板类与std库里面的less模板类同名了,所以为了解决同名冲突,给less,greater,MyPriorityQueue 放在一个命名空间中
那么可以用 命名空间名::来指定less或者其它同名变量、类、函数等是哪个命名空间里的,防止命名冲突的发生
总体实现
#pragma once
#include <vector>
#include <cassert>
namespace MyPriorityQueueModule
{
template <class T>
struct less
{
bool operator()(const T &x, const T &y) const
{
return x < y;
}
};
template <class T>
struct greater
{
bool operator()(const T &x, const T &y) const
{
return x > y;
}
};
template <class T, class Container = std::vector<T>, class Compare = less<T>>
class MyPriorityQueue
{
public:
// 为了支持带状态的比较器
explicit MyPriorityQueue(const Compare &com = Compare())
: _com(com)
{
}
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (_com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T &x)
{
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
void AdjustDown(int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && _com(_con[child], _con[child + 1]))
{
child++;
}
if (_com(_con[parent], _con[child]))
{
std::swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void pop()
{
assert(size() > 0);
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
if (size() > 1)
{
AdjustDown(_con.size(), 0);
}
}
T &top()
{
assert(size() > 0);
return _con[0];
}
const T &top() const
{
assert(size() > 0);
return _con[0];
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
Compare _com;
};
}
测试代码
#include <iostream>
#include <vector>
#include "MyPriorityQueue.hpp"
// 不写 using namespace!
// 带状态比较器
struct MyComp
{
int flag;
MyComp(int f) : flag(f) {}
bool operator()(int a, int b) const {
if (flag == 1) return a < b;
else return a > b;
}
};
int main()
{
// 大根堆(明确写:MyPriorityQueueModule::)
std::cout << "大根堆:";
MyPriorityQueueModule::MyPriorityQueue<int> q1;
q1.push(3);
q1.push(1);
q1.push(5);
q1.push(2);
q1.push(4);
while (!q1.empty()) {
std::cout << q1.top() << " ";
q1.pop();
}
std::cout << std::endl;
// 小根堆
std::cout << "小根堆:";
MyPriorityQueueModule::MyPriorityQueue<int, std::vector<int>, MyPriorityQueueModule::greater<int>> q2;
q2.push(3);
q2.push(1);
q2.push(5);
q2.push(2);
q2.push(4);
while (!q2.empty()) {
std::cout << q2.top() << " ";
q2.pop();
}
std::cout << std::endl;
return 0;
}测试结果
./test
大根堆:5 4 3 2 1
小根堆:1 2 3 4 5
到此这篇关于C++中priority_queue模拟实现的文章就介绍到这了,更多相关C++ priority_queue模拟内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
