【C++】从零开始map与set的封装

在这里插入图片描述
送给大家一句话:
今日的事情,尽心、尽意、尽力去做了,无论成绩如何,都应该高高兴兴地上床恬睡。 – 三毛 《亲爱的三毛》

🌃🌃🌃🌃🌃🌃🌃🌃🌃
🌏🌏🌏🌏🌏🌏🌏🌏🌏


从零开始map与set的封装

  • 1 前言
  • 2 红黑树的迭代器
  • 3 map与set的封装
    • 3.1 红黑树的改进
    • 3.2 map的封装
    • 3.3 set 的封装
  • 4 总结
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 前言

为了map与set 的封装,我们进行了非常充足的知识储备!!!

首先,为了了解map 与 set 的底层原理我们开始学习二叉搜索树,二叉搜索树在二叉树的基础上增添了:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
  • 注意通常二叉搜索树不会有相同的键值

这样可以在一定程度上满足高效搜索的需求,但是在极端的情况(单子树情况)其效率会下降到O(n)。因此就有了改进的二叉搜索树:AVL树和红黑树。他们都增加一些特性使其最大程度上近似平衡二叉树!

AVL 树 和 红黑树 都是在保持二叉搜索树基本性质的基础上,通过旋转和重新平衡等操作,确保树的高度保持在一个相对平衡的状态,从而保证了操作的时间复杂度始终为 O(logn)。它们的出现大大提高了二叉搜索树在实际应用中的性能和稳定性。

AVL树增加了以下特性:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1 / 0 / 1 )

在平衡因子超出要求就会进行旋转,旋转分为:右单旋 ,左单旋,左右双旋,右左双旋。

红黑树加入以下特性:

  • ⚠️每个节点要么是红色,要么是黑色。
  • ⚠️根节点必须是黑色的。
  • ⚠️如果一个节点是红色的,则它的两个子节点必须是黑色的。
  • ⚠️对于任意一个节点,从该节点到其所有后代叶子节点的简单路径上,必须包含相同数目的黑色节点。
  • ⚠️每个叶子节点都是黑色的。这里的叶子节点指的是为空的节点。

在不满足规则时也会进行旋转。但是旋转的频率比AVL树要少很多,红黑树是只是接近平衡,AVL树几乎就是平衡的!

map与set大多数情况是用来检索的工具,我们底层使用红黑树来完成map与set的封装。
进行封装之前,我们先来实现一个非常重要的东西:迭代器

2 红黑树的迭代器

迭代器的好处是可以方便遍历。如果想要给红黑树增加迭代器,需要考虑以前问题:

  1. 迭代器的框架如何实现,才能满足泛型编程的需求??
  2. STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,这里为了方便就给nullptr。
  3. operator++()与operator–()要如何实现?这里的++和–要满足中序遍历的顺序,就不能简单的进行指针的移动了!

接下来我们来逐个实现。
首先我们来搭建一下迭代器的框架

// 迭代器
//T 表示数据类型 Ref为引用 Ptr为指针
template<class T , class Ref , class Ptr>
struct _RBTreeIterator
{
	//为了方便调用,我们重命名一下
	typedef RBTreeNode<T> Node;
	typedef _RBTreeIterator<T, Ref, Ptr> Self;
	//内部是节点指针
	Node* _node;
	_RBTreeIterator(Node* node)
		:_node(node)
	{}
	//两种指向方式
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator&()
	{
		return &_node->_data;
	}
	bool operator!= (const Self& s)
	{
		return _node != s._node;
	}
};

接下来我们来实现++和–的操作。
中序遍历的顺序是先遍历左边再遍历当前节点最后是右子树。所以在跌迭代器指向当前节点的时候,说明当前节点的左子树已经遍历完了,如果++,就要去找右边的最左节点。如果没有右子树,说明该节点以下的部分已经遍历完了,接下来要去向上进行,找到是祖先左边的节点:

//迭代器的++ 中序遍历的顺序
Self& operator++()
{
	//首先,能访问到当前节点说明左子树的都已经访问过啦
	//所以就要分类讨论
	//如果右边有子树,就要去寻找右子树的最左节点
	if (_node->_right)
	{
		Node* cur = _node->_right;
		while (cur->_left)
		{
			cur = cur->_left;
		}
		_node = cur;
	}
	//如果右边没有子树了,说明该节点以下的子树都已遍历完,那么就要向上进行
	//找到祖先节点(注意祖先节点右边还没遍历)
	//此时也要进行分类讨论
	else
	{
		Node* cur = _node;
		Node* parent = _node->_parent;
		//_node == parent->_right
		//说明parent的节点已经访问过了
		while (parent && cur == parent->_right)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

–与++完全相反。

这样红黑树的迭代器就成功设置好了,我们的红黑树更加完美了!!!

实现了迭代器接下来我们就来实现map与set的封装

3 map与set的封装

3.1 红黑树的改进

我们先来看我们写的红黑树的节点代码:

// 节点结构体
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	color _col;
	RBTreeNode(pair<K, V> kv)
		:_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_kv(kv),
		_col(Red)
	{}

};

可以发现,这个节点的设置是写死的,里面的数据就设置为了pair<K , V>。如果我们想实现set的封装还要在写一份红黑树代码,因为set的节点数据是K 。这样就太不优雅了!
为了更好实现map与set的封装,我们来看STL源码里是如何实现的吧!
在这里插入图片描述

可以看到STL源码中使用了非常巧妙的模版来支持我们上层的map与set:

  1. 首先最底层的节点结构体只使用一个模版参数value,用来表明储存什么数据类型,上层的红黑树通过什么value就使用使用什么
  2. 红黑树这层主要使用Key Value KeyOfValue:
    • KEY:表示键值的类型,在Findj函数里有大用处(利用Key值来寻找是否存在)!!!
    • Value:表示储存的数据类型
    • KeyOfValue:这是一个仿函数,用来从Value取出Key值。
  3. map与set这层分别有K VK分别要提供给红黑树Key Value KeyOfValue
    • map:就传给红黑树<K , pair<K,V> ...>
    • set: 就传给红黑树<K , K ...>

这样实现了上层的mapset的模版参数并不一样,却可以使用同一个底层红黑树代码!!!十分巧妙!!!

我们按照源码改进我们的红黑树:

//-------------------------------------------
//---------------- 红黑树实现 -----------------
//-------------------------------------------
//-------- 适配map 与 set 的进阶版本 -----------
//-------------------------------------------
#include<utility>
enum color
{
	Black,
	Red
};
// 节点结构体
// T在这里是 pair<key , value>
template<class T>
struct RBTreeNode
{	
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	
	T _data;
	color _col;
	RBTreeNode(T data)
		:_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_data(data),
		_col(Red)
	{}

};

//适配map与set 的版本
// 迭代器
template<class T , class Ref , class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;
	_RBTreeIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator&()
	{
		return &_node->_data;
	}
	bool operator!= (const Self& s)
	{
		return _node != s._node;
	}
	//迭代器的++ 中序遍历的顺序
	Self& operator++()
	{
	}
	Self& operator--()
	{
	}
};
//K 为键值 T 为储存的结构(pair<K ,V>) KeyOfValue 是取出Key的方式
template<class K, class T , class KeyOfValue>
class RBTree
{
public:
	typedef _RBTreeIterator<T, T&, T*> Iterator;
	typedef RBTreeNode<T> Node;

	Iterator begin()
	{
		Node* cur = _root;
		while (cur->_left)
		{
			cur = cur->_left;
		}
		return Iterator(cur);
	}
	Iterator end()
	{
		return Iterator(nullptr);
	}

