C++:优先队列的模拟实现

1. 行为:堆逻辑

优先队列类似这种数据结构,在堆中可以随时插入和删除元素,并且可以访问堆顶元素。

堆的C语言实现方式和基础知识可以阅读此篇博客,这里不再过多赘述:

堆(C语言)_c语言中的堆是什么-CSDN博客

2. 实现方式:容器适配器

优先队列是一种容器适配器,其底层容器默认使用vector,并对它进行"适配":

  • 保留vector的连续存储能力
  • 增加了堆的优先级规则
  • 屏蔽了随机访问的操作,仅仅提供了访问堆顶元素的接口

注意,底层容器应该具有以下操作权限:

  1. **empty():**检测容器是否为空
  2. **size():**获取容器中元素大小
  3. **front():**返回容器首元素
  4. **push_back():**在容器尾部插入元素
  5. **pop_back():**删除尾部元素

另外,容器还需支持随机访问迭代器,因为堆的核心操作离不开快速访问父子节点和两两交换节点元素。

vector和deque都可以满足上述条件,但由于deque的存储不完全连续,频繁访问的效率较低,因此设置vector为默认的底层容器。

这里设置优先队列的私有成员变量为底层容器和比较逻辑:

cpp 复制代码
private:
    Container c; //底层容器
    Compare comp; //比较规则

3. 模拟实现

3.1 核心操作和相关知识

3.1.1 构造优先队列

将一个容器中的元素整理为堆,标准做法是传入容器的首尾迭代器,然后建堆。

建堆的两种常见方式:

  1. 复用库中的堆算法
  2. 从最后一个非叶子节点向下调整建堆

在模拟实现中我们选择第二种,可以更清晰的展示堆的构建逻辑:

cpp 复制代码
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
    :c(first, last)
{
    //建堆
    // make_heap(c.begin(), c.end(), comp);

    for (int i = (c.size()-1-1)/2; i >= 0; i--)
    {
        adjust_down(i);
    }
}

3.1.2 push

**功能:**尾部插入元素,并整理为堆。

具体实现步骤:

  1. 底层容器对元素进行尾插操作
  2. 堆的向上调整算法

代码如下:

cpp 复制代码
void push(const T& x)
{
    c.push_back(x);
    
    //库中堆算法: push_heap(c.begin(), c.end(), comp);
    adjust_up(c.size() - 1);
}

//手动实现的向上调整算法
void adjust_up(size_t child)
{
    while (child > 0)
    {
        size_t parent = (child - 1) / 2;
        if (comp(c[parent], c[child]))
        {
            break;
        }

        swap(c[child], c[parent]);
        child = parent;
    }
}

3.1.3 pop

**功能:**删除堆顶元素

具体实现步骤:

  1. 交换首尾元素
  2. 删除尾部元素
  3. 对新的堆顶元素进行向下调整算法

代码如下:

cpp 复制代码
void pop()
{
    //堆算法实现交换和调整: pop_heap(c.begin(), c.end(), comp);
    swap(c[0], c[c.size() - 1]);
    adjust_down(0);
    
    c.pop_back();
}

// 手动实现的向下调整算法
void adjust_down(size_t parent)
{
    size_t child = parent * 2 + 1;
    while (child < c.size())
    {
        if (child + 1 < c.size() && comp(c[child], c[child + 1]))
        {
            ++child;
        }
        if (comp(c[parent], c[child]))
        {
            break;
        }

        swap(c[parent], c[child]);
        parent = child;
        child = parent * 2 + 1;
    }
}

3.1.4 比较逻辑

由于模板中的默认比较逻辑是小于,因此优先队列默认创造大堆。在模拟实现时需要额外留意向上和向下调整算法的比较结果。

优先队列的模板如下:

cpp 复制代码
//默认vector,大堆
template< class T, class Container = std::vector<T>, class Compare = less<T> >

如果需要创建小堆,则需另外传入比较逻辑greater<T>:

(以int为例)

cpp 复制代码
vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

//默认大堆
priority_queue<int> q1(v.begin(), v.end());

//小堆
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());

如果需要显式传入比较逻辑,则第二个参数不能省略,不能跳过一个有默认值的参数,去指定后面的参数!

另外,如果堆中的元素为自定义类型 ,则需要重载<和>运算符:大堆重载<,小堆重载>

