[C++历练之路]优先级队列||反向迭代器的模拟实现

news2024/12/26 9:26:05

W...Y的主页 😊 

代码仓库分享💕


🍔前言:
在C++的宇宙中,优先队列似乎是一座巨大的宝库,藏匿着算法的珍宝。而就在这片代码的天空下,我们不仅可以探索优先队列的神奇,还能够揭开反向迭代器的神秘面纱。让我们一同踏入这个编程的探险之旅,在这里,我们将用C++语言创造出一个能按照优先级排列元素的神奇容器,并且探索反向迭代器的魅力,仿佛是在编码的星空下追逐着闪烁的代码流星。准备好了吗?让我们迈出第一步,开启这段惊险又充满奇迹的模拟之旅。

目录

了解priority_queue

模拟实现priority_queue

构建基本框架

仿函数的介绍以及第三个参数添加

反向迭代器的模板实现


了解priority_queue

1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素

op_back():删除容器尾部元素
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

 优先队列其实就是数据结构中的堆,而我们想要进行其实现必须掌握其模板。

默认情况下,priority_queue是大堆,而第一个模板参数class T就是其对应的数据类型,第二个模板参数是其数据结构的类型,缺省值为vector,所以其默认的结构类型就是数组,不是链式结构类型的堆,如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。第三个模板类型就是一种仿函数,其可以操控其创建的是大堆还是小堆。

所以我们要用堆的思想来模拟实现优先队列!

模拟实现priority_queue

构建基本框架

首先我们可以照猫画虎,仿照其参数模板进行仿写:

#pragma once
#include<vector>
#include<iostream>
#include<vector>
#include<deque>
#include<stdbool.h>
using namespace std;
namespace why
{
	template<class T, class Container = vector<T>>
	class priority_queue
	{
	public:
		
		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);
		}
		T& top()
		{
			return _con[0];
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

我们将基本函数框架打好,将优先队列的基本函数接口完善,这些都是我们复用的vector、list、deque等接口,可以直接从STL中直接调用。 

注意:这里我们在函数模板中未加入第三个参数进行参数,这里我们在最后实现。原模板接口的缺省默认参数为less<T>,是构建大堆的,所以我们模拟中是先建立大堆。、

往vector中push数据时就要建立大堆进行排序,pop数据得使用向下调整对堆中的数据重新排序成为大堆,所以建立大堆就是使用数据结构中的向上调整函数进行操作,而pop数据是用向下调整的方法进行。

如果对堆这一块的不太了解,可以一下文章:

堆的基本实现——数据结构icon-default.png?t=N7T8https://blog.csdn.net/m0_74755811/article/details/132794715?spm=1001.2014.3001.5502向上调整:

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

仿函数的介绍以及第三个参数添加

在C++中,仿函数(Functor)是一种重载了函数调用操作符 operator() 的对象。它实际上是一个类或者结构体,通过重载 operator(),使得该对象可以像函数一样被调用。仿函数可以像函数一样接受参数,并返回结果,同时可以包含状态信息,因此它们在C++中被广泛用于实现函数对象,作为算法的参数传递,或者用于定义自定义的操作。

通过仿函数,可以实现自定义的比较、排序、转换或者其他操作,这些操作可以被算法直接使用,例如在标准库中的排序算法 std::sort、查找算法 std::find,或者容器类中的自定义排序规则等。使用仿函数可以提供更大的灵活性,使得算法能够适应不同的需求。

下面是一个简单的示例,展示了一个自定义的仿函数用于比较两个整数的大小:

#include <iostream>

// 定义一个比较器仿函数
struct Compare {
    bool operator()(int a, int b) const {
        return a < b; // 自定义的比较规则:a < b
    }
};

int main() {
    Compare cmp; // 创建比较器对象

    int x = 5, y = 10;
    if (cmp(x, y)) {
        std::cout << "x is less than y." << std::endl;
    } else {
        std::cout << "x is greater than or equal to y." << std::endl;
    }

    return 0;
}

在这个示例中,Compare 结构体重载了 operator(),定义了一个比较规则,判断第一个参数是否小于第二个参数。然后在 main 函数中,创建了一个 Compare 类型的对象 cmp,并使用它进行比较操作。

因此,仿函数是C++中的一种强大机制,可以扩展函数的行为,提供更灵活的功能,并允许开发者以更抽象的方式定义特定操作。

所以我们可以使用仿函数针对第三个参数。

priority_queue函数的第三个默认缺省参数为less<T>,如果我们传greater<T>才可以创建小堆。而我们模拟的函数中创建大小堆只不过是将其比较符号进行转换即可。所以我们就可以使用仿函数创建两个不同类型的进行调用。

#pragma once
#include<vector>
#include<iostream>
#include<vector>
#include<deque>
#include<stdbool.h>
using namespace std;
namespace why
{
	template<class T>
	struct less
	{
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};
	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		void adjust_up(size_t child)
		{
			Compare com;
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])
				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[child] > _con[parent])
				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);
		}
		T& top()
		{
			return _con[0];
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

这样我们只需要在模拟函数中创建一个模板变量即可在函数中进行调用。

反向迭代器的模板实现

在STL中的所有容器里都有迭代器与反向迭代器,而在每个容器的模拟实现中我们也将其进行复现。string、vector中的迭代器都可以类似与指针,因为其底层的存储物理空间是连续的,我们可以很好的进行重定义使用。但是list却不行,因为空间是不连续的,所以我们得重新定义封装出一个类迭代器的重定义,将其运算符进行重载成合理的进行使用。

而反向迭代器中我们可以将list中封装的迭代器进行复制粘贴修改,就可以正确使用。

rend指向头节点,而rbegin指向_head->_prev节点,也就是尾节点即可。 

template<class T, class Ref, class Ptr>
struct __list_reverse_iterator
{
	typedef list_node<T> node;
	typedef __list_reverse_iterator<T, Ref, Ptr> self;
	node* _node;

	__list_reverse_iterator(node* n)
		:_node(n)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	self& operator++()
	{
		_node = _node->_prev;

		return *this;
	}

	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_prev;

		return tmp;
	}

	self& operator--()
	{
		_node = _node->_next;

		return *this;
	}

	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_next;

		return tmp;
	}

	bool operator!=(const self& s)
	{
		return _node != s._node;
	}

	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