	//右单旋
	void RotateR(Node* parent)
	{
	}
	//左右双旋
	void RotateLR(Node* parent)
	{
	}
	//右左双旋
	void RotateRL(Node* parent)
	{
	}
	//------------------
	//返回需要比较的值
	KeyOfValue kot;
	//------------------
	//插入函数	
	bool Insert(T data)
	{
	}

private:
	void _IsBalance(Node* root , int num)
	{
	}
	bool Check(Node* root, int blackNum, const int refNum)
	{
	}

	void _InOrder(Node* cur)
	{
	}
	RBTreeNode<T>* _root = nullptr;
};

注意插入函数等里面的比较方式统一改成类似kot(data) < kot(node.data)的样子哦!!!因为map与set的取出key的方式不同!!!

3.2 map的封装

实现了红黑树的改进,接下来就简单了!
在上层操作我们只需要调用对应的底层代码,给予对应的模版参数就好了!!!

  1. map 要满足K V的模版参数的传入
  2. map 要实现一个仿函数用来取出Key
  3. map 类里要有一个底层红黑树类,传入对应的模版参数<K , pair<const K , V> , MapOfValue> (注意键值不可更改哦,所以使用pair<const K , V>)
  4. map 类里要实例化一个迭代器。只需要提供基本的begin()与end()接口(直接调用红黑树的就可以),剩下++ -- !+交给迭代器操作交给红黑树的迭代器。
//----------------------------------
//----------  MAP 的实现  -----------
//----------------------------------
#include"RBTree.h"
#include<utility>

//层层递进,
//map 上层要提供 key value 键值对
//相应的要改造红黑树的代码 使其满足泛型编程
template<class K , class V>
class map 
{
	struct MapOfValue
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapOfValue>::Iterator iterator;
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end();
	}
	bool Insert(pair<const K, V> kv)
	{
		return _t.Insert(kv);
	}
	void InOrder()
	{
		_t.InOrder();
	}
private:
	//底层是红黑树
	//需要提供对应的键值 储存结构 比较方式
	RBTree<K, pair<const K, V>, MapOfValue > _t;
};

这样就实现了map 的封装!!!

3.3 set 的封装

在上层操作我们只需要调用对应的底层代码,给予对应的模版参数就好了!!!

  1. set 要满足K 的模版参数的传入
  2. set 要实现一个仿函数用来取出Key
  3. set 类里要有一个底层红黑树类,传入对应的模版参数<K , const K , MapOfValue> (注意键值不可更改哦,所以使用 const K )
  4. set 类里要实例化一个迭代器。只需要提供基本的begin()与end()接口(直接调用红黑树的就可以),剩下++ -- !+交给迭代器操作交给红黑树的迭代器。
//----------------------------------
//----------  SET 的实现  -----------
//----------------------------------


#include"RBTree.h"
#include<utility>


//层层递进,
//set 上层要提供 key 键值
//相应的要改造红黑树的代码 使其满足泛型编程

template<class K>
class set
{
	struct SetOfValue
	{
		const K& operator()(const K& k)
		{
			return k;
		}
	};
public:
	typedef typename RBTree<K, const K, SetOfValue>::Iterator iterator;
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end();
	}
	bool Insert(K kv)
	{
		return _t.Insert(kv);
	}
	void InOrder()
	{
		_t.InOrder();
	}
private:
	//底层是红黑树
	//需要提供对应的键值 储存结构 比较方式
	RBTree<K, K, SetOfValue > _t;
};

这样就实现了set的封装!!!

4 总结

通过近一周的学习,我们终于将map和set从零建立起来了,这里不仅需要二叉搜索树的知识还需要AVL树和红黑树的使用!!!甚至还需要对于模版的更深理解!!!

我们写完了发现上层的map和set并没有使用多少代码,大部分是调用底层的代码,所以只有根基稳固才能走到更远!!!

map和set的封装是很值得回味的内容!!!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

看潮成长日程表用户手册(下)

