【C++】C++ STL探索:Priority Queue与仿函数的深入解析

news2024/11/13 15:22:44

在这里插入图片描述

C++语法相关知识点可以通过点击以下链接进行学习一起加油!
命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇
类和对象-下篇日期类C/C++内存管理模板初阶String使用
String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与Queue

这篇文章将深入探讨优先队列的工作原理,并详细讲解仿函数在其优化过程中的应用。

请添加图片描述
Alt
🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

  • 一、优先级队列
    • 1.1 优先级队列介绍
    • 1.2 优先级队列使用
    • 1.3 小补充:priority_queue类提供的仿函数
  • 二、仿函数
    • 2.1 仿函数概念
    • 2.2 仿函数访问限定符
    • 2.3 仿函数相较于普通函数优势
    • 2.4 当T为指针类型
    • 2.5 仿函数解决实际问题
  • 三、反向迭代器
    • 3.1 反向与正向迭代器不同点
  • 四、priority_queue.h

一、优先级队列

1.1 优先级队列介绍

在这里插入图片描述

[优先级队列文档介绍](priority_queue - C++ Reference (cplusplus.com))

  1. 优先队列是一个容器适配器,根据严格的弱排序标准,它的第一个元素总是它所含的元素中最大的
  2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)
  3. 优先级队列已经不能称为队列,不符合FIFO特性拉
  4. 优先队列被实现为容器适配器,容器适配器即将特定的容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素,元素从特定容器的"尾部"弹出,其称为优先队列的顶部
  5. 底层容器可以是任何标准容器类模板,也可以是特定设计的容器类,容器应该可以通过随机访问迭代器访问,并支持以下操作
  • empty():检测容器是否为空
  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素
  1. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector

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

1.2 优先级队列使用

优先级队列属于容器适配器,并跟堆具有是否相似的结构与功能,这里可以看成堆。由于堆是通过完全二叉树进行搭建,优先级队列默认使用vector作为其底层存储数据的容器,调用库中priority_queue类使用头文件

默认情况下priority_queue是大堆

#pragma once
#include <vector>
#include <algorithm>

namespace bit
{

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

	template<class T, class Containor = vector<T>>
	class priority_queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size()-1);
		}

		void adjust_up(size_t child)
		{
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[parent] < _con[child])
				{
					std::swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
					break;
			}
		}

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

		void adjust_down(size_t parent)
		{
			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])
				{
					std::swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
					break;
			}
		}
		
		const T& top()
		{
			return _con[0];
		}

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

		bool empty()
		{
			return _con.empty();
		}
	private:
		Containor _con;
	};

	void test()
	{
		priority_queue<int> pq;
		pq.push(3);
		pq.push(2);
		pq.push(2);
		pq.push(110);


		while (!pq.empty())
		{
			cout << pq.top() << " ";
			pq.pop();
        }
		cout << endl;
	}
}

以上就是建堆的逻辑,但是如果需要建小堆,需要去内部修改大于小于号,显得有些繁琐。对此引用了一个类模板适应不同类型,对operator()函数进行运算符重载,作为控制比较逻辑,其中在外部进行开关的控制。

问题:

  • 如果需要比较的逻辑,C语言中的sqort函数不行吗?

答:

  • C++不喜欢使用qsort函数,参数部分的函数指针显得很麻烦,C++喜欢使用类模板中的仿函数。

1.3 小补充:priority_queue类提供的仿函数

关于仿函数,库中已经实现好了greater和less仿函数可以直接使用,priority_queue默认使用的是less建大堆

在这里插入图片描述
在这里插入图片描述

二、仿函数

2.1 仿函数概念

仿函数是通过类中运算符重载()实现特定的功能,通过使用匿名对象使用,它的对象可以想函数一样去使用,使得很难区分是调用函数还是调用匿名对象

Less lessfunc;

cout << lessfunc(1, 2) << endl;
cout <<  lessfunc.operator()(1, 2) << endl;
cout <<  Less()(1, 2) << endl;

这里推荐使用第二种和第三种,为了提高代码的可读性,第一种写法可能会引起歧义。

2.2 仿函数访问限定符

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

仿函数中数据是需要公开使用,对于可以通过strcut或将class访问权限设置为public。仿函数/函数对象可以像函数一样去使用,**但是本质是类调用运算符重载。**那么为什么这么麻烦呢?直接写个函数不就行吗?还仿函数,听起来很牛逼哄哄呀!

2.3 仿函数相较于普通函数优势

虽然普通函数在大多数情况下足够,但仿函数提供了更强大的功能和灵活性。

