C++ - 优先级队列(priority_queue)的介绍和模拟实现 - 反向迭代器的适配器实现

news2024/10/7 4:30:14

 仿函数

 所谓仿函数,其实它本身不是一个函数,而是一个类,在这个类当中重载了  operator() 这个操作符,那么在外部使用这个类的 operator() 这个成员函数的时候,使用的形式就像是在使用一个函数一样,仿函数(函数对象)这个类的对象可以像函数一样使用。如下就是定义的一个仿函数:

// 简单仿函数的定义
class Less
{
public:
	bool operator()(int x, int y)
	{
		return x < y;
	}
};

像上述 less 类当中 就定义个一个 operator() 运算符重载函数,在外面使用这个函数的时候如下所示:

Less newLess;
cout << newLess(1 , 2) << endl;

 由上述例子我们发现,如果不看多定义的 newLess 对象的话,在 流插入当中使用 形式就像是在使用函数一样,其实上述的代码等价于下面这个代码

cout << newLess.operator()(1 , 2) << endl;

C++ 当中定义 仿函数只要是想要替代C当中哈数指针 。

使用方式:因为仿函数的使用方式是类似于调用函数,那么我们就可以定义多个仿函数(类),然后再使用模版来使用这些类(因为模版是需要用类型的,模版参数是类型直接使用函数不能套用模版),那么也就相当于一个位置可以使用多个函数。也就是把某一个位置写活了,而不是单纯的写死。

比如: 
 

func(bool x)
{
    if(x)
    {
        cout << "yes" << endl;
    }
}

class less
{
    bool operator()(int x , int y)
    {
        return x > y;
    }
}

class greater
{
    bool operator()(int x , int y)
    {
        return x < y;
    }
}

template<class T, class Comapre = less<T>>
class My_class
{
public:
    void My_class_func()
    {
        Comapre com;
        int x = 10, y = 20;
        //func(x < y);  不用这样写死了
        func(com(x , y)); // 这样写看按照传入的Comapre 是什么函数模版来给func函数传入不同值
    }
}

 如上所示,只要在外部创建 My_class 的对象的时候,给的第二个模版参数是 less 那么,给func函数传入的就是 x > y 的bool值;如果传入的参数是 greater,那么给 func 函数传入的就是 x < y 的bool值。

这里就解决了,模版参数当中只能传入类型,如果我们想用模版来套用不同的函数,从而实现某一些代码的变化。 

优先级队列

 头文件 <queue>  

 优先级队列本质也是一个容器适配器,它默认的容器是 vector 容器:

 容器主要功能如下:

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

 我们发现,功能函数的实现和 堆 类似,其实 优先级队列的底层实现就是 二叉树当中的 堆。

 根据堆的特性,优先级队列也是不支持遍历的,也就不支持迭代器,也是需要像 队列 和 栈一样 pop()掉元素才能取到所有的数据。 如下代码演示:

void text1()
{
	priority_queue<int> pq;
	pq.push(5);
	pq.push(3);
	pq.push(1);
	pq.push(2);

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

}

 输出:
5 3 2 1

 priority_queue 默认是大堆,在上述的模版参数当中也可以看出:

class Compare = less<typename Container::value_type>

这个参数控制了 这个 优先级队列是大堆还是小堆,这里给点缺省参数是 小堆。只不过这个缺省参数是给的不太一样,上述 less 是小于的意思,但是这里表达的意思是 大堆的意思,可以说是一个开发过程化当中的一个失误。

对于 小堆的 和 大堆的控制如下所示:

priority_queue<int , vector<int> , less<int>>      // 大堆的创建
priority_queue<int , vector<int> , greater<int>>  //  小堆的创建

这里控制小堆和大堆,使用仿函数来实现的

对于堆的使用,看下面这个例题:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 题目要求:

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

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

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

 这里就可以用堆来解决,那么建大堆还是小堆呢?

本来排升序是需要建大堆的,但是这里数据量少,所以建小堆和大堆都是可以的。

