【C++】容器适配器之priority_queue 仿函数

news2025/1/9 2:04:25

一、priority_queue 的介绍和使用

1.priority_queue 的介绍

我们和学习之前的容器一样,可以使用cplusplus官网进行学习:priority_queue文档介绍

priority_queue(优先级队列)是一种容器适配器,它 和queue使用同一个头文件,其底层结构是一个堆,并且默认情况下是一个大根堆,此外,priority_queue也不支持迭代器,这是为了不破坏堆的结构使用vec,此外,堆需要进行下标的计算,所以priority_queue使用vector作为它的默认容器适配器

在这里插入图片描述

在这里插入图片描述

priority_queue和stack、queue不同的是,多了一个模板参数-仿函数,仿函数我们在后文进行介绍

【总结】

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

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

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

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

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

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

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

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

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

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

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

2.priority_queue 的使用

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

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

【注意】

priority_queue默认使用的仿函数是less,所以默认建成的堆是大堆,如果我们要实现一个小堆,就需要指定仿函数为greater,该仿函数包含在头文件functional中,并且由于仿函数是第三个缺省模板参数,所以我们需要先传递第二个模板参数–适配容器

在这里插入图片描述

在这里插入图片描述

void priority_queue_test()
{
	// 大堆
	priority_queue<int> pq1;
	pq1.push(1);
	pq1.push(2);
	pq1.push(3);
	pq1.push(4);
	cout << pq1.size() << endl;
	while (!pq1.empty())
	{
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;

	// 小堆
	priority_queue<int, vector<int>, greater<int>> pq2;
	pq2.push(10);
	pq2.push(20);
	pq2.push(30);
	pq2.push(40);
	while (!pq2.empty())
	{
		cout << pq2.top() << " ";
		pq2.pop();
	}
	cout << endl;
}

在这里插入图片描述

3.priority_queue 相关OJ题

leetcode 215. 数组中的第K个最大元素

题目描述

给定整数数组 nums 和整数 k,请返回数组中第 k个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例

输入: [3,2,1,5,6,4], k = 2
输出: 5

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

思路1

我们的第一反应就是对数组进行排序,然后返回nums[nums.size()-k]即可,但是即使是快速排序的时间复杂度也为O(N*lonN),复杂度过高,不太符合题意

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end());
        return nums[nums.size()-k];
    }
};

思路2

我们可以建立一个N个数的大堆,然后pop K次,此时堆顶的元素就是第K大的数字,该方法的时间复杂度:向下调整建堆的时间复杂度为O(N),pop 数据之后向下调整的时间复杂度为O(K*logN),所以总的时间复杂度为O(N+k*logN),对于该方法,当N很大,K很小的时候,时间复杂度过高

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // 建立N个数的大堆
        priority_queue<int> pq(nums.begin(),nums.end());
        // pop K次
        while(--k)
        {
            pq.pop();
        }
        return pq.top();
    }
};

思路3

建立K个数的小堆,剩余N-K个数一次与堆顶的元素进行比较,如果大于堆顶的元素pop堆顶的元素,然后将该数据push入堆,最后堆顶的元素就是第K大的数据,建堆的时间复杂度为O(K),push再向上调整的时间复杂度为O((N-K)*logK),所以总的时间复杂度为O(K+(N-K)*logK),当N很大,K很小的时候,复杂度依然不是很高,因为堆的大小固定为K

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // 建K个数的小堆,比堆顶的元素大就入堆
        priority_queue<int,vector<int>,greater<int>> pq(nums.begin(),nums.begin()+k);
        for(size_t i=k;i<nums.size();++i)
        {
            // 比堆顶的数据大就入堆
            if(nums[i]>pq.top())
            {
                pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }
};

二、仿函数

1.什么是仿函数

仿函数也叫函数对象,仿函数是一个类,且类里必须重载函数调用运算符(),即operator(),这样就使得这样类的对象就行函数一样去使用,我们称其为仿函数或者函数对象,我们以比较大小为例,如下:

// 仿函数/函数对象
namespace hdp
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x < y;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x > y;
		}
	};
}