看潮成长日程表用户手册&#xff08;下&#xff09; 四、基础设置1、系统用户设置2、成长学员设置3、自然期间定义4、时间表版本设置5、学员期间设置6、时间表时段设置7、年度假日维护 五、参数设置1、颜色参数设置2、逻辑参数设置3、数值参数设置4、字符参数设置5、列表输入项…

【二分查找 位运算】3145. 大数组元素的乘积

本文涉及知识点 二分查找算法合集 位运算、状态压缩、枚举子集汇总 LeetCode3145. 大数组元素的乘积 一个整数 x 的 强数组 指的是满足和为 x 的二的幂的最短有序数组。比方说&#xff0c;11 的强数组为 [1, 2, 8] 。 我们将每一个正整数 i &#xff08;即1&#xff0c;2&am…

c语言从入门到函数速成(完结篇)

哈喽&#xff0c;小伙伴们大家好呀&#xff0c;本篇文章是这个系列的完结篇&#xff0c;希望大家看完后能有所收获哦 首先能看到这里的同学&#xff0c;一定也是自觉性比较强的了&#xff0c;我会在文章末尾给大家发点小福利 那么&#xff0c;我们先来通过数学中的函数来引入一…

医学中脑机接口技术的未来

医学中脑机接口技术的未来 李升伟 编译 对非侵入性脑机接口&#xff08;而不是植入物&#xff09;日益增长的兴趣可能会提高患者的易使用性&#xff0c;但分辨率需要提高。 图片来源&#xff1a;Denis Pobytov / DigitalVision Vectors / Getty 全球范围内正在展开一场争夺利用…

云服务器购买之后到部署项目的流程

1.通过账号密码登录百度智能云控制台; 2.进入对应的服务器‘云服务器BBC’ 找到’实例‘即找到对应的服务器列表; 此时通过本地电脑 1.cmd命令提示符 PING 服务器公网地址不通&#xff1b; 2.通过本地电脑进行远程桌面连接不通 原因&#xff1a;没有关联安全组&#xff0c;或者…

测试基础02:软件开发流程及模型、敏捷开发

1、软件开发流程 包括&#xff1a;项目开发目的分析与确定、需求分析、设计、编程、软件测试、软件交付、验收和维护。 2、软件开发模型 2.1 定义 软件开发模型(Software Development Model)是软件开发全过程的框架&#xff0c;规定了软件开发过程中各项活动的基本步骤、任务…

InteractiveGraph图谱中vue项目中如何使用

InteractiveGraph图谱中vue项目中如何使用 一、下载js和css和字体二、vue2.0项目中引用三、grap组件 一、下载js和css和字体 //在这里面找 https://github.com/grapheco/InteractiveGraph/blob/master/dist/examples/example1.html二、vue2.0项目中引用 //main.js中全局引入$ …

驱动开发中引入私有数据的原因

系列文章目录 驱动开发中引入私有数据的原因 驱动开发中引入私有数据的原因 系列文章目录驱动开发中引入私有数据的原因 驱动开发中引入私有数据的原因 驱动开发中引入私有数据&#xff08;Private Data&#xff09;概念主要是为了解决以下几个关键问题&#xff1a; 1.多设备支…

Docker环境安装并使用Elasticsearch

1、拉取es docker pull elasticsearch:7.10.12、查看镜像 docker images3、启动es docker run -d --name esearch -p 9200:9200 -p 9300:9300 elasticsearch:7.10.14、如果启动ES时出现一下问题 Unable to find image docker.elastic.co/elasticsearch/elasticsearch:7.10.…

从Python代码到pip包:打包Python项目

大家好&#xff0c;在软件开发的世界中&#xff0c;共享和重用代码是至关重要的。Python社区为我们提供了丰富的资源&#xff0c;使得我们能够轻松地与他人分享我们的工作&#xff0c;并从他人的工作中受益。将代码打包成pip包&#xff08;Python包管理器&#xff09;是一种常见…

