【C++】仿函数 -- priority_queue

news2024/11/15 11:39:17

文章目录

  • 一、priority_queue 的介绍和使用
    • 1、priority_queue 的介绍
    • 2、priority_queue 的使用
    • 3、priority_queue 相关 OJ 题
  • 二、仿函数
    • 1、什么是仿函数
    • 2、仿函数的作用
  • 三、priority_queue 的模拟实现

一、priority_queue 的介绍和使用

1、priority_queue 的介绍

priority_queue (优先级队列) 是一种容器适配器,它与 queue 共用一个头文件,其底层结构是一个堆,并且默认情况下是一个大根堆,所以它的第一个元素总是它所包含的元素中最大的,并且为了不破坏堆结构,它也不支持迭代器:image-20230305182654456

同时,由于堆需要进行下标计算,所以 priority_queue 使用 vector 作为它的默认适配容器 (支持随机访问):image-20230305183003478

但是,priority_queue 比 queue 和 stack 多了一个模板参数 – 仿函数;关于仿函数的具体细节,我们将在后文介绍。

class Compare = less<typename Container::value_type>

2、priority_queue 的使用

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

priority_queue 的使用文档

-函数声明接口说明-
priority_queue()构造一个空的优先级队列
priority_queue(first, last)迭代器区间构造优先级队列
empty( )检测优先级队列是否为空
top( )返回优先级队列中最大(最小元素),即堆顶元素
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元素
size()返回优先级队列中的元素个数

注意事项

priority_queue 默认使用的仿函数是 less,所以默认建成的堆是大堆;如果我们想要建小堆,则需要指定仿函数为 greater,该仿函数包含在头文件 functional 中,并且由于仿函数是第三个缺省模板参数,所以如果要传递它必须先传递第二个模板参数即适配容器。image-20230305184817137

image-20230305184850491

void test_priority_queue() {
	priority_queue<int> pq;  //默认建大堆,仿函数为less
	pq.push(5);
	pq.push(2);
	pq.push(4);
	pq.push(1);
	pq.push(3);
	while (!pq.empty()) {
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;

	priority_queue<int, vector<int>, greater<int>> pq1;  //建小堆,仿函数为greater,需要显式指定
	pq1.push(5);
	pq1.push(2);
	pq1.push(4);
	pq1.push(1);
	pq1.push(3);
	while (!pq1.empty()) {
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;
}

image-20230305205859184

3、priority_queue 相关 OJ 题

215. 数组中的第K个最大元素 - 力扣(LeetCode)

给定整数数组 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

思路1非常简单,直接对 nums 数组使用 sort 进行排序,然后返回 nums[nums.size() - k] 即可,但是排序的时间复杂度为 O(N*logN),太高。

思路2

思路2就是建N个数的大堆,然后 pop k-1 次,此时堆顶元素就是第 K 大的数,向下调整建堆时间复杂度为 O(N),pop 再向下调整的时间复杂度为 K*logN,所以总的时间复杂度为 O(N + K*logN);此方法可行,但是当 N 很大,K 很小时,空间复杂度过高。

思路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) {
        priority_queue<int, vector<int>, greater<int>> pq;  //建小堆
        
        for(int i = 0; i < k; i++) {  //建K个数的小堆
            pq.push(nums[i]);
        }

        for(int i = k; i < nums.size(); i++) {  //剩余n-k个数与堆顶比较,大于就删除堆顶元素入堆
            if(nums[i] > pq.top()) {
                pq.pop();
                pq.push(nums[i]);
            }
        }

        return pq.top();  //堆顶元素为第K大的数
    }
};

二、仿函数

1、什么是仿函数

仿函数也叫函数对象,仿函数是一个类,但是该类必须重载函数调用运算符 (),即 operator()(参数);由于这样的类的对象可以像函数一样去使用,所以我们将其称为仿函数/函数对象,如下:

namespace thj {
	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) {
			return x > y;
		}
	};
}

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

	thj::greater<int> greaterFunc;
	cout << greaterFunc(1, 2) << endl;  //greaterFunc.operator(1,2)
}

image-20230305192812645

可以看到,因为 less 类和 greater 类重载了 () 操作符,所以类对象可以像函数一样去使用,因此它们被称为仿函数。

2、仿函数的作用

我们以最简单的冒泡排序为例来说明仿函数的作用,我们知道,排序分为排升序和排降序,那么在没有仿函数的时候,即C语言阶段,我们是如何来解决这个问题的呢 – 答案是函数指针;

将排序函数的最后一个参数定义为函数指针,然后通过用户给排序函数传递不同的比较函数来决定升序还是降序:

template<class T>
bool cmpUp(const T& e1, const T& e2) {  //排升序
	return e1 > e2;
}

template<class T>
bool cmpDown(const T& e1, const T& e2) {  //排降序
	return e1 < e2;
}

// 冒泡排序
template<class T>
void BubbleSort(T* a, int n, bool (*cmp)(const T&, const T&))
{
	T i = 0;
	T j = 0;
	for (i = 0; i < n - 1; i++)
	{
		int exchange = 0;
		for (j = 0; j < n - i - 1; j++)
		{
			if (cmp(a[j], a[j + 1]))  //函数调用
			{
				std::swap(a[j], a[j + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0) break;
	}
}

void bubbleSort_test() {
	int a[] = { 1, 3, 2, 2, 4, 8, 5, 1, 9 };
	BubbleSort(a, sizeof(a) / sizeof(int), cmpUp);

	for (auto e : a) {
		cout << e << " ";
	}
	cout << endl;

	int b[] = { 1, 3, 2, 2, 4, 8, 5, 1, 9 };
	BubbleSort(b, sizeof(b) / sizeof(int), cmpDown);

	for (auto e : b) {
		cout << e << " ";
	}
	cout << endl;
}

image-20230305195107630

在 C++ 中,我们不再使用函数指针解决升序降序的问题,转而使用更加方便的仿函数。(注:关于仿函数的更多细节以及仿函数和函数指针各自的优缺我们将在以后慢慢学习,现在仅仅是浅浅入门一下仿函数)

// 冒泡排序
template<class T, class Compare>
void BubbleSort(T* a, int n, Compare cmp)  //使用仿函数
{
	T i = 0;
	T j = 0;
	for (i = 0; i < n - 1; i++)
	{
		int exchange = 0;
		for (j = 0; j < n - i - 1; j++)
		{
			if (cmp(a[j], a[j + 1]))  //函数调用 cmp.operator()(a[j], a[j+1])
			{
				std::swap(a[j], a[j + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0) break;
	}
}

//复用前面的 less 和 greater 类
void bubbleSort_test1() {
	int a[] = { 1, 3, 2, 2, 4, 8, 5, 1, 9 };
	BubbleSort(a, sizeof(a) / sizeof(int), thj::less<int>());  //排降序,匿名对象

	for (auto e : a) {
		cout << e << " ";
	}
	cout << endl;

	int b[] = { 1, 3, 2, 2, 4, 8, 5, 1, 9 };
	BubbleSort(b, sizeof(b) / sizeof(int), thj::greater<int>());  //排升序

	for (auto e : b) {
		cout << e << " ";
	}
	cout << endl;
}

image-20230305200741180


三、priority_queue 的模拟实现

其实 priority_queue 的模拟实现我们已经做过了 – priority_queue 的底层是堆,而关于堆的C语言实现包括堆的应用 (堆排序与TopK问题) 我们在数据结构初阶都已经做过了,这里只是用类和对象,再加上容器适配器和仿函数将其封装起来而已,所以下面我就直接给出实现代码而不进行细节分析了,如果有疑问的同学可以回头看看我之前的博客。

【数据结构】二叉树 – 堆

【数据结构】堆的应用 – 堆排序和TopK问题

priority_queue.h:

#pragma once

namespace thj {
	//仿函数
	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) {
			return x > y;
		}
	};

	//priority_queue
	template<class T, class Container = vector<T>, class Compare = less<T>>  //默认建大堆,传less
	class priority_queue {
	public:
		priority_queue() {}  //默认构造

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)  //迭代器区间构造
			: _con(first, last)
		{
			//向下调整建堆 O(N)  从最后一个非叶子节点开始
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--) {
				adjustDown(i);
			}
		}

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

		size_t size() const {  //元素个数
			return _con.size();
		}

		const T& top() const {  //取堆顶数据
			return _con[0];
		}

		void push(const T& x) {  //插入数据
			_con.push_back(x);

			adjustUp(_con.size() - 1);  //从最后一个节点开始向上调整
		}

		void pop() {  //删除堆顶数据
			std::swap(_con[0], _con[_con.size() - 1]);  //为了不破坏堆结构,先将第一个元素和最后一个交换
			_con.pop_back();

			adjustDown(0);  //从堆顶向下调整
		}

		void adjustDown(size_t parent) {  //堆的向下调整
			Compare cmp;  //仿函数

			size_t child = parent * 2 + 1;
			while (child < _con.size()) {  //特别注意边界问题
				if (child + 1 < _con.size() && cmp(_con[child], _con[child + 1])) {  //仿函数
					child = child + 1;  //如果是less,则建大堆,找大孩子,结果为真,右孩子大
				}

				if (cmp(_con[parent], _con[child])) {
					std::swap(_con[parent], _con[child]);

					//迭代
					parent = child;
					child = parent * 2 + 1;
				}
				else break;  //满足堆结构,跳出循环
			}
		}

		void adjustUp(size_t child) {  //堆的向上调整
			Compare cmp;

			size_t parent = (child - 1) / 2;
			while (child > 0) {  //一直向上调整到根节点
				if (cmp(_con[parent], _con[child])) {
					std::swap(_con[parent], _con[child]);

					child = parent;
					parent = (child - 1) / 2;
				}
				else break;
			}
		}

	private:
		Container _con;
	};
}

test.cpp:

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

void my_priority_queue_test() {
	thj::priority_queue<int> pq;  //默认建大堆,仿函数为less
	pq.push(5);
	pq.push(2);
	pq.push(4);
	pq.push(1);
	pq.push(3);
	while (!pq.empty()) {
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;

	thj::priority_queue<int, vector<int>, thj::greater<int>> pq1;  //建小堆,仿函数为greater,需要显式指定
	pq1.push(5);
	pq1.push(2);
	pq1.push(4);
	pq1.push(1);
	pq1.push(3);
	while (!pq1.empty()) {
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;

	std::vector<int> v;
	v.push_back(1);
	v.push_back(8);
	v.push_back(2);
	v.push_back(3);
	v.push_back(6);
	thj::priority_queue<int> pq2(v.begin(), v.end());  //迭代器区间构造
	while (!pq2.empty()) {
		cout << pq2.top() << " ";
		pq2.pop();
	}
	cout << endl;
}

image-20230305211718458


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

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

相关文章

vue3 transition动画

Vue 提供了 transition 的封装组件&#xff0c;通过它可以给任何元素和组件添加进入/离开过渡动画 一、vue怎么实现单组件/元素的过渡动画 Vue 在插入、更新或者移除 DOM 时&#xff0c;提供多种不同方式的应用过渡效果。------vue官网 vue的transition组件通过观察元素的DOM状…

全网最全整理,自动化测试10种场景处理(超详细)解决方案都在这......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 自动化工作流程 自动…

三种让DIV标签中的P标签水平和垂直都居中方法

效果如下图 红色1块是span&#xff0c;属于行内元素。 绿色2块和蓝色4块是p&#xff0c;属于块级元素。 黄色3块h3&#xff0c;属于块 都是块级元素方法是可以通用的 这里两个类别元素一起来展示主要是为了区别 1行内块元素水平居中垂直居中 行内元素和行内块元素水平居中…

docker项目自动化部署脚本(认真排版、工作积累)

要解决什么问题&#xff1f; 把日益复杂化、工程化的开发环境&#xff0c;以及生产环境&#xff0c;变得简单&#xff0c;自动化部署。 达到什么效果&#xff1f; 环境处处一致&#xff0c;并且自动化部署&#xff0c;提升生产力&#xff0c;又快又好。 当您更换电脑、更换…

Vue2.0开发之——购物车案例-Goods组件封装-修改商品的勾选状态(49)

一 概述 如何修改商品的勾选状态自定义state-change事件修改对应商品的勾选状态 二 如何修改商品的勾选状态 2.1 App.vue中data每个Item中goods_state的变化伴随商品勾选状态变化 2.2 Goods.vue中复选框的值是props属性 <inputtype"checkbox"class"custom…

LeeCode:回文子串个数(动态规划)

文章目录一、题目二、算法思路三、代码实现四、复杂度分析一、题目 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字符组成&#xff0c;也会被视作不同的子串。 回文字符串 是正着读…

二值图像骨架线提取

二值图像骨架线提取HilditchThin算法Rosenfeld算法OpenCV_Contrib中的算法示例其他细化算法查表法HilditchThin的另一种算法参考二值图像骨架线提取算法&#xff1a;HilditchThin算法、Rosenfeld算法、OpenCV_Contrib中的算法 HilditchThin算法 1、使用的8邻域标记为&#xff…

Java+ElasticSearch+Pytorch实现以图搜图

以图搜图&#xff0c;涉及两大功能&#xff1a;1、提取图像特征向量。2、相似向量检索。第一个功能我通过编写pytorch模型并在java端借助djl调用实现&#xff0c;第二个功能通过elasticsearch7.6.2的dense_vector、cosineSimilarity实现。一、准备模型创建demo.py&#xff0c;输…

cuda2D FDTD——share

https://www.coder.work/article/30133 shared memory只能在block内共享&#xff0c;之间无法互相通信 对于2D TM波动方程计算&#xff0c;我们可以使用以下策略来处理共享内存的边界&#xff1a; 将全局内存中的数据复制到共享内存中时&#xff0c;除了将每个线程需要的数据…

Python爬虫实践:优志愿 院校列表

https://www.youzy.cn/tzy/search/colleges/collegeList获取目标网址等信息打开开发人员工具&#xff08;F12&#xff09;&#xff0c;拿到调用接口的地址&#xff0c;以及接口请求参数等信息&#xff0c;如下curl https://uwf7de983aad7a717eb.youzy.cn/youzy.dms.basiclib.ap…

假如你知道这样的MySQL性能优化

1. 为查询缓存优化你的查询 大多数的 MySQL 服务器都开启了查询缓存。这是提高性最有效的方法之 一&#xff0c;而且这是被 MySQL 的数据库引擎处理的。当有很多相同的查询被执行了多次的时候&#xff0c;这些查询结果会被放到一个缓存中&#xff0c;这样&#xff0c;后续的相同…

Kogito -- 入门详解

Kogito -- 入门详解1. Introduction1.1 Version1.2 Introduction2.Environment Install2.1 JDK Install2.2 Maven Install&#xff08;3.8.6&#xff09;2.3 Idea2.4 VSCode3. Run Code3.1 Dependency3.2 Run3.3 Swagger4.Awakening4.1 Big Data -- Postgres5.Awakening5.1 Big…

如何做一个高级的文本编辑器 textarea,拥有快捷键操作

如何做一个高级的文本编辑器 textarea&#xff0c;拥有快捷键操作 最近想做一个高级点的 textarea &#xff0c;支持 JetBrains 系列软件的快捷键&#xff0c;比如&#xff1a; CTRL D 复制当前行。Tab 在前面插入 4 个空格。Shift Tab 删除行前的空格&#xff0c;多于4个&a…

google独立站和与企业官网的区别是什么?

google独立站和与企业官网的区别是什么&#xff1f; 答案是&#xff1a;独立站通过谷歌SEO优化可以更好的获取自然排名的流量。 随着互联网的不断发展&#xff0c;企业越来越重视自身网站的建设和优化&#xff0c;而在企业网站建设中&#xff0c;很多人会犯一个常见的错误&am…

模块、包和异常

目录1.模块import 导入from...import 导入2. 模块的搜索顺序3. __name__属性的使用4. 包包的使用步骤5. 发布模块6. 安装模块7. 卸载模块8. pip 安装第三方模块9. 异常处理异常捕获异常的传递抛出 raise 异常1.模块 模块是 Python 程序架构的一个核心概念 每一个以扩展名 py …

LPDDR4x 的 学习总结(4) - SDRAM chip的组织结构

上节总结cell的结构和基本操作 本节基于cell组合起来的DRAM组织结构 DDR Device 的组织结构 Cells 以特定的方式组成 Column/Row/Bank/Chip/Rank/DIMM/Channel等多层级组织结构如下图&#xff1a; 图1 - DRAM的组织结构 图2 - DRAM容量的组织结构图 Channel: 同1个DDR控制器 …

GIT基础常用命令-1 GIT基础篇

git基础常用命令-1 GIT基础篇1.git简介及配置1.1 git简介1.2 git配置config1.2.1 查看配置git config1.2.2 配置设置1.2.3 获取帮助git help2 GIT基础常用命令2.1 获取镜像仓库2.1.1 git init2.1.2 git clone2.2 本地仓库常用命令2.2.1 git status2.2.2 git add2.2.3 git diff2…

seata1.5.2使用从零快速上手(提供代码与安装包)

1.软件准备&#xff1a; 1.1 seata1.5.2 官网下载&#xff1a;地址:http://seata.io/zh-cn/ server源码:https://github.com/seata/seata 百度云下载&#xff08;建议&#xff09;: 百度下载 链接&#xff1a;https://pan.baidu.com/s/1eilbSI0YdmupHYI7FroTsw 提取码&…

【编程基础之Python】10、Python中的运算符

【编程基础之Python】10、Python中的运算符Python中的运算符算术运算符赋值运算符比较运算符逻辑运算符位运算符成员运算符身份运算符运算符优先级运算符总结Python中的运算符 Python是一门非常流行的编程语言&#xff0c;它支持各种运算符来执行各种操作。这篇文章将详细介绍…

构造有向无环图(拓扑排序)

蓝桥杯集训每日一题 acwing3696 给定一个由 n 个点和 m 条边构成的图。 不保证给定的图是连通的。 图中的一部分边的方向已经确定&#xff0c;你不能改变它们的方向。 剩下的边还未确定方向&#xff0c;你需要为每一条还未确定方向的边指定方向。 你需要保证在确定所有边的…