【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现

news2024/11/18 10:21:42



快乐的流畅:个人主页


个人专栏:《C语言》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、红黑树(改造版)
    • 1.1 结点
    • 1.2 迭代器
      • 1.2.1 operator++
      • 1.2.2 operator- -
    • 1.3 本体
      • 1.3.1 begin和end
      • 1.3.2 Find
      • 1.3.3 Insert
  • 二、set
    • 2.1 成员变量与仿函数
    • 2.2 begin和end
    • 2.3 find
    • 2.4 insert
  • 三、map
    • 3.1 成员变量与仿函数
    • 3.2 begin和end
    • 3.3 find
    • 3.4 insert
    • 3.5 operator[ ]

引言

STL库中的set类和map类,其底层原理都是通过红黑树来实现的。尽管set和map可以各自实现一棵红黑树,但是为了提高代码的复用率,STL库中将红黑树进行了一定的改造,实现以相同的底层实现不同的容器

一、红黑树(改造版)

1.1 结点

enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _col;

	RBTreeNode(const T& data)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

细节:

  • 数据类型改为T,因为要同时适用set(存储键值)和map(存储键值对)

1.2 迭代器

改造后的红黑树,最重要的功能之一就是支持双向迭代器,以最左结点为首,以最右结点为尾。

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, T&, T*> Iterator;
	typedef RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

	RBTreeIterator(Node* node)
		: _node(node)
	{}

	RBTreeIterator(const Iterator& it)
		: _node(it._node)
	{}

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

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

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

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

细节:

  1. 一些基本的迭代器范式操作已经给出,重点的++与- -操作后面详细实现
  2. 迭代器的拷贝构造函数有两个用途:
    • 以普通迭代器拷贝出普通迭代器(普通迭代器调用时)
    • 以普通迭代器拷贝出const迭代器(const迭代器调用时)

1.2.1 operator++

