[C++] map、set 的 封装 (二):map、set和红黑树的双向奔赴

news2024/11/29 0:54:54

标题:[C++] map、set 的 封装 (二)

@水墨不写bug



前言

        在正式深入进map、set封装之前,我有一些话想说,map和set的封装在初次理解时可能会比较困难,仅仅是模板,仿函数引起的回调就会把你拌入无底深渊。但是,在我看来,map、set的封装本身就是一个双向奔赴的过程,map、set想要复用红黑树,红黑树也为复用进一步调整,使复用更加简单方便。这是两个齿轮不断校准的过程,直到两侧的齿轮紧紧的咬合在一起。所以,既要讲map、set也要讲红黑树。

        此外,这里的封装技巧也是无数先辈经验和智慧的总结,这当然需要我们花一部分时间来理解和吸收。

        所以,不着急,路要一步一步的走。


目录

前言

一、红黑树的迭代器实现

1.重载 operator++()、 operator--():

 2.const迭代器实现

3.迭代器模板参数传入的细节 

二、红黑树的成员函数实现细节

1.begin,end 

2.find 

3.insert

 三、map、set层面做出的努力


正文开始:

一、红黑树的迭代器实现

        红黑树的迭代器是我们封装的一个类,在类中,我们可以对运算符进行重载。重载运算符的类实例化出的对象会根据我们重载时的规则来表现出我们想要的行为。

        比如:一个整形指针,++代表向后移动一个整形长度(4字节),但是对于关联性容器红黑树,其迭代器++代表移动到中序的下一个位置,这就需要我们自己通过重载来实现。

        迭代器设计出来,是为了方便对容器进行操作。红黑树迭代器中,我们主要讲解实现++和--的重载:

1.重载 operator++()、 operator--():

operator++:

        中序遍历为:左子树,根,右子树。

        在类中隐含有this,代表我们想要让this 以中序 后移一位。我们要暂时用局部的眼光来看待这个问题:

        当this的右子树非空,由于this访问过,接下来要访问右子树的最左节点;

        当右子树为空,则this这棵树访问完了,这就需要向上一级的子树移动,继续判断,这是一个循环的过程:只要当前节点是父节点的右子树根节点,则表示以父为根的子树访问完了,需要向上一级子树移动。


operator--:

        逆中序遍历为:右子树,根,左子树。 

        类似的:

        当this的左子树非空,由于this被访问过,接下来要访问左子树的最右节点;

        当左子树为空,则this这棵树访问完了,这就需要向上一级的子树移动,继续判断,这是一个循环的过程:只要当前节点是父节点的左子树根节点,则表示以父为根的子树访问完了,需要向上一级子树移动。


与++不同的是:我们实现的红黑树没有设置头节点(哨兵),而是如下设计:

        begin():最左节点;

        end():nullptr

        所以当我们想要用end()的返回值用迭代器反向遍历时,会出现对nullptr解引用导致崩溃的问题。

解决方案如下:

        在重载operator--时,进行特殊判断,如果是对end()的返回值(nullptr)解引用,则找到最右节点返回即可。

 2.const迭代器实现

        与实现链表的迭代器的时候类似,我们通过传入特殊类型,与普通模板参数区分开:

                普通迭代器传入 <V,V&,V*>;

                const迭代器传入 <V,const V&,const V*>

        同时多设置两个模板参数额外用于接受引用与指针类型,分别用于operator*,operator->的返回值。

3.迭代器模板参数传入的细节 

         在传入迭代器模板参数时,需要注意:

        map的key不能修改;

        set数据本身就不能修改。

        红黑树是插入的时候根据key的大小来构建的。为了防止我们通过迭代器修改key,导致红黑树失效,由于我们修改的是数值域,所以我们把set迭代器的第二个参数(数值域)设为const;把map的pair内的K设置为const即可:

 

 


红黑树迭代器设计参考:

//迭代器类似于指针,内置类型的指针的++,*,==,!= 运算默认成立
//红黑树迭代器类目的在于 重载++,*,==,!=运算符,红黑树迭代器按照我们想要的方式进行运算
template<class V,class Ref,class Ptr>
struct RBTreeIterator
{
	typedef RBTreeIterator<V,Ref,Ptr> Self;
	typedef RBTreeNode<V> Node;

	Node* _node;
	Node* _root;

	RBTreeIterator(Node* node,Node* root)
		:_node(node)
		,_root(root)
	{}

