【C++】优先级队列priority_queue仿函数

news2024/12/27 10:04:29

这里先简单介绍一下优先级队列priority_queue:优先队列是一种容器适配器,默认的情况下,如果没有为特定的priority_queue类实例化指容器类,则使用vector (deque 也是可以的),需要支持随机访问迭代器,以便始终在内部保持堆结构

文章目录

    • 一、使用
    • 二、仿函数
    • 三、模拟实现
      • 1、基础接口
      • 2、push与向上调整
      • 3、pop与向下调整
      • 4、构造函数
      • 5、总体代码

一、使用

在有了前面容器使用的基础之下,我们对于优先级队列priority_queue的使用成本不是很大,值得注意的是头文件为

普通的队列是先进先出,优先级队列默认是优先级高的先出

image-20230107185047742

Container:优先级队列默认使用vector作为其底层存储数据的容器,支持[]的使用,支持随机访问,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

Compare:注意:默认情况下priority_queue是大堆,仿函数为less。

构造函数

image-20230107195711405

接口

查看文档的接口

image-20230107190120372

常用接口

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

直接进入代码环节:

1.默认情况下,priority_queue是大堆

image-20230107192719480

2.想让priority_queue是小堆:greater,头文件为functional

image-20230107194113819

到了这里,我们发现priority_queue的使用并不难,下面,我们通过一道题目来看看priority_queue的妙处把:

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

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

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

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

示例 1:

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

示例 2:

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

直接排序是O(NlogN),这个地方既可以建大堆也可以建小堆:建n个数的大堆,建堆是O(n),然后在pop K次,即O(N+k*logN),缺陷在于N太大。建K个数的小堆,剩下的数进一个数据与堆顶做比较,大的进堆,最终第K大的就是堆顶,建K个数的小堆为O(K),故时间复杂度为O(K+(N-K)*logK),当N远大于K是建议采用第二种。

第一种:建n个数的大堆

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

第二种:建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();
    }
};

我们可以发现在有了优先级队列之后我们对于这道题的解法大大简便了。不用像之前一样去自己手动实现一个堆了。


二、仿函数

仿函数/函数对象也是类,是一个类对象。仿函数要重载operator(),我们直接自己来通过代码来看一看仿函数:

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

}
int main()
{
	hwc::less<int> lessFunc;
	lessFunc(1, 2);
	return 0;
}
//lessFunc是一个对象,仿函数对象,可以像函数一样使用

仿函数的作用在于:在C语言中我们通过传入函数指针解决升序降序问题,虽然C++兼容了C,但是C++并没有继续利用函数指针,而是通过仿函数来控制升序和降序,我们直接以之前写过的排序为例子,通过利用仿函数来实现升序和降序

template<class T,class Compare>
void BubbleSort(T* a, int n, Compare com)
{
	for (int j = 0; j < n; j++)
	{
		int exchange = 0;
		for (int i = 1; i < n - j; i++)
		{
			//if (a[i]<a[i-1])
			if (com(a[i], a[i-1]))
			{
				swap(a[i - 1], a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}
int main()
{
	hwc::less<int> lessFunc;
	hwc::greater<int> greaterFunc;
	lessFunc(1, 2);
	int a[] = { 2,3,4,5,6,1,2,8 };
    BubbleSort(a, sizeof(a) / sizeof(int),lessFunc);
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
	BubbleSort(a, sizeof(a) / sizeof(int), greaterFunc);
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

image-20230108115155201

传值传参问题这里是直接传值传参Compare com,当然也可以传引用传参const Compare& com,不过要记得加上const进行修饰,另外一般的仿函数比较小,问题不是很大

自定义类型
这里的比较大小是比较常见的,如果是自定义类型:

比如日期类,那该如何去进行大小的比较?一种是重载大小的比较运算符,使之支持日期类的大小比较。另一种是可以自己定义仿函数专门去进行日期类的大小比较

1.重载运算符

比较大小需要我们自己去重载>,<,<<,这些在日期类的时候我们就详细的说过了,直接来看代码:

#include <iostream>
#include <queue>
#include <functional>
#include <algorithm>
#include <vector>


using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestPriorityQueue()
{
	// 大堆,需要用户在自定义类型中提供<的重载
	priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	cout << q1.top() << endl;
	// 如果要创建小堆,需要用户提供>的重载
	priority_queue<Date, vector<Date>, greater<Date>> q2;
	q2.push(Date(2018, 10, 29));
	q2.push(Date(2018, 10, 28));
	q2.push(Date(2018, 10, 30));
	cout << q2.top() << endl;
}
int main()
{
	TestPriorityQueue();
}

2.定义仿函数

如果传入的是Date*指针,此时上面的运算符重载已经不符合我们的需求了,比较的是地址的大小,但是我们要比较的是日期的大小,所以我们通过自己定义仿函数的方式来实现我们的需求:

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

struct PDateLess
{
	bool operator()(const Date* d1, const Date* d2)
	{
		return *d1 < *d2;
	}
};

struct PDateGreater
{
	bool operator()(const Date* d1, const Date* d2)
	{
		return *d1>*d2;
	}
};
void TestPriorityQueue()
{
    //大堆
	priority_queue <Date*, vector<Date*>, PDateLess> q3;
	q3.push(new Date(2018, 10, 29));
	q3.push(new Date(2018, 10, 28));
	q3.push(new Date(2018, 10, 30));
	cout << *q3.top() << endl;
	//小堆
	priority_queue<Date*, vector<Date*>, PDateGreater> q4;
	q4.push(new Date(2018, 10, 29));
	q4.push(new Date(2018, 10, 28));
	q4.push(new Date(2018, 10, 30));
	cout << *q4.top() << endl;
}

最终顺利完成:

image-20230108130705493

有了上面的知识内容之后,下面我们进入的是优先级队列priority_queue的模拟实现!👇


三、模拟实现

1、基础接口

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

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

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

2、push与向上调整

优先级队列就是堆结构,插入一个数之后,我们还需要去进行向上调整

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

向上调整算法:

image-20230108164300931

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[child], _con[parent]);
			child = parent;
			parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

3、pop与向下调整

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

向下调整:

image-20230108164750324

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 (_con[parent]<_con[child])
        if (com(_con[parent], _con[child]))
        {
            swap(_con[child], _con[parent]);
            parent = child;
            child = parent * 2 + 1;
		}
        else
        {
            break;
		}
	}
}

4、构造函数

这里主要说一下迭代器区间的构造函数:自定义类型会调用自己的迭代器区间构造,所以我们并不需要一个一个push,走个初始化列表即可,同时,数据进去之后我们还要建堆,利用向下调整算法:从倒数第一个非叶子节点,既最后一个节点的父节点开始进行向下调整:

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);
        }
    }