仿函数的优势

  • 仿函数是模板编程的重要组成部分,可以与模板一起使用,实现泛型编程的目的。
  • 将逻辑和数据封装在一个类中,并且可以轻松调整或扩展仿函数的行为,而不需要改变其调用接口
  • 仿函数可以将复杂逻辑和数据封装在类对象中,使得代码更加模块化和可维护,重点体现在封装。
  • 仿函数可以有成员变量,在不同的调用之间保持状态。
  • 每个仿函数都有不同的类去封装,也可以更好适应泛型编程,灵活地去调整

在这里插入图片描述

通过一个类来控制这里的比较逻辑,并且内置类型与自定义类型都支持这种做法。

2.4 当T为指针类型

class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);

	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);
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
int main()
{
	priority_queue <Date*, vector<Date*>, greater<Date*>> pqptr;
	pqptr.push(new Date(2024, 4, 14));
	pqptr.push(new Date(2024, 4, 11));
	pqptr.push(new Date(2024, 4, 15));

	while (!pqptr.empty())
	{
		cout << *(pqptr.top()) << " ";
		pqptr.pop();
	}

	cout << endl;
	return 0;
}

在这里插入图片描述

结果说明:正如图中问题,多次运行出现的结果是不同的,导致这种情况的原因是因为地址的大小new出来是随机的,这里如果是单纯的比较大小,只是比较地址编号的大小,而不是比较日期的大小。这里没有对*进行重载,对此需要单独写。

解决措施

class GteaterDate
{
    public:
    bool operator()(const Date* p1, const Date* p2)
    {
        return *p1 > *p2;
    }
};

在类名实现比较指针的可调用函数对象(仿函数控制比较逻辑,控制任何比较)在优先级队列参数部分传递。

在这里插入图片描述

2.5 仿函数解决实际问题

场景如下:

假如我这里有一堆商品,我需要某个商品的排序,但是这里对于实际中需要排序的类型不止一种,那么如果需要看不同类型的排序,就需要修改,如何破局只有大于或者小于,但是总不能说"请稍等,程序员正在改代码"吧!对此需要使用仿函数的配合,模板帮我们控制的是类型不同的类型。

struct Goods
{
    string _name; // 名字
    double _price; // 价格
    int _evaluate; // 评价

    Goods(const char* str, double price, int evaluate)
        :_name(str)
            , _price(price)
            , _evaluate(evaluate)
        {}
};

struct ComparePriceLess
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;
    }
};
struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price > gr._price;
    }
};

struct CompareEvaluateLess
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._evaluate < gr._evaluate;
    }
};
struct CompareEvaluateGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._evaluate > gr._evaluate;
    }
};

int main()
{
    vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
                                                           3 }, { "菠萝", 1.5, 4 } };

    sort(v.begin(), v.end(), ComparePriceLess());
    sort(v.begin(), v.end(), ComparePriceGreater());
    sort(v.begin(), v.end(), CompareEvaluateLess());
    sort(v.begin(), v.end(), CompareEvaluateGreater());
}

注意:有些容器是没有迭代器的,这样子话就会破坏某种特性,一般不提供

三、反向迭代器

反向迭代器本质是写一个反向迭代器的类模板,给编译器传不同的容器的正向迭代器实例化,编译器帮助我们实例化出各种容器的对应反向迭代器。

3.1 反向与正向迭代器不同点

从功能上是类似的,但是运算符重载++与–运算符行为相反的。

在这里插入图片描述

同时反向与迭代器起始位置和结束位置是相反的,库里面也是形成对称,但是不是绝对的,如果满足反向迭代器逻辑就行。

在这里插入图片描述

思考:

这里it.rbegin()指向的位置(it.end)是哨兵位的位置,我们不希望访问到哨兵位的位置

解决办法:

在这里插入图片描述

需要注意:

这里++不是正向迭代器++运算符重载的逻辑,而是–的逻辑,因为是反向迭代器。这里是不支持tmp-1的,tmp是自定义类型,而没有-的运算符重载。

四、priority_queue.h

#pragma once
#include <vector>
#include <algorithm>

