C++priority_queue模拟实现

前言

作者今天上午模拟实现了C++stl的queue,晚上实现priority_queue后,写下了这篇博客

prority_queue也是一个容器适配器,不过它的底层是用数组来实现的,即默认为vector容器

它还多了第三个模板参数,一个用于改变比较方式的类,通过设置不同的类,能实现大根堆或者小根堆

1. 仿函数的使用

cpp 复制代码
    template <class T>
    struct less
    {
        bool operator()(const T &x, const T &y) const
        {
            return x < y;
        }
    };
cpp 复制代码
    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. 向上调整算法时,父节点左右孩子也作比较

cpp 复制代码
            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成员函数

cpp 复制代码
    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()需要对元素个数做检查

cpp 复制代码
        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关键字 修饰函数的作用

cpp 复制代码
        explicit MyPriorityQueue(const Compare &com = Compare())
            : _com(com)
        {
        }

explicit防止了实参的隐式类型转换

因为隐式类型转换会带来代码可读性的问题

以及误写出代码时,不好排查

6. MyPriorityQueue仿函数类对象 构造函数存在的意义

cpp 复制代码
        explicit MyPriorityQueue(const Compare &com = Compare())
            : _com(com)
        {
        }

用仿函数类对象 来构造 MyPriorityQueue,是为了适配各种仿函数类,因为除了作者写的greater<int>和less<int>以外,还会有带状态变量的仿函数类

cpp 复制代码
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或者其它同名变量、类、函数等是哪个命名空间里的,防止命名冲突的发生

总体实现

cpp 复制代码
#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;
    };
}

测试代码

cpp 复制代码
#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

相关推荐
lly2024061 小时前
Python 列表(List)
开发语言
skilllite作者1 小时前
SkillLite 技术演进笔记:Workspace、沙箱与进化
java·开发语言·前端·笔记·安全·agentskills
程序员zgh1 小时前
C++ decltype 关键字 详解
c语言·开发语言·c++
weixin_307779131 小时前
SparkPySetup:基于Python的Windows 11 PySpark环境自动化搭建工具
大数据·开发语言·python·spark
Y学院1 小时前
鸿蒙ArkTS动画开发全解析:从基础入门到实战精通
开发语言·鸿蒙
DevilSeagull1 小时前
Rust 结构体详解:从定义到实例化的指南
开发语言·算法·安全·rust
feng_you_ying_li1 小时前
linux之程序地址空间
开发语言
孬甭_1 小时前
自定义类型:结构体
c语言·开发语言
乐观勇敢坚强的老彭2 小时前
C++信奥洛谷循环章节练习题
java·c++·算法