我们只需要将++、--运算符进行重载即可。将++指向当前节点的_prev节点,而--指向当前节点的_next节点。

那我们看一下STL-list中的反向迭代器是怎么写的:

class reverse_bidirectional_iterator {
  typedef reverse_bidirectional_iterator<_BidirectionalIterator, _Tp, 
                                         _Reference, _Distance>  _Self;
protected:
  _BidirectionalIterator current;
public:
  typedef bidirectional_iterator_tag iterator_category;
  typedef _Tp                        value_type;
  typedef _Distance                  difference_type;
  typedef _Tp*                       pointer;
  typedef _Reference                 reference;

  reverse_bidirectional_iterator() {}
  explicit reverse_bidirectional_iterator(_BidirectionalIterator __x)
    : current(__x) {}
  _BidirectionalIterator base() const { return current; }
  _Reference operator*() const {
    _BidirectionalIterator __tmp = current;
    return *--__tmp;
  }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
  _Self& operator++() {
    --current;
    return *this;
  }
  _Self operator++(int) {
    _Self __tmp = *this;
    --current;
    return __tmp;
  }
  _Self& operator--() {
    ++current;
    return *this;
  }
  _Self operator--(int) {
    _Self __tmp = *this;
    ++current;
    return __tmp;
  }
};

STL中的反向迭代器是封装了正向迭代器,构造一个反向迭代器了。正向迭代器的++就是反向迭代器的--,而正向迭代器的--就是反向迭代器的++。

注意:STL源码中的复用代码rbegin()与rend()的源码为:

直接返回的是其正向迭代器的begin()与end(),所以其解引用的内容就要发生变化:

其结构就不同:

与原来的结构是不同的。

我们可以自己封装一个类进行使用:

#pragma once
namespace why
{
	template<class Iterator, class Ref>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref> Self;
		Iterator _cur;

		ReverseIterator(Iterator it)
			:_cur(it)
		{}

		Ref operator*()
		{
			Iterator tmp = _cur;
			--tmp;
			return *tmp;
		}

		Self& operator++()
		{
			--_cur;
			return *this;
		}

		Self& operator--()
		{
			++_cur;
			return *this;
		}

		bool operator!=(const Self& s)
		{
			return _cur != s._cur;
		}
	};
}

 但是这样复用正向迭代器与刚才的写法所表达的写法是一样的,为什么还要这样单独创建一个类呢?因为list的正向迭代器就是进行封装的,可以复用。但是string、vector的正向迭代器就是指针就不能进行此操作了,所以我们必须复用。