namespace bit
{
    class Date
    {
        public:
        friend ostream& operator<<(ostream& _cout, const Date& d);

        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);
        }
        private:
        int _year;
        int _month;
        int _day;
    };

    ostream& operator<<(ostream& _cout, const Date& d)
    {
        _cout << d._year << "-" << d._month << "-" << d._day;
        return _cout;
    }


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

    template<class T, class Containor = vector<T>, class Compare = Less<T>>
        class priority_queue
        {
            public:
            Compare _com;
            void push(const T& x)
            {
                _con.push_back(x);
                adjust_up(_con.size() - 1);
            }

            void adjust_up(size_t child)
            {
                size_t parent = (child - 1) / 2;
                while (child > 0)
                {
                    //if (_con[parent] < _con[child])
                    if (_com(_con[parent], _con[child]))
                    {
                        std::swap(_con[child], _con[parent]);
                        child = parent;
                        parent = (child - 1) / 2;
                    }
                    else
                        break;
                }
            }

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

            void adjust_down(size_t parent)
            {
                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])
                    if (_com(_con[parent], _con[child]))
                    {
                        std::swap(_con[child], _con[parent]);
                        parent = child;
                        child = parent * 2 + 1;
                    }
                    else
                        break;
                }
            }
            const T& top()
            {
                return _con[0];
            }

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

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

            private:
            Containor _con;
        };

    void test1()
    {
        priority_queue<int> pq;
        pq.push(3);
        pq.push(2);
        pq.push(2);
        pq.push(110);


        while (!pq.empty())
        {
            cout << pq.top() << " ";
            pq.pop();
        }
        cout << endl;
    }

    class GteaterDate
    {
        public:
        bool operator()(const Date* p1, const Date* p2)
        {
            return *p1 > *p2;
        }
    };


    void test2()
    {
        priority_queue <Date*, vector<Date*>, GteaterDate> pqptr;
        //priority_queue <Date*, vector<Date*>> pqptr;
        pqptr.push(new Date(2024, 4, 14));
        pqptr.push(new Date(2024, 4, 11));
        pqptr.push(new Date(2024, 4, 15));

        while (!pqptr.empty())
        {
            cout << *(pqptr.top()) << " ";
            pqptr.pop();
        }
        cout << endl;
    }
}

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!
请添加图片描述

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

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

相关文章

中国数据中心服务器CPU行业发展概述

2024中国服务器CPU行业概览&#xff1a;信创带动服务器CPU国产化 AA体系是一种基于ARM指令系统和Android操作系统的体系结构&#xff0c;主要用于移动设备。与Wintel体系不同&#xff0c;AA体系中CPU厂商对芯片或系统厂商进行指令系统或IP核授权&#xff0c;操作系统厂商提供基…

Ansbile-变量

文章目录 一、Ansible的常量&#xff08;内置的变量&#xff09;有哪些&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1…

八、explicit关键字在C++中的用法

使用方法&#xff1a;修饰单参构造函数 作用&#xff1a;explicit修饰构造函数&#xff0c;禁止类型转换 使用Date d2 19&#xff1b; 这样的方式来进行d2对象的实例化。 在程序上是可以进行下去的&#xff0c;但不符合语法、也不合逻辑。 class Date { public:Date(int day)…

sqlite数据库设计工具

下载 开发环境 VS2022 + Qt5.14.2 CMake修改 add_subdirectory(sqlite3-cmake) include_directories(${CMAKE_SOURCE_DIR}/sqlite3-cmake/src) target_link_libraries(${PROJECT_NAME} sqlite3) 效果 参考 https://github.com/sqlitebrowser/sqlitebrowser

Java之封装

文章目录 1.封装1.1 什么是封装1.2 访问限定符1.3 包1.3.1 什么是包1.3.2 导包1.3.3 自定义包 2. static2.1 static 修饰成员变量2.2 static 修饰成员方法2.3 static成员变量初始化 3. 代码快3.1 普通代码块3.2 实例代码块3.3 静态代码块 4. 对象的打印 1.封装 1.1 什么是封装…

【JPCS出版】第四届电气工程与计算机技术国际学术会议(ICEECT 2024,9月27-29)

会议信息 会议官网&#xff1a;www.iceect.com 2024 4th International Conference on Electrical Engineering and Computer Technologywww.iceect.com 时间地点&#xff1a;2024年9月27日-29日 | 线上&#xff08;ZOOM&#xff09; 最终截稿时间&#xff1a;9月23日 主办…

【GVINS】

【GVINS】 1. GVINS的系统特点2. GVINS的融合导航存在问题3. GVINS的信号的组成4. GVINS的信号的组成 原理推导知乎 1. GVINS的系统特点 概述了一种名为GVINS的系统&#xff0c;它旨在解决视觉-惯性里程计&#xff08;VIO&#xff09;在长时间运行时出现的漂移问题。GVINS通过…

neo4j导入csv数据

neo4j数据可视化实践 手动输入数据 - 官方democsv数据导入准备数据数据处理导入步骤① 导入疾病表格② 导入药物表格③导入疾病-药物关系表格 爬虫的csv文件 手动输入数据 - 官方demo 点击之后&#xff0c;按照左边10张图中的代码&#xff0c;复制粘贴熟悉语法 效果如下 csv数据…

(undone) 学习语音学中关于 i-vector 和 x-vector

