【C++杂货铺】优先级队列的使用指南与模拟实现

news/2024/5/20 10:13:07 标签: c++, 开发语言, 优先级队列, , 热门

在这里插入图片描述

文章目录

  • 一、priority_queue的介绍
  • 二、priority_queue的使用
    • 2.1 数组中的第k个最大元素
  • 三、priority_queue模拟实现
    • 3.1 仿函数
    • 3.2 成员变量
    • 3.3 成员函数
      • 3.3.1 构造函数
      • 3.3.2 AdjustDown
      • 3.3.3 push
      • 3.3.4 AdjustUp
      • 3.3.5 pop
      • 3.3.6 empty
      • 3.3.7 size
  • 四、结语

一、priority_queue的介绍

  • 优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。

  • 此上下文类似于,在中可以随时插入元素,并且只能检索最大元素(优先级队列中位于顶部的元素)。

  • 优先级队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue 提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先级队列的顶部。

  • 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问、迭代器访问,并支持以下操作:

    • empty():检测容器是否为空

    • size():返回容器中有效元素的个数

    • front():返回容器中第一个元素的引用

    • push_back():在容器尾部插入元素

    • pop_back():删除容器尾部元素

  • 标准容器类 vector 和 deque 满足这些需求。默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用 vector。

  • 需要支持随机访问迭代器,以便始终在内部保持结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap 和 pop_heap 来自动完成次操作。

二、priority_queue的使用

优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了算法将 vector 中元素构造成的结构,因此 priority_queue 就是,所有需要用到的位置,都可以考虑使用 priority_queue。注意:默认情况下 priority_queue 是大

函数声明接口说明
priority_queue()构造一个空的优先级队列
empty()检测优先级队列是否为空,是返回 true,否则返回 false
top()返回优先级队列中最大(最小)元素,即顶元素
push(x)优先级队列中插入元素 x
pop()删除优先级队列中最大(最下)元素,即顶元素

小Tips:默认情况下,priority_queue 是大(默认第三个模板参数是 less);如果在 priority_queue 中放自定义类型的数据,用户需要在自定义类型中提供 > 或者 < 的重载。

2.1 数组中的第k个最大元素

在这里插入图片描述

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        //建一个大,向下调整建的时间复杂度是O(N)
        priority_queue<int> pq(nums.begin(), nums.end());

        //pop k-1次,时间复杂度是O(K*logN)
        while(--k)
        {
            pq.pop();
        }

        return pq.top();
    }
};

上面这种做法当 K 的值接近 N 的时候,它的时间复杂度就是 O(N*logN),是不满足题目要求的,下面再用另外一种方法来解决。

lass Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        //用前k个数建一个小
        priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k);

        //遍历剩下的 N-k 个,比顶大就换进去
        //时间复杂度是 (N-K)logN
        for(size_t i = k; i < nums.size(); i++)
        {
            if(nums[i] > pq.top())
            {
                pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }
};

上面这种解法是先用数组中的前 K 个元素建一个小,然后从数组的第 K+1 个元素开始往后遍历,遇到比顶元素大的就将顶的元素 pop 出来,将当前元素 push 进去。建的时间复杂度是 O(K),更新小的时间复杂度是 O((N-K)logK),这种做法当 K 很小的时候时间复杂度可以近似看做 O(NlogK),当 K 很大的时候,时间复杂度可以近似看做 O(logK)。

三、priority_queue模拟实现

3.1 仿函数

仿函数本质上一个类,之所以把它叫仿函数是因为这个类的对象可以像函数一样去使用。举例如下:

//一个仿函数
template<class T>
class Less
{
public:
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};


int main()
{
	Less<int> lessfunc;//定义一个对象
	cout << lessfunc(1, 2) << endl;//该类型对象可以像函数一样去使用
	//cout << lessfunc.operator()(1, 2) << endl;//和上面等价
	return 0;
}

小Tips:仿函数一般都是类模板,这样就可以支持不同类型的大小比较,前提是该种类型重载实现了比较运算符。仿函数的诞生是为了代替函数指针,函数指针的可读性比较差。

3.2 成员变量

template<class T, class Container=std::vector<T>, class Compare = Less<T>>
	class priority_queue
	{
	public:
		//成员
	private:
		Container _con;
	};

小Tips:需要注意这里有三个模板参数,第一个模板参数表示要在优先级队列中存储的数据类型;优先级队列本质上也是一个容器适配器,所以第二个模板参数表示优先级队列要使用的底层容器;第三个模板参数是一个仿函数,用来进行大小比较的,因为优先级队列中会涉及到建大还是建小,中间会涉及到比较,如果没有这个仿函数,那么大小比较就只能写死了,这样不太好。

3.3 成员函数

3.3.1 构造函数

priority_queue()
{}

template<class InputeIterator>
priority_queue(InputeIterator first, InputeIterator last)
{
	//先将数据插入
	while (first != last)
	{
		_con.push_back(*first);
		++first;
	}

	//建,从最后一个非叶子节点开始向下调整
	for (int i = (_con.size() - 1) / 2; i >= 0; i--)
	{
		AdjustDown(i);
	}
}

小Tips:迭代器区间构造函数是一个函数模板,只要是能支持 ++ 操作的迭代器区间都可以,即单向迭代器、双向迭代器、随机迭代都可以。其次将数据插入容器后还需要建,这里采用向下调整建,它的时间复杂度是 O(N)。