总结:

在这段代码的奇妙旅程中,我们成功地创造了一个C++中的优先队列,仿佛编织了一个可以按照优先级排序元素的魔法网。这个队列不仅仅是一段代码,更是算法的交响乐,奏响着排序、插入、删除的优美旋律。而更加令人惊叹的是,我们在这个编码的仙境中,还揭开了反向迭代器的神秘面纱,为我们的容器增添了一抹独特的色彩。

通过这个模拟实现,我们深入理解了C++中优先队列的本质,并感受到了反向迭代器的便利之处。这不仅是一次代码之旅,更是对数据结构和算法的深刻思考,是对编程艺术的一次追求和探索。

或许,在未来的编程征途中,你会在实际项目中运用这些知识,创造出更为强大、高效的代码。无论何时何地,优先队列和反向迭代器的魔法都将伴随着你,成为解决问题的得力工具。

让我们怀着对编码奇迹的敬畏之心,结束这段代码的冒险。愿你的代码之路充满创造与探索,愿你的算法之舞永远翩翩起舞。编码的世界里,冒险永不止步,期待着你下一次的代码奇迹。

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

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

相关文章

C# Onnx PP-Vehicle 车辆分析(包含:车辆检测,识别车型和车辆颜色)

目录 效果 模型信息 mot_ppyoloe_s_36e_ppvehicle.onnx vehicle_attribute_model.onnx 项目 代码 下载 其他 C# Onnx PP-Vehicle 车辆分析&#xff08;包含&#xff1a;车辆检测&#xff0c;识别车型和车辆颜色&#xff09; 效果 模型信息 mot_ppyoloe_s_36e_ppvehi…

聚观早报 |一加12正式开启预订;OPPO Reno11系列卖点

【聚观365】11月24日消息 一加12正式开启预订 OPPO Reno11系列卖点 小鹏第三季度营收财报 Claude 2.1 聊天机器人公布 现代汽车将与伦敦大学学院合作 一加12正式开启预订 全新的一加12系列公开亮相已有一段时间&#xff0c;不久前一加官方宣布&#xff0c;该机将于12月4日…

Ubuntu20.04清理垃圾vscode缓存

使用VM虚拟机安装了Ubuntu系统&#xff0c;主目录空间越来越小&#xff0c;硬盘扩容之后很快又空间不足&#xff0c;甚至出现了开机卡黑屏的情况&#xff0c;这里记录一下解决过程。 1 重新开机进入系统 状态&#xff1a;卡到了开机黑屏状态&#xff0c;左上角有一条小横杠 原…

居家适老化设计第二十八条---卫生间之地漏

以上产品图片均来源于淘宝网&#xff0c;侵权联系删除 居家适老化的地漏是为了满足老年人和身体不便者的需求&#xff0c;使其能够更方便、安全地使用&#xff0c;具有以下特点&#xff1a;1. 防滑设计&#xff1a;地漏表面的材质采用防滑处理&#xff0c;以防止老人在使用过程…

qgis添加wms服务

例如添加geoserver的wms服务 左右浏览器-WMS/WMTS-右键-新建连接 URL添加geoserver的wms地址 http://{ip}:{port}/geoserver/{workspace}/wms 展开wms目录&#xff0c;双击相应图层即可打开

C++学习之路(一)什么是C++?如何循序渐进的学习C++?【纯干货】

C是一种高级编程语言&#xff0c;是对C语言的扩展和增强。它在C语言的基础上添加了面向对象编程&#xff08;OOP&#xff09;的特性&#xff0c;使得开发者能够更加灵活和高效地编写代码。 C的名字中的“”符号表示在C语言的基础上向前发展一步&#xff0c;即“加加”&#x…

『Linux升级路』基础开发工具——gcc/g++篇

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;Linux &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、快速认识gcc/g 二、预处理 &#x1f4d2;1.1头文件展开 &#x1f4d2;1…

Unity UGUI的HorizontalLayoutGroup(水平布局)组件

Horizontal Layout Group | Unity UI | 1.0.0 1. 什么是HorizontalLayoutGroup组件&#xff1f; HorizontalLayoutGroup是Unity UGUI中的一种布局组件&#xff0c;用于在水平方向上对子物体进行排列和布局。它可以根据一定的规则自动调整子物体的位置和大小&#xff0c;使它…

案例023:基于微信小程序的童装商城的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

大数据量条件SQL查询内存处理方案以及数据过滤算法优化

