C++中priority_queue模拟实现

 更新时间:2026年04月24日 08:23:51   作者:Flyfish25  
本文主要介绍了C++中priority_queue模拟实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

作者今天上午模拟实现了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模拟内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • OpenCV实现图像轮廓检测以及外接矩形

    OpenCV实现图像轮廓检测以及外接矩形

    这篇文章主要为大家详细介绍了OpenCV实现图像轮廓检测以及外接矩形,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • C语言数组应用实现三子棋游戏

    C语言数组应用实现三子棋游戏

    这篇文章主要为大家详细介绍了C语言数组应用实现三子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • 深入解析最长公共子串

    深入解析最长公共子串

    本篇文章是对最长公共子串进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C语言实现简单学生成绩管理系统项目

    C语言实现简单学生成绩管理系统项目

    这篇文章主要为大家详细介绍了C语言实现简单学生成绩管理系统项目,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • QT中QChart绘制折线图

    QT中QChart绘制折线图

    本文主要介绍了QChart绘制折线图,Qt Charts基于Qt的Graphics View架构,其核心组件是QChartView 和 QChart,感兴趣的可以了解一下
    2022-04-04
  • C语言实现通讯录的详细代码

    C语言实现通讯录的详细代码

    本文详细讲解了C语言实现通讯录的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • C++AVL树4种旋转详讲(左单旋、右单旋、左右双旋、右左双旋)

    C++AVL树4种旋转详讲(左单旋、右单旋、左右双旋、右左双旋)

    AVL树即平衡二叉搜索树,平衡因子bf=右子树的高度-左子树的高度,bf为0,-1,1时,此树即平衡,下面这篇文章主要给大家介绍了关于C++AVL树4种旋转(左单旋、右单旋、左右双旋、右左双旋)的相关资料,需要的朋友可以参考下
    2022-11-11
  • C语言结构体指针的示例代码

    C语言结构体指针的示例代码

    本文主要介绍了C语言结构体指针的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-07-07
  • C++如何去除cpp文件的注释详解

    C++如何去除cpp文件的注释详解

    在日常工作中,我们会给c/c++代码写上一些注释,但是往往为了保持最终的代码尽可能小,我们需要删除注释,手动删除太缓慢了,下面这篇文章主要给大家介绍了关于C++如何去除cpp文件注释的相关资料,需要的朋友可以参考下
    2022-09-09
  • C++之const和static的使用及说明

    C++之const和static的使用及说明

    C++中const与static的用法:const限制变量/对象只读性,确保数据安全;static控制变量/函数的存储周期与作用域,实现数据共享,两者结合的static const用于定义类级常量,需注意const变量不可强制修改、static局部变量线程安全问题及类static成员必须类外初始化的要点
    2025-09-09

最新评论