建大堆代码;

    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq(nums.begin(), nums.end());

        while(--k)
        {
            pq.pop();
        }

        return pq.top();

    }

建小堆代码:

          int findKthLargest(vector<int>& nums, int k) {

    priority_queue<int , vector<int> , greater<int>> pq(nums.begin(), nums.begin() + k);

       for(int i = k;i < nums.size();i++)
       {
           if(nums[i] > pq.top())
           {
               pq.pop();
               pq.push(nums[i]);
           }
       }

        return pq.top();
    }

priority_queue 优先级队列的模拟实现

 关于容器适配器的介绍请看下面这博客,这里不再多讲:

 大体框架

#pragma once
#include<vector>

namespace My_priority_queue
{
	template<class T , class Container = vector<T>>
	class priority_queue
	{
	private:

    private:
		Container _con;
	};
}

构造函数

 支持自己 或 其他容器的迭代器区间的 构造函数

	private:
		void Adjustdown(int parent)
		{
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				{
					++child;
				}

				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	public:
		template<class Inputiterator>
		priority_queue(priority_queue first, priority_queue last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}

			// 建堆 向下调整建大堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				Adjustdown(i);
			}
		}

这里主要实现就是, 先把数据从其他容器当中拷贝到 存储 堆 的容器当中,然后进行向下调整建大堆。堆的具体实现过程可以参考下面博客当中对于堆排序的介绍:(1条消息) 数据结构(c语言版)-5_为这个查询提供一个最大化p ai 5的排序实例_chihiro1122的博客-CSDN博客

 增删查改

 pop()

 堆的删除不能直接对 堆顶 的元素进行删除,然后再把元素按照数组的方式向前移动。这样是不行的,如果这样做的话,整个 堆 的父亲 和  孩子 的关系,全乱了。

我们需要把 堆顶元素和 数组最后一个元素进行交换,然后删除数组最后一个元素,在把堆顶的元素进行向下调整建堆

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

push()

 往数组最后一个位置插入,然后对数组最后一个位置的元素进行向上调整即可:

// 向上调整
		void adjustup(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		// 插入元素
		void push(const T& x)
		{
			_con.push_back(x);
			adjustup(_con.size());
		}

其他函数

 包括top()函数   size() 函数  empty()函数。

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

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

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

仿函数实现 priority_queue 的大堆和小堆的选择

 我们上述已经说过了 仿函数的用法,那么这里就直接用到 priority_queue 当中,因为建大堆和建小堆的区别,就在与向上调整和向下调整两个函数的 孩子结点和 双亲结点的大小比较关系,所以中替换这个两个函数即可:

两个仿函数,Less类实现的是大堆;Greater类实现的是小堆:

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

下面是对 向上调整和向下调整的改进:
 

// 向下调整
		void Adjustdown(int parent)
		{
			Comapre com;

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

注意:因为我们上述实现的仿函数只是单纯的比较大小,如果是内置类型那么没有问题;但如果是自定义类型,那么这个类型必须要重载 operator< 和 operator> 这两个重载函数,不然会编译报错。

除了实现 operator< 和 operator> 这两个重载函数,像上述实现的仿函数其实在库当中已经实现了的,在库当中也是用 " < " " > " 来计算的,所以,其实这个仿函数我们可以不自己实现,直接用库当中的仿函数。

但是使用库当中的仿函数还是弊端,假设使用的自定义类没有实现 operator< 和 operator> 这两个重载函数,那么我们需要自己在仿函数当中去实现;其实这种情况是可以避免的,一般来说 operator< 和 operator> 这两个重载函数 在自定义类型当中就应该实现。

除了上述说的一种情况,还有一种情况,如下代码所示:

	My_priority_queue::priority_queue<Date*> pq;
	pq.push(new Date(2023, 7, 1));
	pq.push(new Date(2023, 6, 2));
	pq.push(new Date(2023, 8, 3));

在 上述的 priority_queue 容器当中存储不是一个 Date 自定义类型对象,而是存储的是对象的指针,在 push()的时候新开辟的空间。

这种情况的输出结果就非常的奇怪,我们不改变代码,多次运行 可执行程序,每一次的输出结果都是不一样的

 这时候,传入的模版参数的类似是 一个指针,那么走到仿函数当中去 比较的时候,是按照地址的大小去排序的,而每一次在空间当中开辟空间的,这个空间的位置是不确定的,所以导致了结果不一样。

如果在这种情况下,想要按照日历去排序的话,就不能按照 库当中的仿函数去走了,要自己在仿函数当中进行日期的判断。

注意:不能直接使用类当中的重载运算符函数,只能自己控制的仿函数当中去实现/使用。因为指针属于是内置类型,内置类型是不能重载运算符的,Date类当中实现的 重载运算符函数是 属于这个自定义类型的,不属于指针。如下代码所示:

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

就要像如上方式去调用 Date类 当中重载运算符函数。

反向迭代器

 之前对 list 的正向迭代器进行了实现,list 的迭代器不再像 vector  和 string一样使用原生指针来做作为迭代器,而是把指针 用一个类 进行了包装/封装,让这个迭代器使用起来 和 vector 一样,使用 解引用(*),向后迭代(++),等等操作来使用。

反向迭代器 相比于 正向迭代器,不同的就是 (++)是向前迭代器,开始位置 和 终止位置 反过来而已,那么,我们大可以直接拷贝一份 正向迭代器的 代码,然后对 operator++ 和 operator-- 两个函数换一下,然后在完善一些 typedef 的工作等等,可以实现,但是冗余

在库 当中 list 的反向迭代器不是这样实现的:库当中的实现简单来说,在list环境下,用一个反向迭代器的类,对正向迭代器的类进行封装,(++)调用 正向迭代器当中的 (--),(--)调用 (++);

 但是其实没有这么简单。库当中是实现了一个反向迭代器的类模版(反向迭代器的适配器),只要我使用某一个类的反向迭代器,就会去调用这个 反向迭代器的类模板;也就是说,库当中用一个类模板实现了全部容器的反向迭代器。

 它并不是针对某一个类去实现的 反向迭代器,针对的是全部的容器。

 在 STL 当中,反向迭代器的代码在 stl_iterator.h 这个文件当中:

大概是这样的;

 而且,对于rend(), rbegin()的实现是 和 end (),rbegin()镜像对称的,所以我们看见了,反向迭代器当中的 解引用操作是 先 (--)后解引用的,也就是访问的是 当前位置的前一个位置:

 

 迭代器关系如下:

 由此可见是完全镜像的,反向迭代器和正向迭代器的位置是对称的。

反向迭代器 的 适配器 实现

 根据上述的描述,我们下述在 list 当中的反向迭代器的实现,就参照库当中的逻辑去实现。

问题:operator*的返回值问题,因为 反向迭代器不想正向迭代器是实现在 各个 容器当中的,反向迭代器没有 这个容器存储的数据类型,正向迭代器使用模版参数来知道 容器存储数据的类型的。

官方库当中,是用 萃取的 方式把容器当中存储的数据类型套出来的,但是这个方式非常的复杂。

另一种方式相对简单一些,就是让用的人,把容器的 数据类型 用模版参数传过来

 代码实现:

#pragma once

#include<iostream>

using namespace std;

namespace reverse_iterator
{
	//			迭代器类型     T*         T&
	template<class iterator, class Ref, class ptr>
	class ReverseIterator
	{
		typedef ReverseIterator< iterator, Ref, ptr> self;
		iterator _it;

		ReverseIterator(it)
			:_it(it)
		{}

		Ref operator*()
		{
			iterator tmp = it;
			return *(--tmp);
		}

		ptr operator->()
		{
			return &(operator*());
		}

		self operator++()
		{
			--_it;
			return _it;
		}

		self operator--()
		{
			++_it;
			return _it;
		}

		bool operator!= (const T& x) const
		{
			return _it != x._it;
		}
	};
}

对于在类当中的实现,typedef 一下就行:

template<class T, class Ref, class ptr>
class xxx
{
public:
    // 非 const迭代器
    typedef ReverseIterator<T, T* , T&> reverse_iterator;
    // const 迭代器
    typedef ReverseIterator<T, const T* , const T&> const_reverse_iterator;

    reverse_iterator rbegin()
    {
        return reverse_iterator(end());
    }

    reverse_iterator rend()
    {
        return reverse_iterator(begin());
    }

    const_reverse_iteratorrbegin()
    {
        return const_reverse_iterator(end());
    }

    const_reverse_iteratorrend()
    {
        return const_reverse_iterator(begin());
    }

}

这个反向迭代器,根据给的是哪一个容器的正向迭代器,就适配哪一个的反向迭代器。

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

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

相关文章

Web3.0:已经开启的互联网革命!

1 痛点 2 web发展形态 只读、封闭式、协作式。 3 一个高度联系、全球统一的数字经济体 去中心化架构通过计算几余打破数据垄断&#xff0c;同时实现数字确权大量的功能依靠智能合约自动实现&#xff0c;运转效率大大提升DAO大量涌现&#xff0c;全球范围实现资源配置 4 特…

类加载机制,类加载顺序

类加载顺序 ①类加载从上往下执行&#xff0c;依次执行静态的初始化语句和初始化块&#xff0c;而且类加载优先于对象创建。&#xff08;静态初始化语句和初始化块只加载一次&#xff09; ②创建本类的对象时&#xff0c;从上往下执行一次非静态的初始化语句和初始化块&#…

ElementUI Select选择器如何根据value值显示对应的label

修改前效果如图所示&#xff0c;数据值状态应显示为可用&#xff0c;但实际上仅显示了状态码1&#xff0c;并没有显示器对应的状态信息。在排查了数据类型对应关系问题后&#xff0c;并没有产生实质性影响&#xff0c;只好对代码进行了如下修改。 修改前代码&#xff1a; <…

出海周报|Temu在美状告shein、ChatGPT安卓版上线、小红书回应闪退

工程机械产业“出海”成绩喜人&#xff0c;山东相关企业全国最多Temu在美状告shein&#xff0c;跨境电商战事升级TikTok将在美国推出电子商务计划&#xff0c;售卖中国商品高德即将上线国际图服务&#xff0c;初期即可覆盖全球超200个国家和地区ChatGPT安卓版正式上线&#xff…

【梯度下降应用于波士顿房价预测(岭回归)】

数据准备 首先&#xff0c;我们需要获取波士顿房价数据集&#xff0c;并对数据进行处理。我们从CMU统计学习数据集库中获取数据&#xff0c;并将其划分为训练集和测试集。 import pandas as pd import numpy as npdata_url "http://lib.stat.cmu.edu/datasets/boston&q…

CFS调度器(原理->源码->总结)

一、CFS调度器-基本原理 首先需要思考的问题是&#xff1a;什么是调度器&#xff08;scheduler&#xff09;&#xff1f;调度器的作用是什么&#xff1f;调度器是一个操作系统的核心部分。可以比作是CPU时间的管理员。调度器主要负责选择某些就绪的进程来执行。不同的调度器根…

基于JAVA SpringBoot和Vue高考志愿填报辅助系统

随着信息技术在管理中的应用日益深入和广泛&#xff0c;管理信息系统的实施技术也越来越成熟&#xff0c;管理信息系统是一门不断发展的新学科&#xff0c;任何一个机构要想生存和发展&#xff0c;要想有机、高效地组织内部活动&#xff0c;就必须根据自身的特点进行管理信息时…

VUE中使用ElementUI组件的单选按钮el-radio-button实现第二点击时取消选择的功能

页面样式为&#xff1a; html 代码为&#xff1a; 日志等级&#xff1a; <el-radio-group v-model"logLevel"><el-radio-button label"DEBUG" click.native.prevent"changeLogLevel(DEBUG)">DEBUG</el-radio-button><el-r…

Harbor私有仓库搭建

Harbor 是由 VMware 公司中国团队为企业用户设计的 Registry server 开源项目&#xff0c;包括了权限管理(RBAC)、LDAP、审计、管理界面、自我注册、HA 等企业必需的功能&#xff0c;同时针对中国用户的特点&#xff0c;设计镜像复制和中文支持等功能。 作为一个企业级私有 Reg…

31. Oracle开发中遇到的一些问题

文章目录 Oracle开发中遇到的一些问题一、Oracle中的同义词二、 Oracle创建表空间无权限1.问题&#xff1a;2. 解决 三、设置Oracle不区分大小写四、查询语句表名是否需要加双引号问题 Oracle开发中遇到的一些问题 一、Oracle中的同义词 1.现在有一个这样的oracle业务场景 我…

MLagents 多场景并行训练

MLagents多场景并行训练调试总结 摘要 关于Unity MLagents的环境安装已经有了很多的blog和Video&#xff0c;本文针对MLagents的多场景的并行训练&#xff0c;以及在探索过程中出现的问题进行总结。 内容 Unity MLagents 多场景并行训练可以同时设置开多个场景进行并行探索…

C# 翻转二叉树

226 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;…

【Java基础教程】(四十八)集合体系篇 · 上:全面解析 Collection、List、Set常用子接口及集合元素迭代遍历方式~【文末送书】

Java基础教程之集合体系 上 &#x1f539;本章学习目标1️⃣ 类集框架介绍2️⃣ 单列集合顶层接口&#xff1a;Collection3️⃣ List 子接口3.1 ArrayList 类&#x1f50d; 数组&#xff08;Array&#xff09;与列表&#xff08;ArrayList&#xff09;有什么区别?3.2 LinkedL…

java执行ffmpeg命名的Docker镜像制作

今天来记录一下通过Dockerfile制作docker镜像的过程 背景 我需要通过java服务调用ffmpeg去执行视频合并的功能&#xff0c;想把这个环境封装到docker镜像当中&#xff0c;方便以后迁移部署。 实现方法 随便找一个路径创建一个Dockerfile文件 touch Dockerfilevim Dockerfi…

OpenTelemetry框架

文章目录 1、分布式监控系统2、OpenTelemetry3、OpenTelemetry-Trace相关组件4、Context Propagation搭配HTTP Header传递信息5、Span相关 1、分布式监控系统 随着单体架构演变为微服务架构&#xff0c;线上问题的追踪和排查变的越来越困难&#xff0c;想解决这个问题就得实现…

【高分论文密码】大尺度空间模拟预测与数字制图教程

详情点击链接&#xff1a;【高分论文密码】大尺度空间模拟预测与数字制图 一&#xff0c;R语言空间数据及数据挖掘关键技术 1、R语言空间数据及应用特点 1)R语言基础与数据科学 2)R空间矢量数据 3)R栅格数据 2、R语言空间数据挖掘关键技术 二&#xff0c;R语言空间数据高…

【并发编程】线程池

背景 线程的创建和销毁都需要很大的开销&#xff0c;当线程数量过大&#xff0c;并且线程生命周期短。这时候线程频繁地创建和销毁就很没有必要。 在 Java 中可以通过线程池来解决此问题。线程池里的每一个线程代码结束后&#xff0c;并不会死亡&#xff0c;而是再次回到线程…

[Java] 观察者模式简述

模式定义&#xff1a;定义了对象之间的一对多依赖&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff0c;当主题对象发生变化时&#xff0c;他的所有依赖者都会收到通知并且更新 依照这个图&#xff0c;简单的写一个代码 package Section1.listener;import java.ut…

枚举类型

enum 枚举类型名 {命名枚举常量列表}; enum DAYS {MON, TUE, WED, THU, FRI, SAT, SUN};

POLARDB IMCI 白皮书 云原生HTAP 数据库系统 一 数据压缩和打包处理与数据更新

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…