5、总体代码

#pragma once
namespace hwc
{
	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[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		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 (_con[parent]<_con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void push(const T& x)
		{
			_con.push_back(x);

			adjust_up(_con.size() - 1);
		}

		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.empty();
		}

		size_t size() const
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}
#include <iostream>
#include <queue>
#include <functional>
#include <algorithm>
#include <vector>


using namespace std;

#include "priority_queue.h"


int main()
{
	//hwc::priority_queue<int> pq;
	hwc::priority_queue<int,vector<int>,greater<int>> pq;
	pq.push(3);
	pq.push(1);
	pq.push(4);
	pq.push(2);
	pq.push(5);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
	return 0;
}

image-20230108123546186

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

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

相关文章

Springboot配置静态资源

目录 1. springboot默认的静态资源存放路径 2.Springboot添加静态资源映射addResourceHandlers addResourceLocations 3.坑 如果方法一和二同时配置 那么就会遵循方法二 方法一的静态文件将找不到 1. springboot默认的静态资源存放路径 静态资源的存放路径为classpath,也就是…

【卷积码系列4】卷积码的状态转移函数、距离谱和译码性能界分析及matlab仿真

一、卷积码的状态图和转移函数 以一个例子入手,对于如下编码器所示的码率1/3卷积码 根据输入和寄存器状态,可以得到其状态图如下所示 图中虚线表示输入比特为1时的转移,而实线表示输入比特为0时的转移 同样,图中虚线表示输入比特为1时的转移,而实线表示输入比特为0…

Diffusion Models从入门到放弃:必读的10篇经典论文

前言&#xff1a;diffusion models是现在人工智能领域最火的方向之一&#xff0c;并引爆了AIGC方向&#xff0c;一大批创业公司随之诞生。笔者2021年6月开始研究diffusion&#xff0c;见证了扩散模型从无人问津到炙手可热的过程&#xff0c;这些篇经典论文我的专栏里都详细介绍…

从0到1完成一个Vue后台管理项目(五、登录页(表单校验的使用和封装))

往期 从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;二、使用element-ui&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;三、使用SCSS/LESS&#xff0c;安装图标库&#xff09; 从0到1完成一个Vu…

IDEA的使用

1.安装 官网: https://www.jetbrains.com/IDEA 工作界面介绍 2.使用技巧 √设置字体和颜色主题 菜单 file -> settings √字符编码设置 可以自己设置快捷键 常用快捷键&#xff1a;删除当前行, 默认是 ctrl Y 自己配置 ctrl d复制当前行, 自己配置 ctrl alt 向下光…

10、可观测性--系统告警

文章目录告警的作用告警数据来源黑盒白盒日志统计指标链路追踪故障分类告警的作用 在没有告警的时候&#xff0c;我们一般是人工定期地查看相关的指标或者链路数据&#xff0c;再去程序上确认。虽然人工也能监控&#xff0c;但有时还是难以判定是否真的出现了问题&#xff0c;…

【云原生进阶之容器】第二章Controller Manager原理2.6节--Informer controller

6 Informer 的 controller DeltaFIFO 是一个非常重要的组件,真正让他发挥价值的,便是 Informer 的 controller。 虽然 Kubernetes 源码中的确用的是 controller 这个词,但是此 controller 并不是 Deployment Controller 这种资源控制器。而是一个承上启下的事件控制器(从 A…

Java并发编程学习14-任务关闭(下)

任务关闭&#xff08;下&#xff09; 《任务关闭》由于篇幅较多&#xff0c;拆分了两篇来介绍各种任务和服务的关闭机制&#xff0c;以及如何编写任务和服务&#xff0c;使它们能够优雅地处理关闭。 1. 处理非正常的线程终止 我们知道&#xff0c;当单线程的控制台程序由于…

Urban NeRF

本文首发于馆主君晓的博客&#xff0c;文章链接 简要介绍 这是谷歌和多伦多大学合作的一篇发表在CVPR2022上的工作&#xff0c;延续NeRF重建的相关思路。考虑到之前的一些工作要么是在合成数据集上进行的NeRF重建&#xff0c;要么就是用到真实的场景&#xff0c;但是场景很小&a…

JDK1.8和JDK1.7的HashMap源码分析以及线程不安全问题

参考&#xff1a; 教你如何阅读HashMap源码~吊打面试官 - 腾讯云开发者社区-腾讯云 (tencent.com) 有一些面试题 Map - HashSet & HashMap 源码解析 | Java 全栈知识体系 (pdai.tech) HashMap源码&底层数据结构分析 | JavaGuide(Java面试学习指南) hashmap头插法和尾插…

LAB1 VRRP实验

■实验拓扑 ■实验需求 多厂商的网关冗余&#xff08;VRPP&#xff09; 考虑上行/上上行/下行链路的之间的track 生成树配置 VPC能访问R4的loopback口地址&#xff08;8.8.8.8&#xff09; ■实验步骤 ▶思科路由器CISCO-R4 Router(config)#hostname CISCO-R4 CISCO-…

【博客581】为什么MASQUERADE都在POSTROUTING做

为什么MASQUERADE都在POSTROUTING做 MASQUERADE都在POSTROUTING做&#xff0c;为什么不能在output做 1、iptables flow graph&#xff1a; 2、output之后的routing和rerouting&#xff1a; 对于本机 app 发出(outcoming)的流量&#xff0c;netfilter 有2次 routing 过程&…

算法刷题打卡第59天:相交链表

相交链表 难度&#xff1a;简单 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 …

空洞卷积atrous/dilated convolution

1、定义 空洞卷积&#xff08;atrous/dilated convolution&#xff09;又称膨胀卷积是针对图像语义分割问题中下采样会降低图像分辨率、丢失信息而提出的一种卷积思路。空洞卷积向卷积层引入了一个称为“扩张率/膨胀率(dilation rate)”的新参数&#xff0c;该参数定义了卷积核…

Excel 个人财务:如何在 Excel 模板中创建预算

wpcmf “金钱是一种工具。使用得当&#xff0c;它会变得美丽——使用不当&#xff0c;它会变得一团糟&#xff01;” – 布拉德利文森 正确使用金钱需要纪律。在本教程中&#xff0c;我们将了解如何使用 Excel 进行个人财务以正确管理我们的预算和财务。我们将涵盖以下主题。 …

Java使用spire进行word文档的替换

前言 今天遇到一个需求&#xff0c;需要对word模板进行替换制定的变量 在网上找了很多方案&#xff0c;做了很多的demo&#xff0c;下面就把我觉得比较简单的一种分享给大家 本次的主角是&#xff1a;spire.doc spire.doc是专门实现对word的操作&#xff08;包括文字&#…

「数据密集型系统搭建」原理篇|OLAP、OLTP,竟是两个世界

本篇来聊聊OLAP与OLTP的区别以及它们各自的适用场景&#xff0c;以此话题为导引和大家聊聊技术视野与知识储备对于研发同学的重要性&#xff0c;最后站在事务处理与在线分析的角度分别论述下两个数据世界的底层构建逻辑。 OLAP、OLTP的概念与区别 概念 了解OLAP、OLTP的概念&…

【CANN训练营第三季】学习ascend-CANN遇到的经典疑难问题总结

1、/home/HwHiAiUser/samples_1/cplusplus/level2_simple_inference/1_classification/resnet50_imagenet_classification/src/…/inc/utils.h:13:10: fatal error: acl/acl.h: No such file or directory #include “acl/acl.h” 原因&#xff1a;放错了DDK——PATH export D…

【Unity3D】快速上手 EasyAR

目录 一&#xff0c;AR技术 1.AR简介 2.AR特点 3.AR工作原理 二&#xff0c;EasyAR 插件 1.获取Key 2.EasyAR 插件下载和导入 三&#xff0c;快速上手 EasyAR 废话不多说上运行效果 一&#xff0c;AR技术 1.AR简介 AR&#xff08;Augmented Reality&#xff0c;增强现…

RedLock算法(红锁算法)介绍

文章目录一. 部署图二. RedLock算法简单介绍加锁解锁一. 部署图 各redis独立部署&#xff0c;各自独立 二. RedLock算法简单介绍 加锁 应用程序获取系统当前时间应用程序使用相同的kv值依次从多个redis实例中获取锁。 如果某一个节点超过一定时间依然没有获取到锁则直接放…