STL C++详解——priority_queue的使用和模拟实现 堆的使用

priority_queue的使用

std::priority_queue 是 C++ 标准模板库(STL)中的一个容器适配器,提供了优先队列的功能。

优先队列:是一种特殊的队列,队列中的每个元素都有与之关联的优先级,优先级高的元素会先出队,而不是像普通队列那样遵循先进先出(FIFO)原则。

使用场景:在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

priority_queue的定义方式

方式一: 使用vector作为底层容器,内部构造大堆结构。

复制代码
priority_queue<int, vector<int>, less<int>> q1;

方式二: 使用vector作为底层容器,内部构造小堆结构。

复制代码
priority_queue<int, vector<int>, greater<int>> q2;

方式三: 不指定底层容器和内部需要构造的堆结构。(默认情况下priority_queue是大堆

复制代码
priority_queue<int> q;

注意⚠️:当我们调用less时是大堆,greater是小堆。(并不知道祖师爷是怎么想的)

功能 接口 描述 复杂度 示例代码
插入元素 push(const T& value) 向优先队列插入一个元素,插入后自动调整维持堆性质 O(logn) std::priority_queue<int> pq; pq.push(5);
获取堆顶元素 top() 返回优先队列的队首元素(大顶堆为最大值,小顶堆为最小值) O(1) std::priority_queue<int> pq; pq.push(5); int top = pq.top();
移除堆顶元素 pop() 移除优先队列的队首元素,移除后重新调整堆结构 O(logn) std::priority_queue<int> pq; pq.push(5); pq.pop();
获取元素数量 size() 返回优先队列中元素的数量 O(1) std::priority_queue<int> pq; pq.push(5); size_t s = pq.size();
检查是否为空 empty() 检查优先队列是否为空,空则返回 true,否则返回 false O(1) std::priority_queue<int> pq; bool isEmpty = pq.empty();
复制代码
#include <iostream>
#include <functional>
#include <queue>
using namespace std;
int main()
{
	priority_queue<int> q;
	q.push(3);
	q.push(6);
	q.push(0);
	q.push(2);
	q.push(9);
	q.push(8);
	q.push(1);
	while (!q.empty())
	{
		cout << q.top() << " ";
		q.pop();
	}
	cout << endl; //9 8 6 3 2 1 0
	return 0;
}

priority_queue的模拟实现

priority_queue的底层实际上就是堆结构,实现priority_queue之前,我们先认识两个重要的堆算法。

注意⚠️:我以less<T>为例,在STL中是大堆,我是以大堆实现的

堆的性质

堆具有以下性质

堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;

堆总是⼀棵完全⼆叉树。

⼆叉树性质

对于具有 n 个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的数组顺序对所有结点从

0 开始编号,则对于序号为 i 的结点有:

  1. 若 i>0 , i 位置结点的双亲序号:(i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点
  2. 若 2i+1<n ,左孩⼦序号: 2i+1 ,2i+1>=n 否则⽆左孩⼦
  3. 若 2i+2<n ,右孩⼦序号: 2i+2 ,2i+2>=n 否则⽆右孩⼦

向上调整算法

向上调整算法

先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后

插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可

复制代码
void AdjustUp(int child)
{
	less<T> com;
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (_con[parent] < _con[child])
		{
			swap(_con[child], _con[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

堆的向下调整算法

堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后⼀个数据⼀换,然后删除数组最后⼀个数据,再进⾏

向下调整算法。

向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整。

向下调整算法

将堆顶元素与堆中最后⼀个元素进⾏交换

删除堆中最后⼀个元素

将堆顶元素向下调整到满⾜堆特性为⽌

复制代码
void AdjustDown(int parent)
{
	less<T> com;

	size_t child = parent * 2 + 1;
	while (child < _con.size())
	{
		// 假设法,选出左右孩子中小的那个孩子
		if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		{
			++child;
		}

		if (_con[parent] < _con[child])
		{
			swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

priority_queue的模拟实现

成员函数 实现方法
push 在容器尾部插入元素后进行一次向上调整算法
pop 将容器头部和尾部元素交换,再将尾部元素删除,最后从根结点开始进行一次向下调整算法
top 返回容器的第0个元素
size 返回容器的当前大小
empty 判断容器是否为空
复制代码
#pragma once
#include<vector>//优先队列默认使用 vector 作为底层容器
namespace wlw
{
    // 定义一个仿函数 less,用于实现小于比较
    // 仿函数是一个重载了函数调用运算符(),可像函数一样使用
    template <class T>
    struct less
    {
        bool operator() (const T& x, const T& y) const
        {
            return x < y;
        }
    };
    // 定义一个仿函数 greater,用于实现大于比较
    template <class T>
    struct greater
    {
        bool operator() (const T& x, const T& y) const
        {
            return x > y;
        }
    };
    // 定义一个模板类 priority_queue,实现优先队列的功能
    // T 表示存储的元素类型
    // Container 表示底层容器类型,默认使用 vector<T>
    // Compare 表示比较器类型,默认使用 less<T>,即大顶堆
    template<class T, class Container = std::vector<T>, class Compare = less<T>>
    class priority_queue
    {
    public:
        // 使用 default 关键字强制编译器生成默认构造函数
        priority_queue() = default;//因为下面使用了初始化列表编译器不能默认生成
        template <class InputIterator>// InputIterator 是一个模板参数,表示输入迭代器类型
        priority_queue(InputIterator first, InputIterator last)
            :_con(first, last) 
        {
            // 建堆操作,从最后一个非叶子节点开始,依次进行向下调整
            // 最后一个非叶子节点的索引为 (size - 1 - 1) / 2 其中size-1为最后一个索引位置
            for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
            {
                AdjustDown(i);
            }
        }
        // 向上调整
        void AdjustUp(int child)
        {
            // 创建一个比较器对象
            Compare com;
            // 计算父节点的索引
            int parent = (child - 1) / 2;
            // 当子节点索引大于 0 时,继续调整
            while (child > 0)
            {
                // 使用比较器对象比较父节点和子节点
                if (com(_con[parent], _con[child]))
                {
                    // 如果父节点小于子节点(根据比较器规则),则交换它们
                    std::swap(_con[child], _con[parent]);
                    // 更新子节点和父节点的索引
                    child = parent;
                    parent = (parent - 1) / 2;
                }
                else
                {
                    // 如果父节点大于等于子节点,说明堆的性质已经满足,退出循环
                    break;
                }
            }
        }

        // 插入元素到优先队列中
        void push(const T& x)
        {
            // 将元素添加到底层容器的末尾
            _con.push_back(x);
            // 调用 AdjustUp 函数进行向上调整,维护堆的性质
            AdjustUp(_con.size() - 1);
        }

        // 向下调整函数,用于在删除堆顶元素后维护堆的性质
        void AdjustDown(int parent)
        {
            // 创建一个比较器对象
            Compare com;
            // 计算左子节点的索引
            size_t child = parent * 2 + 1;
            // 当子节点索引小于容器大小时,继续调整
            while (child < _con.size())
            {
                // 假设左子节点是较小(或较大,根据比较器规则)的节点
                // 如果右子节点存在且右子节点比左子节点小(或大),则更新子节点索引为右子节点
                if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
                {
                    ++child;
                }
                // 使用比较器对象比较父节点和子节点
                if (com(_con[parent], _con[child]))
                {
                    // 如果父节点小于子节点(根据比较器规则),则交换它们
                    std::swap(_con[child], _con[parent]);
                    // 更新父节点和子节点的索引
                    parent = child;
                    child = parent * 2 + 1;
                }
                else
                {
                    // 如果父节点大于等于子节点,说明堆的性质已经满足,退出循环
                    break;
                }
            }
        }

        // 删除堆顶元素
        void pop()
        {
            // 交换堆顶元素和最后一个元素
            std::swap(_con[0], _con[_con.size() - 1]);
            // 删除最后一个元素
            _con.pop_back();
            // 调用 AdjustDown 函数进行向下调整,维护堆的性质
            AdjustDown(0);
        }

        // 判断优先队列是否为空
        bool empty()
        {
            // 调用底层容器的 empty 函数进行判断
            return _con.empty();
        }

        // 获取堆顶元素的引用
        const T& top()
        {
            // 返回底层容器的第一个元素
            return _con[0];
        }

        // 获取优先队列中元素的数量
        size_t size()
        {
            // 调用底层容器的 size 函数获取元素数量
            return _con.size();
        }

    private:
        // 底层容器,用于存储优先队列的元素
        Container _con;
    };
}
相关推荐
坚持就完事了10 分钟前
python链表
开发语言·python·链表
zhaoyqcsdn26 分钟前
建造者模式详解及其在自动驾驶场景的应用举例(以C++代码实现)
c++·笔记·设计模式
硬匠的博客29 分钟前
C++面向对象
开发语言·c++·单片机
{⌐■_■}37 分钟前
【go】什么是Go语言的GPM模型?工作流程?为什么Go语言中的GMP模型需要有P?
java·开发语言·后端·golang
why15142 分钟前
滴滴-golang后端开发-企业事业部门-二面
开发语言·网络·golang
旺旺大力包1 小时前
【 React 】重点知识总结 && 快速上手指南
开发语言·前端·react.js
慕容青峰1 小时前
【第十六届 蓝桥杯 省 C/Python A/Java C 登山】题解
c语言·c++·python·算法·蓝桥杯·sublime text
enyp801 小时前
C++抽象基类定义与使用
开发语言·c++
superior tigre1 小时前
C++学习:六个月从基础到就业——C++学习之旅:STL容器详解
c++·学习
硬匠的博客2 小时前
C++IO流
c++