3.3.2 AdjustDown

void AdjustDown(int parent)
{
	Compare com;
	while (parent * 2 + 1 < (int)_con.size())
	{
		//找到最大的孩子
		int maxchild = parent * 2 + 1;
		if (maxchild + 1 < (int)_con.size() && com(_con[maxchild], _con[maxchild + 1]))
		{
			maxchild++;
		}

		//和父节点比较
		if (com(_con[parent], _con[maxchild]))
		{
			std::swap(_con[maxchild], _con[parent]);
			parent = maxchild;
		}
		else
		{
			break;
		}
	}
}

小Tips:在仿函数的加持下,向下调整代码中的大小比较不再固定,建大和小这一份代码就够了,最终是大还是小是由仿函数来控制的。

3.3.3 push

void push(const T& val)
{
	_con.push_back(val);
	AdjustUp(_con.size() - 1);
}

小Tips:在优先级队列的尾部追加数据,会涉及到向上调整,向上调整的代码如下所示。

3.3.4 AdjustUp

void AdjustUp(int child)
{
	Compare com;
	int parent = (child - 1) / 2;
	while (child > 0)//父亲不会小于0,因此这里的判断条件要用孩子大于0,孩子等于0说明已经调整到根节点,就无需继续调整了
	{
		if (com(_con[parent], _con[child]))
		{
			std::swap(_con[child], _con[parent]);
			child = parent;
			parent = (child - 1) / 2;//这里parent不会小于0
		}
		else
		{
			break;
		}
	}
}

3.3.5 pop

void pop()
{
	std::swap(_con[0], _con[_con.size() - 1]);
	_con.pop_back();
	AdjustDown(0);
}

小Tips优先级队列中出数据,出的是顶的数据,顶的数据也就是容器中的第一个数据,如果底层容器是 vector 那么顶的数据就是下标为 0 的数据,出顶的数据不能直接使用头删,这样会导致后面数据的父子关系全乱了。正确的做法是,将顶的数据和容器中的最后一个数据交换,然后执行 pop_back 去删除,最后还需要从根节点开始执行一次向下调整,让交换到顶的数据去到它应该去的地方。

3.3.6 empty

bool empty()
{
	return _con.size() == 0;
}

3.3.7 size

size_t size()
{
	return _con.size();
}

四、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述


http://www.niftyadmin.cn/n/5023615.html

相关文章

2023年墨西哥 SP/BMV IPC 研究报告

第一章 指数概况 1.1 指数基本情况 墨西哥 S&P/BMV IPC 指数衡量在墨西哥证券交易所 (Bolsa Mexicana de Valores, BMV)上市&#xff0c;规模最大、流动性最高的股票表现。提供一个覆盖墨西哥股市的广泛、具有代表性且可轻易复制的指数。根据多元化要求&#xff0c;按市值…

一站式解决代购烦恼!独家代购商城源码助你实现挣钱新途径!

代购是现代社会一项常见的购物方式&#xff0c;但随之而来的繁琐流程和不确定性常常让人感到困惑和疲惫。然而&#xff0c;现在有了一种全新的解决方案!借助独家代购商城源码&#xff0c;你将能够实现一站式解决代购烦恼&#xff0c;同时也开启了一个挣钱的全新途径。 一、什么…

Windows服务器exe程序无法开机自启动解决办法

Windows服务器exe程序无法开机自启动 问题背景解决办法1. 新建*.reg*文件&#xff0c;将下面的代码放到reg文件中2. WindowsR 调用运行程序命令&#xff1a;*netplwiz*3. 取消勾选 问题背景 我将exe程序放到服务器&#xff0c;然后用注册表的方式设置自启动或使用快捷方式自启…

linux安装nacos2.2.0

1、使用docker拉取镜像&#xff1a;docker pull nacos/nacos-server:v2.2.0 2、下载官方配置文件&#xff1a;https://github.com/alibaba/nacos/releases 3、修改配置文件的数据库连接信息&#xff0c;修改完成后将配置文件移至挂载目录/home/shixp/docker/nacos/conf&#xf…

解决 SLF4J: Class path contains multiple SLF4J bindings.

1. 异常现象 启动springboot项目&#xff0c;抛出警告信息&#xff1a; SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/Users/quanll5/Documents/java_repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.j…

dart 学习 之 Getters and setters

前言 任何需要对属性进行更多控制而不是允许简单字段访问的时候&#xff0c;你都可以自定义 getter 和 setter。 正文 讲解 Getter&#xff08;获取器&#xff09;和Setter&#xff08;设置器&#xff09;是面向对象编程中用于控制对类属性访问的特殊方法。Getter用于获取属…

SpringCloudFoundation

SpringCloudFoundation 一. Nacos 服务中心&配置中心1.1 基础配置1.1.1 配置nacos1.1.4 启动1.1.4.1 单机启动1.1.4.1 集群启动1.1.4.1.1 修改conf\application.properties1.1.4.1.2 创建nacos\conf\cluster.conf 文件1.1.4.1.3 如果出现内存不足:修改启动脚本(bin\start…

Redis 核心数据结构理论解析

一、简述 redis是一个开源的使用C语言编写的一个kv存储系统&#xff0c;是一个速度非常快的非关系远程内存数据库。它支持包括String、List、Set、Zset、hash五种数据结构。 除此之外&#xff0c;通过复制、持久化和客户端分片等特性&#xff0c;用户可以很方便地将redis扩展…