SpringCloudAlibaba:6.2RocketMQ的普通消息的使用

简介 普通消息也叫并发消息&#xff0c;是发送效率最高&#xff0c;使用最多的一种 依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSch…

Web上机:JSP+Servlet+JDBC的交互流程

目录 需求与设计 基础需求&#xff1a; 项目结构&#xff1a; 项目逻辑&#xff1a; 运行图示&#xff1a; 代码实现 Login.jsp InsertServlet SelectServlet Table.jsp user mysql表结构 Web开发技术迭代速度日新月异&#xff0c;对于技术的更新往往是基于底层一…

Kubernetes核心组件Ingress详解

1.1 Ingress介绍 Kubernetes 集群中&#xff0c;服务&#xff08;Service&#xff09;是一种抽象&#xff0c;它定义了一种访问 Pod 的方式&#xff0c;无论这些 Pod 如何变化&#xff0c;服务都保持不变。服务可以被映射到一个静态的 IP 地址&#xff08;ClusterIP&#xff09…

element ui 的el-input输入一个字后失去焦点,需重新点击输入框才能再次输入!

解决方案&#xff1a; 我是form表单嵌套表格&#xff0c;里面的el-input输入框&#xff0c;输入第一个值的时候会突然失去焦点&#xff0c;需要再次点击输入框才能正常输入&#xff0c;原因是table的key值&#xff0c;需要改成正常的index即可&#xff0c;如果你是循环的&…

精益生产培训公司:为企业量身定制的精益解决方案——张驰咨询

在当今竞争激烈的市场环境下&#xff0c;企业要想持续发展&#xff0c;就必须不断寻求转型升级的途径。精益生产作为一种高效的生产管理方式&#xff0c;已经成为众多企业追求的目标。而精益生产培训公司&#xff0c;正是帮助企业实现这一目标的重要力量。 一、精益生产培训的…

Kubernetes可视化界面之DashBoard

1.1 DashBoard Kubernetes Dashboard 是 Kubernetes 集群的一个开箱即用的 Web UI&#xff0c;提供了一种图形化的方式来管理和监视 Kubernetes 集群中的资源。它允许用户直接在浏览器中执行许多常见的 Kubernetes 管理任务&#xff0c;如部署应用、监控应用状态、执行故障排查…

WPF中CommandParameter用法

1. 界面样式 2. XAML中代码部分 <ButtonGrid.Row"0"Grid.Column"1"Command"{Binding BtnClick_Number}"CommandParameter"7"Content"7"Style"{StaticResource BtnStyle_Num}" /> <ButtonGrid.Row"…

产品经理-需求收集(二)

1. 什么是需求 指在一定的时期中&#xff0c;一定场景中&#xff0c;无论是心理上还是生理上的&#xff0c;用户有着某种“需要”&#xff0c;这种“需要”用户自己不一定知道的&#xff0c;有了这种“需要”后用户就有做某件事情的动机并促使达到其某种目的&#xff0c;这也就…

最新dofm飞行棋高阶版,分享情侣版飞行棋高级版和终极版

阿星今天要给大家带来一款甜蜜蜜的小游戏——情侣飞行棋。这不是普通的飞行棋&#xff0c;而是专为情侣设计的&#xff0c;让你们的感情在游戏中升温&#xff0c;擦出更多爱的火花。 准备好了吗&#xff1f;跟着阿星一起&#xff0c;咱们来看看这款软件的魅力所在&#xff01;…

设置虚拟机为静态IP

为什么需要设置静态IP&#xff1a;有时候我们在练习项目的时候&#xff0c;明明已经连接好了虚拟机的ip&#xff0c;某一天突然连接不上了&#xff0c;通过ifconfig命令查看发现虚拟机的ip发生了变化&#xff0c;导致之前做的内容都需要重新布置&#xff0c; 一、设置静态IP …