3.1.5 仿函数

仿函数的本质是 ,重载了operator(),从而使对象可以像函数一样使用,因此被称为仿函数。

优先队列里体现在类型less<T>和greater<T>,这是C++库里提供的"仿函数类",使用时需要引入<functional>头文件。简化的实现逻辑如下:

cpp 复制代码
template <typename T>
struct MyLess {
    bool operator()(const T& a, const T& b) const
    {
        return a < b;
    }
};

优先队列内部会创建这个类型的对象:Compare comp;

随后在成员函数中调用这个对象,就可以发挥比较的功能了。例如之前提到的向上调整算法:

cpp 复制代码
void adjust_up(size_t child)
{
    while (child > 0)
    {
        size_t parent = (child - 1) / 2;
        if (comp(c[parent], c[child]))  //小于
        {
            break;
        }

        swap(c[child], c[parent]);
        child = parent;
    }
}

仿函数可以通过创建临时对象 使用,也可以通过实例化对象使用:

cpp 复制代码
//创建临时对象
greater<int>()(a, b);

//实例化对象
greater<int> comp;
comp(a, b);

优先队列的模拟实现中采用了实例化的方式。

3.2 代码实现

cpp 复制代码
#include<iostream>
#include<vector>
#include<functional>
using namespace std;

namespace tairitsu_h
{
    template< class T, class Container = std::vector<T>, class Compare = less<T> >

    class priority_queue
    {
    public:

        priority_queue()
        {}

        template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last)
            :c(first, last)
        {
            //建堆
            // make_heap(c.begin(), c.end(), comp);
            for (int i = (c.size()-1-1)/2; i >= 0; i--)
            {
                adjust_down(i);
            }
        }

        bool empty() const
        {
            return c.empty();
        }

        size_t size() const
        {
            return c.size();
        }

        T& top() const
        {
            return c[0];
        }

        void push(const T& x)
        {
            c.push_back(x);
            
            //库中堆算法: push_heap(c.begin(), c.end(), comp);
            adjust_up(c.size() - 1);
        }

        void pop()
        {
            //堆算法实现交换和调整: pop_heap(c.begin(), c.end(), comp);
            swap(c[0], c[c.size() - 1]);
            adjust_down(0);
            
            c.pop_back();
        }

    private:
        Container c; //底层容器
        Compare comp; //比较规则

        void adjust_up(size_t child)
        {
            while (child > 0)
            {
                size_t parent = (child - 1) / 2;
                if (comp(c[parent], c[child]))
                {
                    break;
                }

                swap(c[child], c[parent]);
                child = parent;
            }
        }

        void adjust_down(size_t parent)
        {
            size_t child = parent * 2 + 1;
            while (child < c.size())
            {
                if (child + 1 < c.size() && comp(c[child], c[child + 1]))
                {
                    ++child;
                }
                if (comp(c[parent], c[child]))
                {
                    break;
                }
    
                swap(c[parent], c[child]);
                parent = child;
                child = parent * 2 + 1;
            }
        }
    };
};

//鸽了比较久,在忙学校的一些事ww

//封面:凯尔希(❁´◡`❁)

相关推荐
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第48题】【JVM篇】第8题:JVM 里的有几种 ClassLoader?为什么会有多种?
java·开发语言·jvm·面试
wjs20241 小时前
SQLite Having 子句详解
开发语言
lzh200409191 小时前
效率与安全并重:C++ 线程安全
linux·c++
AIBox3651 小时前
Claude 中转站怎么接入:2026 年国内调用 Claude API 的方法、能力与示例
服务器·开发语言·人工智能·gpt·php·python3.11
Shan12051 小时前
RAII妙用:使用标准库的包装器
开发语言·c++
才疏学浅7431 小时前
批量下载鹏程实验室数据的方法
java·开发语言·word
Hua-Jay1 小时前
OpenCV联合C++/Qt 学习笔记(十八)----二维码检测及积分图像
c++·笔记·qt·opencv·学习
皮卡祺q1 小时前
【JVM】:类加载机制,jvm内存布局,垃圾回收,String 不可变性源码分析
java·开发语言·jvm·多线程·string
JAVA面经实录9171 小时前
Java核心底层原理全集(终版无遗漏·生产级PDF)
java·开发语言·学习