MySQL是一个广泛使用的关系型数据库管理系统。通过SQL语言进行数据操作和查询&#xff0c;还支持多用户、多线程和分布式操作等功能。 在实际使用中&#xff0c;我们会遇到各种查询条件&#xff0c;如字段名、表名、逻辑运算符、比较运算符、函数等。其中&#xff0c;有些查询…

模电知识点总结(二)二极管

系列文章目录 文章目录 系列文章目录二极管二极管电路分析方法理想模型恒压降模型折线模型小信号模型高频/开关 二极管应用整流限幅/钳位开关齐纳二极管变容二极管肖特基二极管光电器件光电二极管发光二极管激光二极管太阳能电池 二极管 硅二极管&#xff1a;死区电压&#xf…

ruoyi 若依框架采用第三方登录

在项目中&#xff0c;前后端分离的若依项目&#xff0c;需要通过统一认证&#xff0c;或者是第三方协带认证信息跳转到本系统的指定页面。需要前后端都做相应的改造&#xff0c;由于第一次实现时已过了很久&#xff0c;再次重写时&#xff0c;发现还是搞了很长时间&#xff0c;…

新材料制造ERP用哪个好?企业应当如何挑选适用的

有些新材料存在特殊性&#xff0c;并且在制造过程中对车间、设备、工艺、人员等方面提出更高的要求。还有些新材料加工流程复杂&#xff0c;涉及多种材料的请购、出入库、使用和管理等环节&#xff0c;解决各个业务环节无缝衔接问题是很多制造企业面临的管理难题。 新材料制造…

【实验笔记】C语言实验——降价提醒机器人

降价提醒机器人 题目&#xff1a; 小 T 想买一个玩具很久了&#xff0c;但价格有些高&#xff0c;他打算等便宜些再买。但天天盯着购物网站很麻烦&#xff0c;请你帮小 T 写一个降价提醒机器人&#xff0c;当玩具的当前价格比他设定的价格便宜时发出提醒。 输入格式&#xf…

使用C++从0到1实现人工智能神经网络及实战案例

引言 既然是要用C++来实现,那么我们自然而然的想到设计一个神经网络类来表示神经网络,这里我称之为Net类。由于这个类名太过普遍,很有可能跟其他人写的程序冲突,所以我的所有程序都包含在namespace liu中,由此不难想到我姓刘。在之前的博客反向传播算法资源整理中,我列举…

IIC驱动OLED HAL库+CubeMX

一.IIC传输数据的格式 1.写操作 2.读操作 3.IIC信号 二. IIC底层驱动 #define SCL_PIN GPIO_PIN_6 #define SDA_PIN GPIO_PIN_7#define SCL_PORT GPIOB #define SDA_PORT GPIOB/********************** 函数宏定义 **********************/ #d…

element-ui表格无法横向拖动问题

是不是用到了fixed // 因为我只有在小屏显示不下的时候才会出现这个问题所以我在这里做了适配(建议把样式放在全局) media screen and (max-width: 1800px) {// 由于使用了fixed导致横向条无法拖动出现bug.Table-page .el-table__fixed {height: auto !important;bottom: 2px …

在ASP.NET Core 中使用 .NET Aspire 消息传递组件

前言 云原生应用程序通常需要可扩展的消息传递解决方案&#xff0c;以提供消息队列、主题和订阅等功能。.NET Aspire 组件简化了连接到各种消息传递提供程序&#xff08;例如 Azure 服务总线&#xff09;的过程。在本教程中&#xff0c;小编将为大家介绍如何创建一个 ASP.NET …

[架构之路-249]:目标系统 - 设计方法 - 软件工程 - 需求工程- 需求开发:如何用图形表达需求,结构化方法的需求分析

目录 一、概述 二、数据模型&#xff1a;E-R图/实体关系图&#xff08;数据单元之间的结构关系&#xff09; 三、功能模型&#xff1a;数据流图DFD&#xff08;逻辑运算&#xff0c;包括输入和输出&#xff0c;实体之间的关系&#xff09;&#xff1a;输入》处理 》 输出 四…

Mysql 锁机制分析

整体业务代码精简逻辑如下&#xff1a; Transaction public void service(Integer id) {delete(id);insert(id); }数据库实例监控&#xff1a; 当时通过分析上游问题流量限流解决后&#xff0c;后续找时间又重新分析了下问题发生的根本原因&#xff0c;现将其总结如下&#xf…