来源&#xff1a;https://ieeexplore.ieee.org/stamp/stamp.jsp?tp&arnumber8461375 (这是一篇跟 X-vector 有关的论文) 这里有更适合初学者的两个资料: 1.https://www.youtube.com/watch?vR3rzN6JYm38 &#xff08;MIT教授的youtube视频&#xff09; 2.https://people.c…

JavaScript高级——组合继承

1、借用构造函数继承&#xff08;假的&#xff09; &#xff08;1&#xff09;套路&#xff1a; ① 定义父类型构造函数 ② 定义子类型构造函数 ③ 在子类型构造函数中调用父类型构造 &#xff08;2&#xff09;关键&#xff1a;在子类型构造函数中通用 call&#xff08;&…

VS2019配置C++版本的GDAL

VS2019配置GDAL教程 【特别注意】 vs2019编译好的GDAL库是可以在VS2022上面使用的&#xff0c;我这边做项目已经测试过没有问题&#xff0c;所以vs2022使用vs2019编译的gdal没有问题。 【编译版本介绍】 由于GDAL在vs2019源码流程有点复杂&#xff0c;因此我们在这不做讲解…

CSP-J2024年全真模拟题 阅读程序篇2

因为明天考试&#xff0c;这回给大家准备了超详细的解析~ 22.程序中 n 和 m 只有输入正整数&#xff0c;程序的输出值才可能是 YES A.对B.错 23.程序中用到了递归函数 bool fun&#xff08;int n&#xff09; A.对B.错 24.若输入 n 和 m 都是素数&#xff0c;程序的输出值…

vue MVC设计模式与MVVM设计模式

使用Vue框架开发前端项目,最大的优势就是再也不用进行复杂的DOM操作了,我们只要关心数据的变化即可,Vue框架会帮我们把复杂的DOM进行渲染,这背后都要归功于他的设计思想,即MVVM设计模式。 了解MVVM设计模式之前,有必要先了解一下MVC设计模式,MVVM模式是在MVC模式基础上…

【博弈论 斐波那契博弈】 HRPA

HRPA 这道题就是一个斐波那契博弈的经典模型&#xff1a; 有一堆个数为n的石子&#xff0c;第一个人第一次不能全部取完&#xff0c;每次一个人取石子的个数都应当大于0&#xff0c;且小于等于上一个人取的石子个数的两倍。 问是否有先手必胜策略。 如果有&#xff0c;第一个人…

分享一个通用OCR模型GOT-OCR2.0

通用OCR模型GOT-OCR2.0 在信息化快速发展的今天&#xff0c;光学字符识别&#xff08;OCR&#xff09;技术的应用越来越广泛。从文档数字化到自动化数据录入&#xff0c;OCR技术都发挥着重要作用。今天&#xff0c;我将为大家介绍一个开源的通用OCR模型——GOT-OCR2.0。 软件…

手把手教你-MAC虚拟环境搭建TensorFlow开发环境

参考如下代码布置&#xff0c;直接运行&#xff0c;即可: 1) 安装virtualenv $ sudo pip install virtualenv 2&#xff09;创建虚拟环境文件夹 $ virtualenv --system-site-packages -p python2.7 ./EnvPy27 3) 激活环境 $ source EnvPy27/bin/activate 4) 更新pip $ pi…

【动态规划】最大正方形

最大正方形&#xff08;难度&#xff1a;中等&#xff09; 该题对应力扣网址 思路 min_valuemin({dp[i-1][j-1],dp[i-1][j],dp[i][j-1]}) dp[i][j]min_value 关键点是正方形的右下角(n>1时)&#xff0c;通过画图&#xff0c;可以看出&#xff0c;在基础正方形22中&#x…

安卓13长按电源按键直接关机 andriod13不显示关机对话框直接关机

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 有些设备需要在长按电源键的时候,直接关机。不需要弹出对话框进行询问。 2.问题分析 过滤电源按键,需要在系统里面处理的话,那么我们需要熟悉android的事件分发,然后再…

Unsupervised Deep Representation Learning for Real-Time Tracking

摘要 我们的无监督学习的动机是稳健的跟踪器应该在双向跟踪中有效。具体来说&#xff0c;跟踪器能够在连续帧中前向定位目标对象&#xff0c;并回溯到其在第一帧中的初始位置。基于这样的动机&#xff0c;在训练过程中&#xff0c;我们测量前向和后向轨迹之间的一致性&#xf…

【专题】2024年9月直播、短视频行业报告合集汇总PDF分享(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p37744 在信息时代的浪潮下&#xff0c;直播短视频行业以其独特的魅力和强大的影响力&#xff0c;迅速成为了大众娱乐、信息传播以及商业营销的重要领域。从微短剧的异军突起&#xff0c;到 TikTok 在海外市场的蓬勃发展&#xff1b;…