void functor_test()
{
	hdp::less<int> lessFunc;
	hdp::greater<int> greaterFunc;
	// lessFunc.operator()(1, 2);
	cout << lessFunc(1, 2) << endl;
	// greaterFunc.operator()(1, 2);
	cout << greaterFunc(1, 2) << endl;
}

在这里插入图片描述

我们可以看到,less和greater类重载了()运算符之后,类对象就可以像函数一样去使用,因此他们被称为仿函数

2.仿函数的运用和作用

我们以冒泡排序为例来说明仿函数的作用,我们知道,排序分为升序和降序,在C语言阶段,我们只能通过改变交换判断的条件来改变升降序方式,但是这种方式及其麻烦,需要写两份只有一个地方不同的代码,造成了代码的冗余,还有一种方式就是传递一个函数指针,根据比较函数来决定排序的方式(升序还是降序),所以我们在排序函数的最后一个参数传递一个函数指针:

template<class T>
bool compare_up(const T& x, const T& y)
{
	return x > y;
}
template<class T>
bool compare_down(const T& x, const T& y)
{
	return x < y;
}

//冒泡排序

template<class T>
void BubbleSort(T* a, int n, bool(*cmp)(const T&,const T&))
{
	for (size_t i = 0; i < n; ++i)
	{
		int exchange = 0;
		for (size_t j = 0; j < n - i - 1; ++j)
		{
			if (cmp(a[j], a[j + 1]))
			{
				swap(a[j], a[j + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}

void BubbleSort()
{
	int a[] = { 1,3,4,3,2,5,7,2,6,3 };
	// 升序
	BubbleSort(a, sizeof(a) / sizeof(int), compare_up);
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
	// 降序
	BubbleSort(a, sizeof(a) / sizeof(int), compare_down);
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;

}

在这里插入图片描述

在C++中,我们不需要再使用函数指针来解决排序的升降序的问题,而是使用仿函数:

// 仿函数/函数对象
namespace hdp
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x < y;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x > y;
		}
	};
}

template<class T,class Compare>
// 仿函数一般比较小,所以可以不用传引用
//void BubbleSort(T* a, int n, const Compare& com)
void BubbleSort(T* a, int n, const Compare com)
{
	for (size_t i = 0; i < n; ++i)
	{
		int exchange = 0;
		for (size_t j = 0; j < n - i - 1; ++j)
		{
			if (com(a[j], a[j + 1]))
			{
				swap(a[j], a[j + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}

void BubbleSort_test()
{
	int a[] = { 3,2,6,4,2,5,6,4,7,3,2,1 };
	BubbleSort(a, sizeof(a) / sizeof(int), hdp::less<int>());
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
	BubbleSort(a, sizeof(a) / sizeof(int), hdp::greater<int>());
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

三、priority_queue 的模拟实现

priority_queue.h

#pragma once

namespace hdp
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x < y;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x > y;
		}
	};

	template<class T,class Container=vector<T>,class Compare=less<T>>
	
	class priority_queue
	{
	public:

		// 无参构造
		priority_queue()
		{}

		// 迭代器区间构造
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first,last)
		{
			// 向下调整建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i)
			{
				adjust_down(i);
			}
		}

		// 向上调整
		void adjust_up(size_t child)
		{
			Compare com;
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		// 尾插数据
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

		// 向下调整
		void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				// if (child+1 < _con.size() && _con[child] < _con[child+1])
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		// 删除堆顶元素
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		// 获取堆顶元素
		const T& top()  const
		{
			return _con[0];
		}

		// 判空
		bool empty()  const
		{
			return _con.size() == 0;
		}

		// 获取元素个数
		size_t size()  const
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
using namespace std;
#include <functional>
#include <vector>
#include "priorityqueue.h"

void priority_queue_test()
{
	std::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(5);
	v.push_back(4);
	// 大堆
	hdp::priority_queue<int> pq1(v.begin(), v.end());
	while (!pq1.empty())
	{
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;

	// 小堆
	hdp::priority_queue <int, vector<int>, greater<int>> pq2;
	pq2.push(10);
	pq2.push(20);
	pq2.push(30);
	pq2.push(40);

	cout << pq2.size() << endl;
	while (!pq2.empty())
	{
		cout << pq2.top() << " ";
		pq2.pop();
	}
	cout << endl;
}
int main()
{
	priority_queue_test();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/421601.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elastic(ELK) Stack 架构师成长路径

Elastic Stack&#xff08;ELK Stack&#xff09;是一个开源的日志分析平台&#xff0c;由 Elasticsearch、Logstash 和 Kibana 三个组件组成&#xff0c;主要用于数据搜索、分析和可视化。要成为一名 ELK Stack 架构师&#xff0c;需要遵循一定的成长路径&#xff0c;以便逐步…

详解HiveSQL执行计划

一、前言 Hive SQL的执行计划描述SQL实际执行的整体轮廓&#xff0c;通过执行计划能了解SQL程序在转换成相应计算引擎的执行逻辑&#xff0c;掌握了执行逻辑也就能更好地把握程序出现的瓶颈点&#xff0c;从而能够实现更有针对性的优化。此外还能帮助开发者识别看似等价的SQL其…

【计算机组成原理】计算机组成原理(三)

计算机组成原理&#xff08;三) 奇偶校验码&#xff1a; 校验原理&#xff1a; 2个比特位可以映射出4种合法的情况 2的2次方 3个比特位可以映射出8种不同的情况&#xff0c;其中4种为合法情况&#xff0c;另外4种为非法情况 上图的每个编码都是一个码字 在同一组码字内&am…

【DES详解】(一)处理input block(64 bits)

一、DES 加密算法总览 0-1、初识置换 IP&#xff08;Initial Permutation&#xff09; 输入&#xff1a;明文&#xff08;64 bits&#xff09; 过程&#xff1a;初识置换 输出&#xff1a;处理后的明文permuted input&#xff08;64 bits&#xff09; 首先&#xff0c;对需要解…

手写一个IO泄露监测框架

作者&#xff1a;长安皈故里 大家好&#xff0c;最近由于项目原因&#xff0c;对IO资源泄漏的监测进行了一番调研深入了解&#xff0c;发现IO泄漏监测框架实现成本比较低&#xff0c;效果很显著&#xff1b;同时由于IO监测涉及到反射&#xff0c;还了解到了通过一种巧妙的方式实…

AEC-Q认证介绍及所有最新工程文件下载

AEC-Q认证介绍及所有最新文件&#xff08;英文版&#xff09;下载 注意&#xff1a; 更多交流及资料请加V&#xff1a;john-130 AEC-Q认证介绍 1&#xff0c;AEC-Q认证总体情况介绍 &#xff08;​1&#xff09;AEC&#xff08;Automotive Electronics Council&#xff09;…

图像分类:Pytorch图像分类之-- MobileNet系列模型

文章目录前言MobileNetV1模型介绍DW&#xff08;Depthwise Convolution&#xff09;卷积PW &#xff08;Pointwise Convolution&#xff09;卷积深度可分离卷积&#xff08;DWPW&#xff09;ReLU6激活函数的介绍MobileNet V1网络结构MobileNet V1程序MobileNetV2模型介绍Invert…

链接、包管理工具、polyrepo、monorepo以及Lerna 工具的使用

nodejs 链接、包管理工具、多包管理以及Lerna 工具的使用jcLee95&#xff1a;https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/129903902 目 录1. 概述…

bjdctf_2020_babyrop2-fmt-leak canary

1,三连 分析:开了canary&#xff0c;先想办法获取canary值。 2&#xff0c;IDA静态分析&#xff0c;查看可以泄露canary的地方&#xff0c;否则只能爆破了 发现可以格式化字符串函数泄露的地方&#xff1a; 栈帧结构&#xff1a; 高地址 -------------- gift_ret栈帧 ------…

【算法宇宙——在故事中学算法】背包dp之01背包问题

唯手熟尔方成艺&#xff0c;唯读书能致卓越。勤学苦练方可成&#xff0c;路漫漫其修远兮&#xff01; 文章目录前言正文故事总结前言 尽管计算机是门严谨的学科&#xff0c;但正因为严谨&#xff0c;所以要有趣味才能看得下去。在笔者的前几篇算法类文章中&#xff0c;都采用了…

智慧公厕系统的应用示例

近几年&#xff0c;在一些高速服务区或者一些城市的公共厕所当中&#xff0c;总会看见一些富有科技感的硬件&#xff0c;比如厕位有无人指示灯、厕所除臭杀菌机、智能取纸机、智能洗手台镜面广告机等。现在在衡量城市发展的过程中&#xff0c;总会以城市的建设&#xff0c;城市…

Weblogic远程代码执行漏洞 CVE-2023-21839

漏洞简介 WebLogic Core远程代码执行漏洞&#xff08;CVE-2023-21839&#xff09;&#xff0c;该漏洞允许未经身份验证的远程攻击者通过T3/IIOP协议进行 JNDI lookup 操作&#xff0c;破坏易受攻击的WebLogic服务器&#xff0c;成功利用此漏洞可能导致Oracle WebLogic服务器被接…

MySQL可重复读事务隔离具体是怎么实现的

事务的启动会有的操作 事务的隔离等级有四种&#xff0c;现在说默认的可重复读&#xff0c;可重复读就是一个事务执行过程中看到的数据&#xff0c;总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下&#xff0c;未提交变更对其他事务也是不可见的。 可重复…

Java阶段一Day22

Java阶段一Day22 文章目录Java阶段一Day22线程安全synchronized教师总结新单词多线程多线程并发安全问题概念例synchronized关键字同步方法同步块在静态方法上使用synchronized互斥锁总结重点:多线程并发安全问题聊天室(续)实现服务端发送消息给客户端服务端转发消息给所有客户…

内网穿透实现在外远程连接RabbitMQ服务

文章目录前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址转载自远控源码文章&#xff1a;无公网IP&#xff0c;…

Linux Systemd type=simple和type=forking的区别

Typeforking 使用Typeforking时&#xff0c;要求ExecStart启动的命令自身就是以daemon模式运行的。 而以daemon模式运行的进程都有一个特性&#xff1a;总是会有一个瞬间退出的中间父进程&#xff0c;例如&#xff0c;nginx命令默认以daemon模式运行&#xff0c;所以可直接将其…

Nodejs vm/vm2沙箱逃逸

文章目录什么是沙箱以及VM&#xff1f;vm模块nodejs作用域vm沙箱vm沙箱逃逸vm2例题分析&#xff1a;&#xff08;待补充&#xff09;[HFCTF2020]JustEscape[HZNUCTF 2023 final]eznode參考文章:什么是沙箱以及VM&#xff1f; 什么是沙箱&#xff1a; 沙箱就是能够像一个集装箱…

Ansys Speos | 联合 optiSLang 背光板设计优化方案

在这个例子中&#xff0c;讲述如何建模一个典型的背光单元及其与亮度和均匀性有关的照度分布。其中一个关键特点是使用了Speos 3D Texture功能&#xff0c;这是最初开发的用于背光单元产品&#xff0c;并可用于设计导光板&#xff0c;亮度增强膜(BEF)和由数千/数百万组成的背光…

《程序员面试金典(第6版)》面试题 10.03. 搜索旋转数组(二分法,分钟思想,入门题目)

题目描述 搜索旋转数组。给定一个排序后的数组&#xff0c;包含n个整数&#xff0c;但这个数组已被旋转过很多次了&#xff0c;次数不详。请编写代码找出数组中的某个元素&#xff0c;假设数组元素原先是按升序排列的。若有多个相同元素&#xff0c;返回索引值最小的一个。 示例…

C学习笔记2

1、二进制由 0 和 1 两个数字组成&#xff0c;使用时必须以0b或0B&#xff08;不区分大小写&#xff09;开头 2、符号位进制形式进制数据 &#xff08;进制形式决定后面的数据是哪种进制&#xff09; 3、合法的二进制 int a 0b101; // 0b是二进制的进制形式 101是进制…