Self operator++()
{
	if (_node->_right)//右不为空,找右子树的最左结点
	{
		Node* subLeft = _node->_right;
		while (subLeft->_left)
		{
			subLeft = subLeft->_left;
		}
		_node = subLeft;
	}
	else//右为空,向上找孩子是父亲左的那个父亲
	{
		Node* parent = _node->_parent;
		Node* cur = _node;
		while (parent && parent->_right == cur)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

Self operator++(int)
{
	Node* tmp = _node;
	++*this;
	return tmp;
}

细节:

  1. 前置++的思路:
    • 右不为空,找右子树的最左结点
    • 右为空,向上找孩子是父亲左的那个父亲
  2. 后置++:复用前置++,返回临时对象

1.2.2 operator- -

Self operator--()
{
	if (_node->_left)//左不为空,找左子树的最右结点
	{
		Node* subRight = _node->_left;
		while (subRight->_right)
		{
			subRight = subRight->_right;
		}
		_node = subRight;
	}
	else//左为空,向上找孩子是父亲右的那个父亲
	{
		Node* parent = _node->_parent;
		Node* cur = _node;
		while (parent && parent->_left == cur)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

Self operator--(int)
{
	Node* tmp = _node;
	--*this;
	return tmp;
}

细节:

  1. 前置- -的思路:
    • 左不为空,找左子树的最右结点
    • 左为空,向上找孩子是父亲右的那个父亲
  2. 后置- -:复用前置- -,返回临时对象

1.3 本体

template<class K, class T, class KeyOfT>
class RBTree
{
protected:
	typedef RBTreeNode<T> Node;
public:
protected:
	Node* _root = nullptr;
};

细节:

  1. 模板参数第一个为K,键值类型(比较时会用到)
  2. 模板参数第二个为T,同时适用set(存储键值)和map(存储键值对)
  3. 模板参数第三个为KeyOfT(仿函数类型),用于获取不同数据T的键值key来进行比较

1.3.1 begin和end

typedef RBTreeIterator<T, T&, T*> iterator;
typedef RBTreeIterator<T, const T&, const T*> const_iterator;

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

const_iterator begin() const
{
	Node* cur = _root;
	while (cur->_left)
	{
		cur = cur->_left;
	}
	return const_iterator(cur);
}

iterator end()
{
	return iterator(nullptr);
}

const_iterator end() const
{
	return const_iterator(nullptr);
}

细节:begin返回最左节点的迭代器,end返回空迭代器

1.3.2 Find

iterator Find(const K& key)
{
	if (_root == nullptr)
	{
		return iterator(nullptr);
	}

	KeyOfT kot;
	Node* cur = _root;
	while (cur)
	{
		if (kot(cur->_data) < key)
		{
			cur = cur->_right;
		}
		else if (kot(cur->_data) > key)
		{
			cur = cur->_left;
		}
		else
		{
			return iterator(cur);
		}
	}

	return iterator(nullptr);
}

细节:

  1. 返回迭代器
  2. 运用仿函数进行键值比较

1.3.3 Insert

pair<iterator, bool> Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root), true);
	}

	KeyOfT kot;
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (kot(cur->_data) < kot(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(cur->_data) > kot(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return make_pair(iterator(cur), false);
		}
	}

	Node* newnode = new Node(data);
	cur = newnode;
	if (kot(parent->_data) < kot(data))
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	while (parent && parent->_col == RED)
	{
		Node* grandparent = parent->_parent;
		if (grandparent->_right == parent)//uncle在左,parent在右
		{
			Node* uncle = grandparent->_left;
			if (uncle && uncle->_col == RED)//uncle为红,变色+向上调整
			{
				parent->_col = uncle->_col = BLACK;
				grandparent->_col = RED;

				cur = grandparent;
				parent = cur->_parent;
			}
			else//uncle为空或为黑,变色+旋转
			{
				if (parent->_right == cur)//左单旋
				{
					RotateL(grandparent);
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				else//右左旋
				{
					RotateR(parent);
					RotateL(grandparent);
					cur->_col = BLACK;
					grandparent->_col = RED;
				}
			}
		}
		else//parent在左,uncle在右
		{
			Node* uncle = grandparent->_right;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandparent->_col = RED;

				cur = grandparent;
				parent = cur->_parent;
			}
			else
			{
				if (parent->_left == cur)//右单旋
				{
					RotateR(grandparent);
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				else//左右旋
				{
					RotateL(parent);
					RotateR(grandparent);
					cur->_col = BLACK;
					grandparent->_col = RED;
				}
			}
		}
	}
	_root->_col = BLACK;

	return make_pair(iterator(newnode), true);
}

细节:

  1. 返回pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
  2. 运用仿函数进行键值比较

二、set

2.1 成员变量与仿函数

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
protected:
	RBTree<K, K, SetKeyOfT> _t;
};

细节:

  1. set类仿函数,直接返回参数key
  2. 成员变量的第二个模板参数为K,第三个模板参数为SetKeyOfT

2.2 begin和end

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

iterator begin()
{
	return _t.begin();
}

const_iterator begin() const
{
	return _t.begin();
}

iterator end()
{
	return _t.end();
}

const_iterator end() const
{
	return _t.end();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. set中存储的键值key均不允许修改,所以其普通迭代器和const迭代器均为红黑树的const迭代器
  3. 由于set的普通迭代器也是红黑树的const迭代器,调用普通begin()时,便有从普通迭代器到const迭代器的转换,此时之前写的拷贝构造(以普通迭代器拷贝构造const迭代器)便派上用场了。

2.3 find

iterator find(const K& key)
{
	return _t.Find(key);
}

2.4 insert

pair<iterator, bool> insert(const K& key)
{
	return _t.Insert(key);
}

细节:

  1. 插入参数类型为K(键值)
  2. 此时也有从普通迭代器到const迭代器的转换

三、map

3.1 成员变量与仿函数

template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
protected:
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

细节:

  1. map类仿函数,返回参数pair的first
  2. 成员变量的第二个模板参数为pair,第三个模板参数为MapKeyOfT

3.2 begin和end

typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

iterator begin()
{
	return _t.begin();
}

const_iterator begin() const
{
	return _t.begin();
}

iterator end()
{
	return _t.end();
}

const_iterator end() const
{
	return _t.end();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. map同样不允许修改key,故加上const修饰,但是允许修改存储的value,所以普通和const迭代器一一对应

此时,可能有人会问,那刚刚set不允许修改key,为什么不也直接用const修饰呢?请看以下这段代码:

typedef RBTreeIterator<T, const T&, const T*> const_iterator;

如果变成第二个模板参数T传入const K,那么就会形成两个连续的const,这是不被允许的。所以才想了其他方法来补救。

3.3 find

iterator find(const K& key)
{
	return _t.Find(key);
}

3.4 insert

pair<iterator, bool> insert(const pair<const K, V>& kv)
{
	return _t.Insert(kv);
}

细节:插入参数类型为pair(键值对)

3.5 operator[ ]

map最好用的重载运算符[ ],我们肯定也要实现,平常插入和修改使用[ ]更加方便。

V& operator[](const K& key)
{
	pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
	return ret.first->second;
}

细节:

  1. 插入成功便是插入,插入失败便是查找+修改
  2. 返回value的引用,可以直接插入或修改

真诚点赞,手有余香

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

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

相关文章

Stable Diffusion XL之使用Stable Diffusion XL训练自己的AI绘画模型

文章目录 一 SDXL训练基本步骤二 从0到1上手使用Stable Diffusion XL训练自己的AI绘画模型2.1 配置训练环境与训练文件2.2 SDXL训练数据集制作(1) 数据筛选与清洗(2) 使用BLIP自动标注caption(3) 使用Waifu Diffusion 1.4自动标注tag(4) 补充标注特殊tag(5) 训练数据预处理(标注…

什么是物联网监控平台?部署物联网平台有什么作用?

随着物联网技术的飞速发展&#xff0c;越来越多的企业开始关注并投入到这一领域。物联网监控平台作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为企业数字化转型的关键组件。今天&#xff0c;我们将深入探讨物联网监控平台的定义、部署物联网平台的作用&#xff0c;以…

企微获客助手功能,行为触发如何实现回传的?

获客助手&#xff0c;这个听起来就相当酷炫的名字&#xff0c;它实际上是一个帮助企业将推广流量快速导入企业微信的神器。通过它&#xff0c;企业可以吸引越来越多的用户加为好友&#xff0c;从而建立起更紧密的客户关系。但是&#xff0c;如何进一步提升导入企业微信的流量质…

Docker数据卷挂载

一、容器与数据耦合的问题: 数据卷是虚拟的&#xff0c;不真实存在的&#xff0c;它指向文件中的文件夹 &#xff0c;属主机文件系统通过数据卷和容器数据进行联系&#xff0c;你改变我也改变。 解决办法&#xff1a; 对宿主机文件系统内的文件进行修改&#xff0c;会立刻反应…

信号处理--情绪分类数据集DEAP预处理(python版)

关于 DEAP数据集是一个常用的情绪分类公共数据&#xff0c;在日常研究中经常被使用到。如何合理地预处理DEAP数据集&#xff0c;对于后端任务的成功与否&#xff0c;非常重要。本文主要介绍DEAP数据集的预处理流程。 工具 图片来源&#xff1a;DEAP: A Dataset for Emotion A…

docker关闭全部运行容器命令是什么?

环境&#xff1a; docker v22.1 问题描述&#xff1a; docker关闭全部运行容器命令是什么&#xff1f; 解决方案&#xff1a; 要关闭所有正在运行的Docker容器&#xff0c;可以使用如下命令&#xff1a; docker stop $(docker ps -a -q)这条命令首先执行 docker ps -a -q…

WiFiSpoof for Mac wifi地址修改工具

WiFiSpoof for Mac&#xff0c;一款专为Mac用户打造的网络隐私守护神器&#xff0c;让您在畅游互联网的同时&#xff0c;轻松保护个人信息安全。 软件下载&#xff1a;WiFiSpoof for Mac下载 在这个信息爆炸的时代&#xff0c;网络安全问题日益凸显。WiFiSpoof通过伪装MAC地址&…

【QQ版】QQ群短剧机器人源码 全网短剧机器人插件

内容目录 一、详细介绍二、效果展示2.效果图展示 三、学习资料下载 一、详细介绍 QQ版本可以兼容两个框架&#xff08;HTQQ&#xff0c;MYQQ这两个的vip版也可以使用) 支持私聊与群聊&#xff0c;命令是 搜剧影视关键词 如果无法搜索到影视资源&#xff0c;请使用下方命令&…

网络链路层之(2)PPP协议

网络链路层之(2)PPP协议 Author: Once Day Date: 2024年3月27日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSDN…

[TS面试]keyof和typeof关键字作用?

keyof和typeof关键字作用? keyof 索引类型查询操作符, 获取索引类型属性名, 构成联合类型 typeof 获取一个变量或者对象的类型 let str:string ‘hello’ // typeof str >:string keyof typeof 获取 联合类型的key enum A{A, B, C }type unionType keyof typeof A; /…

JDK8的下载安装与环境变量配置教程

前言 官网下载&#xff1a;Java Archive Downloads - Java SE 8u211 and later 现在应该没人用32位的系统了吧&#xff0c;直接下载Windows x64 Installer jdk-8u391-windows-x64.exe 一、安装JDK 1. 打开jdk-8u391-windows-x64.exe 2. 直接下一步 3. 这个地方不要动他&…

python opencv稍基础初学

傅里叶变换 傅里叶变换f​​​​​傅里叶分析之掐死教程&#xff08;完整版&#xff09;更新于2014.06.06 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/19763358 相当nice 傅里叶变换的作用 高频&#xff1a;变化剧烈的灰度分量&#xff0c;例如边界 低频&#xff1a;变…

P8649 [蓝桥杯 2017 省 B] k 倍区间:做题笔记

目录 思路 代码思路 代码 推荐 P8649 [蓝桥杯 2017 省 B] k 倍区间 思路 额嗯&#xff0c;这道题我刚上来是想到了前缀和&#xff0c;但是还要判断每个子序列&#xff0c;我就两层for嵌套&#xff0c;暴力解了题。就是我知道暴力肯定过不了但是写不出来其他的[留下了苦…

[linux] AttributeError: module ‘transformer_engine‘ has no attribute ‘pytorch‘

[BUG] AttributeError: module transformer_engine has no attribute pytorch Issue #696 NVIDIA/Megatron-LM GitHub 其中这个答案并没有解决我的问题&#xff1a; import flash_attn_2_cuda as flash_attn_cuda Traceback (most recent call last): File "<stdi…

golang 在多线程中避免 CPU 指令重排

发布日期&#xff1a;2024-03-26 16:29:39 起因 golang 的发明初衷便是多线程&#xff0c;是一门专门用于多线程高并发的编程语言。其独创的 GMP 模型在多线程的开发上提供了很大的便利。 现代计算机基本上都是多核 CPU 的结构。CPU 在进行指令运行的时候&#xff0c;为了提高…

基于大语言模型的云故障根因分析|顶会EuroSys24论文

*马明华 微软主管研究员 2021年CCF国际AIOps挑战赛程序委员会主席&#xff08;第四届&#xff09; 2021年博士毕业于清华大学&#xff0c;2020年在佐治亚理工学院做访问学者。主要研究方向是智能运维&#xff08;AIOps&#xff09;、软件可靠性。近年来在ICSE、FSE、ATC、EuroS…

【系统架构师】-第13章-层次式架构设计

层次式体系结构设计是将系统组成一个层次结构&#xff0c;每一层 为上层服务 &#xff0c;并作为下层客户。 在一些层次系统中&#xff0c;除了一些精心挑选的输出函数外&#xff0c; 内部的层接口只对相邻的层可见 。 连接件通过决定层间如何交互的协议来定义&#xff0c;拓扑…

免费SSL证书和付费SSL证书的区别点

背景&#xff1a; 在了解免费SSL证书和付费SSL证书的区别之前&#xff0c;先带大家了解一下SSL证书的概念和作用。 SSL证书的概念&#xff1a; SSL证书就是基于http超文本传输协议的延伸&#xff0c;在http访问的基础上增加了一个文本传输加密的协议&#xff0c;由于http是明…

让手机平板成为AI开发利器:AidLux

想ssh登录自己的手机吗&#xff1f; 想在手机上自由的安装lynx、python、vscode、jupyter甚至飞桨PaddlePaddle、Tensorflow、Pytorch和昇思Mindspore吗&#xff1f; 那么看这里....装上AidLux&#xff0c;以上全都有&#xff01; AidLux是一个综合的AI开发平台&#xff0c;…

Java 学习和实践笔记(49):用javabean和一维数组的方式来存储表格数据

还是存储下面这个表格的数据&#xff0c;但使用另一种方法来做。 用javabean和一维数组的方法来做&#xff0c;示例代码如下&#xff1a; /*先创建一个类&#xff0c;其实就是创建好一个只有各属性列的空表格*/ class Employees {private int id;private String name;private …