	//中序遍历,左 根 右
	Self& operator++()
	{
		//右子树不为空,找右子树的最左节点
		if(_node->_right)
		{
			Node* leftMost = _node->_right;
			while (leftMost->_left)
			{
				leftMost = leftMost->_left;
			}
			_node = leftMost;
		}
		else
			//右子树为空,表示此树访问完了
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	//反向的中序 右 根 左 
	//由于end是nullptr,需要特殊处理
	Self& operator--()
	{
		if (_node == nullptr)
			//找最右节点
		{
			Node* rightMost = _root;
			while (rightMost->_right)
			{
				rightMost = rightMost->_right;
			}
			_node = rightMost;
		}
		else if (_node->_left)
			//左子树的最右节点
		{
			Node* rightMost = _node->_left;
			while (rightMost->_right)
			{
				rightMost = rightMost->_right;
			}
			_node = rightMost;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
	bool operator==(const Self& s)
	{
		return _node == s._node;
	}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
};

二、红黑树的成员函数实现细节

1.begin,end 

对于begin、end不再赘述,本别返回最左节点、nullptr。

2.find 

根据key找到对应的节点,返回这个节点的迭代器,如果此节点不存在,则返回nullptr构造的迭代器。

3.insert

是红黑树的重点逻辑。在之前的实现逻辑上,我们主要更改了insert的返回值类型,从单一的bool型改为了含有迭代器和bool的pair型:pair<iterator,bool>;与之前的实现基本相同。

 

Iterator Begin()
{
	Node* leftMost = _root;
	while (leftMost && leftMost->_left)
	{
		leftMost = leftMost->_left;
	}
	return Iterator(leftMost,_root);
}
//nullptr 暂时代表end
Iterator End()
{
	return Iterator(nullptr,_root);
}

Iterator Find(const K& key)
{
	KeyOfV kov;
	Node* cur = _root;
	while (cur)
	{
		if (kov(cur->_data) < key)
		{
			cur = cur->_right;
		}
		else if (kov(cur->_data) > key)
		{
			cur = cur->_left;
		}
		else
		{
			return Iterator(cur, _root);
		}
	}
	return End();
}

pair<Iterator,bool> insert(const V& data)
{
	//对于空的特殊处理
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;

		//插入成功
		return { Iterator(_root,_root),true };
	}
	//找到插入位置
	Node* cur = _root, * parent = nullptr;
	KeyOfV keyofv;

	while (cur)
	{
		if (keyofv(cur->_data) < keyofv(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (keyofv(cur->_data) > keyofv(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{//插入失败,有相同值
			return { Iterator(cur,_root),false };
		}
	}
	//new并连接
	cur = new Node(data);
	Node* newnode = cur;

	if (keyofv(parent->_data) < keyofv(data))
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	//降低平衡
	//二叉树逻辑结束,红黑树开始
	//cur为红,p为红,g为黑,
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			//uncle在右侧
			//        g
			//   p          u
			//	
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)//u存在且为红
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else//u不存在或者u存在且为黑
				//旋转
			{
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}
		else
		{
			//uncle在左侧s
			//        g
			//   u          p
			//					
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)//uncle存在且为红
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = grandfather->_parent;
			}
			else//uncle不存在或者存在且为黑
				//旋转
			{
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}

	}
	_root->_col = BLACK;
	return { Iterator(newnode,_root),true };
}

 三、map、set层面做出的努力

         其实,在底层红黑树已经实现出来的情况下,map、set层面并不复杂。

        需要做的工作是为map、set设计一个表层调用的壳。函数的实现大部分是通过红黑树对象调用红黑树成员函数。

 实现如下:

my_set.h:

#pragma once
#include"RBTree.h"
namespace ddsm
{
	template<class K>
	class set
	{
		struct SetKeyOfV
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		//封装红黑树的Iterator,在本封装层套一层壳
		typedef typename RBTree<K,const K, SetKeyOfV>::Iterator iterator;
		typedef typename RBTree<K,const K, SetKeyOfV>::ConstIterator const_iterator;
		iterator begin()
		{
			return _rbtree.Begin();
		}
		iterator end()
		{
			return _rbtree.End();
		}
		const_iterator begin() const
		{
			return _rbtree.Begin();
		}
		const_iterator end() const
		{
			return _rbtree.End();
		}
		pair<iterator,bool> insert(const K& data)
		{
			return _rbtree.insert(data);
		}
		iterator find(const K& key)
		{
			return _rbtree.Find(key);
		}	
	private:
		bool isvaildset()
		{
			return _rbtree.IsValidRBTree();
		}
		RBTree<K,const K, SetKeyOfV> _rbtree;
	};
}

my_map.h:

#pragma once
#include"RBTree.h"

namespace ddsm
{

	template<class K,class V>
	class map
	{
		struct MapKeyOfV
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		//封装红黑树的Iterator,在本封装层套一层壳

		typedef typename RBTree<K, pair<const K, V>, MapKeyOfV>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfV>::ConstIterator const_iterator;
		iterator begin()
		{
			return _rbtree.Begin();
		}
		iterator end()
		{
			return _rbtree.End();
		}
		const_iterator begin() const
		{
			return _rbtree.Begin();
		}
		const_iterator end() const
		{
			return _rbtree.End();
		}
		pair<iterator,bool> insert(const pair<K,V>& data)
		{
			return _rbtree.insert(data);
		}
		iterator find(const K& key)
		{
			return _rbtree.Find(key);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert({key,V()});
			return ret.first->second;
		}
	private:
		bool isvaildmap()
		{
			return _rbtree.IsValidRBTree();
		}
		RBTree<K, pair<const K, V>, MapKeyOfV> _rbtree;
	};

}

完~

未经作者同意禁止转载

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

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

相关文章

【从Qwen2,Apple Intelligence Foundation,Gemma 2,Llama 3.1看大模型的性能提升之路】

从早期的 GPT 模型到如今复杂的开放式 LLM&#xff0c;大型语言模型 (LLM) 的发展已经取得了长足的进步。最初&#xff0c;LLM 训练过程仅侧重于预训练&#xff0c;但后来扩展到包括预训练和后训练。后训练通常包括监督指令微调和校准&#xff0c;这是由 ChatGPT 推广的。 自 …

redis AOF机制

在redis运行期间&#xff0c;不断将redis执行的写命令写到文件中&#xff0c;redis重启之后&#xff0c;只要将这些命令重复执行一遍就可以恢复数据。因为AOF只是将少量的写命令写入AOF文件中&#xff0c;因此其执行效率高于RDB&#xff0c;开启AOF即使Redis发生故障&#xff0…

Redis缓存场景

Redis缓存场景 文章目录 Redis缓存场景Redis做缓存旁路缓存缓存异常缓存穿透缓存击穿缓存雪崩 缓存一致性先写缓存再写数据库先写数据库再写缓存先删除缓存再写数据库先写数据库再删缓存缓存双删Binlog异步更新总结 Redis做缓存 部分图解来自于&#xff1a;https://www.miansh…

微课录制不再难:精选三款录屏软件助你一臂之力

在这个信息爆炸的时代&#xff0c;微课以其短小精悍、易于消化的特点&#xff0c;成为知识传播的新宠。无论是教师备课、企业培训&#xff0c;还是个人知识分享&#xff0c;微课都能以其独特的方式&#xff0c;让学习变得更加高效和便捷。在数字化教学的浪潮中&#xff0c;PPT课…

SAP SUBSCREEN使用

step1&#xff1a;在SELECTION-SCREEN中定义SUBSCREEN SELECTION-SCREEN BEGIN OF SCREEN 0101 AS SUBSCREEN. SELECT-OPTIONS S_XMGS FOR ZTPO0074-XMGS . SELECTION-SCREEN END OF SCREEN 0101.STEP2: 引用SUBSCREEN 关键字 CALL SUBSCREEN SUBSCR_0100 INCLUDING SY-RE…

Java基础核心知识学习笔记

方法重载 请记住下面重载的条件 方法名称必须相同。参数列表必须不同&#xff08;个数不同、或类型不同、参数类型排列顺序不同等&#xff09;。方法的返回类型可以相同也可以不相同。仅仅返回类型不同不足以成为方法的重载。重载是发生在编译时的&#xff0c;因为编译器可以根…

结合ChatGPT与Discord,提高团队合作效率

本文将教你如何集成Discord Bot&#xff0c;助力团队在工作中实现更高效的沟通与协作。通过充分发挥ChatGPT的潜力&#xff0c;进一步提升工作效率和团队协作能力。无需编写任何代码即可完成本文所述的操作&#xff0c;进行个性化定制只需对参数进行微调即可。 方案介绍 如果在…

联想闪电鲨移动硬盘文件没删除却消失了怎么办

在日常的数据存储与管理中&#xff0c;移动硬盘作为便携且容量可观的存储设备&#xff0c;深受用户青睐。然而&#xff0c;当您发现联想闪电鲨移动硬盘中的文件突然消失&#xff0c;而您确信并未进行删除操作时&#xff0c;这无疑会令人感到困惑与焦虑。本文旨在为您揭开这一谜…

MySQL各个版本root账号没有最高权限的解决方法

一、详细报错 ERROR 1045 (28000): Access denied for user ‘root’‘localhost’ (using password: YES) 报错原因&#xff08;分析过程&#xff09;&#xff1a; rootlocalhost用户密码修改导致 解决方法&#xff1a; 跳过权限验证启动数据库&#xff0c;并修改密码。如下…

[028-3].第05节:RabbitMQ中的交换机

1.什么是Exchanges(交换机&#xff09;: 1.RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上&#xff0c;通常生产者甚至都不知道这些消息传递传递到了哪些队列中2.生产者只能将消息发送到交换机(exchange)&#xff0c;交换机工作的内容非常…

C++ TinyWebServer项目总结(7. Linux服务器程序规范)

进程 PID 进程的PID&#xff08;Process ID&#xff09;是操作系统中用于唯一标识一个进程的整数值。每个进程在创建时&#xff0c;操作系统都会分配一个唯一的PID&#xff0c;用来区分不同的进程。 PID的特点 唯一性&#xff1a; 在操作系统运行的某一时刻&#xff0c;每个…

江苏省第二中医院案例│“互联网+医疗”下的灾备建设

江苏省第二中医院&#xff0c;成立于1988年12月&#xff0c;是一所融医疗、教学、科研、预防保健为一体的公立省级综合性三级甲等中医院。目前&#xff0c;正对照“大学附属医院”“省级三甲医院”建设标准&#xff0c;全面推进医院建设高质量跨越式发展。 用户需求&#xff1a…

冒 泡 排 序

今天咱们单独拎出一小节来聊一聊冒泡排序昂 冒泡排序的核心思想就是&#xff1a;两两相邻的元素进行比较&#xff08;理解思路诸君可看下图&#xff09; 接下来我们上代码演示&#xff1a; 以上就是我们初步完成的冒泡排序&#xff0c;大家不难发现&#xff0c;不管数组中的元…

智能停车计费系统设计与实现_urqs9

TOC springboot552智能停车计费系统设计与实现_urqs9--论文 绪 论 1.1 研究背景 在新世纪的今天&#xff0c;计算机已经发展到一定的规模&#xff0c;带动了国内经济和科学技术的快速发展&#xff0c;科学技术的发展大大提高了生产效率&#xff0c;使人们的物质生活需求得到…

为啥每个语音的printf(“%d%d%d%d“,i,j,i++,j++)不一样

题目来源一位考研同学的题目。 第一眼&#xff1a;小子&#xff0c;这都不会&#x1f600; 第二眼&#xff1a;wok&#xff0c;咋没有选项&#x1f622; 作为一个大一学C语音&#xff0c;大二学Java的同学来说&#xff0c;我一看就觉得肯定是11 6 11 6 。 结果很遗憾&#xff0…

微服务开发相关问题

微服务开发相关问题 服务注册nacos 2.X注册问题[Nacos Config] config[dataIddatasource.yml, groupDEFAULT_GROUP] is empty 参考 持续更新… 服务注册 nacos 2.X注册问题 [Nacos Config] config[dataIddatasource.yml, groupDEFAULT_GROUP] is empty 因为&#xff1a;# 由于…

C++第十二弹 -- STL之list模拟实现

文章索引 前言模拟实现list1. ListNode节点类2. list的迭代器封装3. 反向迭代器4. list类的模拟实现测试代码 list的反向迭代器总结 前言 通过模拟实现可以让我们更加深刻的理解C底层STL的实现逻辑, 本篇就对list的底层进行模拟实现. 博客主页: 酷酷学!!! 点击关注 共同进步!…

影响五金精密零件加工价格的因素

在制造业中&#xff0c;五金精密零件的加工价格受到多种因素的影响。了解这些因素&#xff0c;对于企业合理控制成本、选择合适的加工供应商至关重要。 首先&#xff0c;零件的设计复杂度是一个重要因素。复杂的设计通常需要更先进的加工技术和更多的加工工序。例如&#xff0c…

intel ECI作为ACRN VM使用dpdk(vfio和iommu问题)以及img扩容

ACRN虚拟机内IOMMU 对非虚拟机而言&#xff0c;只要在BIOS里开启VT-d就可以用iommu去映射vfio使用DPDK&#xff0c;但是在虚拟机中即便BIOS开启了VT-d&#xff0c;它也传不到VM中。因此这个帖子解决一下这个问题。 在ACRN的launch脚本中需要passthru两个网卡&#xff0c;一个用…

C++ TinyWebServer项目总结(8. 高性能服务器程序框架)

《Linux 高性能服务器编程》一书中&#xff0c;把这一章节作为全书的核心&#xff0c;同时作为后续章节的总览。这也意味着我们在经历了前置知识的学习后&#xff0c;正式进入了 Web 服务器项目的核心部分&#xff01; 前置内容回顾&#xff1a; 1. C TinyWebServer项目总结&…