C++之模拟实现map和set

news2024/11/25 22:25:34

文章目录

  • 前言
  • 一、迭代器
    • 1.begin()和end()
    • 2.operator++()
  • 二、改造红黑树
  • 三、map的模拟实现
  • 四、set的模拟实现
  • 总结


前言

基于之前的红黑树和map、set的相关知识,本节我们使用红黑树来模拟实现STL中的map和set。


一、迭代器

使用迭代器可以方便我们对数据结构进行遍历,它使数据结构的底层实与用户的使用分离(封装了底层实现),因此我们要给红黑树增加一个迭代器。

1.begin()和end()

STL中明确规定,begin()和end()代表的是一段前闭后开的区间。我们知道对红黑树进行中序遍历可以得到一个有序的序列,因此begin()可以放置在红黑树的最小节点处(即,最左节点),end()应该放置在红黑树最大节点的下一个位置。但是最大结点的下一个位置是什么呢?这个位置是nullptr吗?答案是不能是nullptr,因为对end()位置进行–操作要能找到最后一个元素,如果设置为nullptr就找不到最后一个结点了。
我们可以给红黑树增加一个header结点,让最大结点的next指向它
在这里插入图片描述
但是我们只是对它进行模拟,理解它的底层原理即可,为了不要让代码太过复杂,我们本次模拟实现就不设定header结点,直接让end()为nullptr即可(不实现–)。

在这里插入图片描述

2.operator++()

找迭代器的下一个结点(它的值一定比当前结点的值大)

	template<class T, class Ref, class Ptr>
	struct __RBTreeIterator
	{
		typedef RBTreeNode<T> Node;
		typedef __RBTreeIterator<T, Ref, Ptr> Self;
		typedef __RBTreeIterator<T, T&, T*> iterator;

		Node* _node;
		__RBTreeIterator(Node* node)
			:_node(node)
		{}
		Ref operator*()
		{
			return _node->_data;
		}

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

		Self& operator++()
		{
			if (_node->_right)
			{
				Node* min = _node->_right;
				while (min->_left)
				{
					min = min->_left;
				}
				_node = min;
			}
			else
			{
				Node* cur = _node;
				Node* parent = _node->_parent;
				while (parent && cur == parent->_right)
				{
					cur = cur->_parent;
					parent = parent->_parent;
				}
				_node = parent;
			}
			return *this;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
		bool operator==(const Self& s)
		{
			return _node == s._node;
		}
	};

二、改造红黑树

namespace Jinger
{
	enum Colour//结点颜色
	{
		RED,
		BLACK,
	};

	template<class K,class V>
	struct RBTreeNode//红黑树结点
	{
		pair<K, V> _kv;
		typedef RBTreeNode<K, V> Node;
		RBTreeNode(const pair<K,V>& kv)
			:_kv(kv)
			, _parent(nullptr)
			, _left(nullptr)
			, _right(nullptr)
			, _colour(RED)
		{}
		Node* _parent;
		Node* _left;
		Node* _right;
		Colour _colour;
	};

	template<class K, class V>
	struct __RBTreeIterator//迭代器
	{
		typedef RBTreeNode<K,V> Node;
		typedef __RBTreeIterator<K,V> Self;
		Node* _node;
		__RBTreeIterator(Node* node)
			:_node(node)
		{}
		pair<K,V>& operator*()
		{
			return _node->_kv;
		}
		pair<K, V>* operator->()
		{
			return &_node->_kv;
		}
		Self& operator++()
		{
			if (_node->_right)
			{
				_node = _node->_right;
			}
			else
			{
				Node* parent = _node->_parent;
				if (_node == parent->_left)
				{
					_node = parent;
				}
				else
				{
					while (parent && _node == parent->_right)
					{
						_node = parent;
						parent = parent->_parent;
					}
					_node = parent;
				}
			}
			return *this;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};

	template<class K,class V>
	struct RBTree
	{
		typedef RBTreeNode<K,V> Node;
		typedef __RBTreeIterator<K,V> iterator;
		RBTree()
			:_root(nullptr)
		{}

		//左旋
		void RotateL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;
			parent->_right = SubRL;
			if (SubRL)
			{
				SubRL->_parent = parent;
			}
			Node* Grandpa = parent->_parent;
			SubR->_left = parent;
			parent->_parent = SubR;
			if (!Grandpa)
			{
				_root = SubR;
				SubR->_parent = nullptr;
			}
			else
			{
				if (parent == Grandpa->_left)
				{
					Grandpa->_left = SubR;
				}
				else
				{
					Grandpa->_right = SubR;
				}
			}
			SubR->_parent = Grandpa;
		}

		//右旋
		void RotateR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;
			Node* Grandpa = parent->_parent;
			parent->_parent = SubL;
			parent->_left = SubLR;
			if (SubLR)
			{
				SubLR->_parent = parent;
			}
			if (!Grandpa)
			{
				_root = SubL;
				SubL->_parent = nullptr;
			}
			else
			{
				if (parent == Grandpa->_left)
				{
					Grandpa->_left = SubL;
				}
				else
				{
					Grandpa->_right = SubL;
				}
			}
			SubL->_right = parent;
		}

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

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

		bool insert(const pair<K, V>& kv)
		{
			Node* newnode = new Node(kv);
			if (!_root)//空树
			{
				_root = newnode;
				_root->_colour = BLACK;
			}
			else
			{
				Node* parent = _root;
				Node* cur = _root;
				while (cur)
				{
					parent = cur;
					if (cur->_kv.first > kv.first)
					{
						cur = cur->_left;
					}
					else if (cur->_kv.first < kv.first)
					{
						cur = cur->_right;
					}
					else
					{
						return false;
					}
				}
				if (parent->_kv.first > kv.first)
				{
					parent->_left = newnode;
				}
				else
				{
					parent->_right = newnode;
				}
				newnode->_parent = parent;
				cur = newnode;
				parent = cur->_parent;
				if (parent->_colour == BLACK)//如果父亲的结点为黑色
				{
					return true;
				}
				while (parent && parent->_colour == RED)//如果parent为空,说明此时cur为根节点(如果调整到父节点为黑色就不需要再调整了)
				{
					Node* g = parent->_parent;//祖父
					Node* u = nullptr;//叔叔结点
					if (parent == g->_left)//如果父亲是祖父的左孩子,那么叔叔是祖父的右孩子
					{
						u = g->_right;
					}
					else
					{
						u = g->_left;
					}
					if (u && u->_colour == RED)
					{
						g->_colour = RED;
						u->_colour = parent->_colour = BLACK;
						cur = g;
						parent = cur->_parent;
					}
					else//叔叔不存在/叔叔的结点为黑色
					{
						//parent是g的右孩子,cur是parent的右孩子(左单旋)
						if (parent == g->_right && cur == parent->_right)
						{
							RotateL(g);
							parent->_colour = BLACK;
							g->_colour = RED;
						}
						//parent是g的左孩子,cur是parent的左孩子(右单旋)
						else if (parent == g->_left && cur == parent->_left)
						{
							RotateR(g);
							parent->_colour = BLACK;
							g->_colour = RED;
						}
						//parent是g的左孩子,cur是parent的右孩子(左右双旋)
						else if (parent == g->_left && cur == parent->_right)
						{
							RotateL(parent);
							RotateR(g);
							cur->_colour = BLACK;
							g->_colour = RED;
						}
						//parent是g的右孩子,cur是parent的左孩子(右左双旋)
						else if (parent == g->_right && cur == parent->_left)
						{
							RotateR(parent);
							RotateL(g);
							cur->_colour = BLACK;
							g->_colour = RED;
						}
						break;
					}
				}
			}
			_root->_colour = BLACK;//性质2要求根节点的颜色为黑色
			return true;
		}
		void inoder()
		{
			_inorder(_root);
		}
		void _inorder(Node* root)
		{
			if (!root) return;
			_inorder(root->_left);
			cout << root->_kv.first << ":" << root->_kv.second<< "  ";
			_inorder(root->_right);
		}
		bool _isbalance(Node* root, int count, const int& leftcount)
		{
			if (root == nullptr)
			{
				if (count != leftcount)
				{
					cout << "每条路径黑色结点数不相同,违反了性质4" << endl;
					return false;
				}
				else
				{
					return true;
				}
			}
			if (root->_colour == RED && root->_parent->_colour == RED)
			{
				cout << "父亲结点和cur都是红色,违反了性质3" << endl;
				return false;
			}
			if (root->_colour == BLACK)
			{
				count++;
			}
			bool left = _isbalance(root->_left, count, leftcount);
			bool right = _isbalance(root->_right, count, leftcount);
			return left && right;
		}
		bool isBalance()
		{
			if (_root == nullptr)
			{
				return true;
			}
			if (_root->_colour == RED)
			{
				cout << "根节点为红色,违反了性质2" << endl;
				return false;
			}
			int leftcount = 0;
			Node* cur = _root;
			while (cur->_left)
			{
				if (cur->_colour == BLACK)
				{
					leftcount++;
				}
				cur = cur->_left;
			}
			cout << leftcount << endl;
		 return	_isbalance(_root, 0, leftcount);
		}
	private:
		Node* _root;
	};
}

三、map的模拟实现

map的底层结构就是一个红黑树,因此在map中直接封装一个红黑树,然后包装一下它的借口即可。

namespace Jinger
{
	template<class K, class V>
	class Map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair< const K, V>, MapKeyOfT>::iterator iterator;
		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			return _t.insert(kv);
		}
		V& operator[](const K& k)
		{
			pair<iterator, bool> ret = _t.insert(make_pair(k, V()));
			return ret.first->second;
		}
		iterator& find(const K& k)
		{
			_t.find(k);
		}
	private:
		Jinger::RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

四、set的模拟实现

set的底层结构就是一个红黑树,因此在map中直接封装一个红黑树,然后包装一下它的借口即可。

namespace Jinger
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
		iterator begin() const
		{
			return _t.begin();
		}
		iterator end() const
		{
			return _t.end();
		}
		pair<iterator, bool> insert(const K& key)
		{
			pair<typename RBTree<K,K,SetKeyOfT>::iterator,bool>  ret = _t.insert(key);
			return pair<iterator, bool>(ret.first, ret.second);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

总结

以上就是今天要讲的内容,本文介绍了如何用红黑树模拟实现map和set的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

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

相关文章

windows安装wsl2

总的来说是按照这三个链接来的&#xff0c;也写了一个大体流程。 wsl对win版本有要求&#xff0c;可以 winr winver查看 原始参考链接&#xff1a; 1&#xff09;https://zhuanlan.zhihu.com/p/466001838 2&#xff09;https://cloud.tencent.com/developer/article/1986728 3&…

SpringCloud之Eureka、Ribbon及Nacos

SpringCloud之Eureka、Ribbon及Nacos 文章目录SpringCloud之Eureka、Ribbon及Nacos1. 单体架构和微服务架构2. SpringBoot、SpringCloud及SpringCloud Alibaba之间的版本对应关系2022.x 分支2021.x 分支2.2.x 分支组件版本关系3. Eureka3.1 Eureka-server注册中心3.2 Eureka-cl…

Elasticsearch:使用 ingest pipeline 来管理索引名称

在我之前的文章 “Elasticsearch&#xff1a;使用 pipelines 路由文档到想要的 Elasticsearch 索引中去” 我详述了如何使用已有的 date_index_name 处理器来把文档归类到所需要的和文档日期相关的的索引中去。比如&#xff0c;我们想把 2023 年 4 月的所有文档写入到 my-index…

【QT】MainWindow中如何为菜单栏或工具栏中的Menu或Action设置快捷键

目录1. 设置快捷键的两种方法1.1 在控件title属性的某个字母前加上&&#xff0c;&#xff08;Alt该字母&#xff09;作为快捷键1.2 使用 setShortcuts,&#xff08;Ctrl字母&#xff09;作为快捷键2. 为菜单栏中的 menu 设置快捷键2.1 测试2.2 代码3. 为菜单栏或工具栏中的…

百兆以太网使用的电信号编码分析

以太网是一种计算机局域网的组网技术。在IEEE制定的IEEE 802.3标准给出了以太网的技术标准。它规定了包括物理层的连线、电信号和介质访问层协议的内容。以太网是当前应用普遍的局域网技术。它很大程度上取代了其他局域网标准&#xff0c;如令牌环、FDDI和ARCNET。 我们常见的网…

Netty通信技术进阶一

Netty通信技术进阶1. 概念2. 线程同步、异步3. 其他通信技术对比4. Netty中的Reactor实现5. Pipeline 和 Handler5.1 ChannelHandler 分类6. 入站事件传播7.inbound/outbound 加载顺序和执行顺序8. 出站事件传播9. Code example9.1 编写服务端9.2 编写客户端10. 核心组件10.1 B…

虚拟直播需要哪些设备?如何搭建虚拟直播团队?

虚拟直播不止是新兴的娱乐途径 &#xff0c;还是新的商业模式 。虚拟直播的出现&#xff0c;是互联网娱乐趋势的变化&#xff0c;带来了更加丰富多彩的娱乐形式&#xff0c;同时也优化了传统直播模式下的人力物力成本&#xff0c;使直播行业更加效率及智能。 科技不断发展&…

JDBC(数据库连接)

MYSQL 数据库总结&#xff1a; http://t.csdn.cn/Ka9Vm JDBC是使用Java语言操作关系型数据库的一套API。 将mysql-connector-j-8.0.32jar复制粘贴到一个新建的目录里&#xff0c;然后右键mysql-connector-j-8.0.32jar&#xff0c;添加为库。 DriverManager 一个工厂类&…

2023易派客工业品展览会在苏州开幕

展厅面积达5.3万平方米&#xff0c;500多家重要工业领军企业参展&#xff0c;20组企业签署购销意向协议&#xff0c;签约金额超82亿元 ​ 4月13日&#xff0c;“2023易派客工业品展览会”在苏州国际博览中心开幕。展会以“绿色智造融通赋能”为主题&#xff0c;500多家重要工业…

CART分类树算法

1. CART分类树算法的最优特征选择方法 我们知道&#xff0c;在ID3算法中我们使用了信息增益来选择特征&#xff0c;信息增益大的优先选择。在C4.5算法中&#xff0c;采用了信息增益比来选择特征&#xff0c;以减少信息增益容易选择特征值多的特征的问题。但是无论是ID3还是C4.…

FreeRTOS中临界段的保护(笔记)

目录临界段的定义Cortex-M内核快速关开关中断的指令关中断开中断进入临界段的宏退出临界段的宏进入临界段&#xff0c;不带中断保护&#xff0c; 不能嵌套进入临界段&#xff0c;带中断保护版本&#xff0c;可以嵌套退出临界段&#xff0c;不带中断保护版本&#xff0c;不能嵌套…

【数据结构与算法】堆的实现(附源码)

目录 一.堆的概念及结构 二.接口实现 A.初始化 Heapinit 销毁 Heapdestroy B.插入 Heappush 向上调整 AdjustUp 1.Heappush 2.AdjustUp C.删除 Heappop 向下调整 AdjustDown D.堆的判空 Heapempty 堆顶数据 Heaptop 堆的大小 Heapsize 三.源码 Heap.h He…

Windows通过RDP异地远程桌面Ubuntu【内网穿透】

文章目录前言1. ubuntu安装XRDP2.局域网测试连接3. Ubuntu安装cpolar内网穿透4.cpolar公网地址测试访问5.固定域名公网地址前言 XRDP是一种开源工具&#xff0c;它允许用户通过Windows RDP访问Linux远程桌面。 除了Windows RDP外&#xff0c;xrdp工具还接受来自其他RDP客户端(…

文心一格,百度AI作画产品

文章目录AIGC什么是AI作画&#xff1f;Prompt文心一格使用方法注册账号使用AI绘图AIGC的未来发展结语AIGC AIGC&#xff08;AI Generated Content&#xff09;是指利用人工智能生成内容。是利用人工智能来生成你所需要的内容&#xff0c;GC的意思是创作内容。与之相对应的概念中…

ElasticSearch索引文档写入和近实时搜索

一、基本概念 1.Segments In Lucene 众所周知&#xff0c;ElasticSearch存储的基本单元Shard&#xff0c;ES中一个Index可能分为多个Shard&#xff0c;事实上每个Shard都是一个Lucence的Index&#xff0c;并且每个Lucene Index由多个Segment组成&#xff0c;每个Segment事实上…

【JS运算】分组求和/平均值(reduce函数)

对于数组求和的问题&#xff0c;使用reduce函数能够最快的解决 如果你还不会reduce函数&#xff0c;可以看这一篇&#xff1a; reduce函数的使用 思路 reduce函数对相同group的值进行迭代求和 将分组的总和除以组里的个数得到平均值&#xff0c;然后存储起来 Sum函数&#x…

Linux ubuntu更新meson版本

问题描述 在对项目源码用meson进行编译时&#xff0c;可能出现以下错误 meson.build:1:0: ERROR: Meson version is 0.45.1 but project requires > 0.58.0. 或者 meson_options.txt:1:0: ERROR: Unknown type feature. 等等&#xff0c;原因是meson版本跟设置的不适配。 …

Linux 学习总结(92)—— Linux 高效率使用技巧

1、跳转目录优雅顺滑 1.1 bd 命令 快速回到 Bash 中的特定父目录&#xff0c;而不是多余地键入 cd ../../..。如果在此路径中/home/radia/work/python/tkinter/one/two并且想快速转到目录 python&#xff0c;只需键入: bd python或者仅输入目录的前几个字母&#xff0c;如匹…

锁子甲 bulid+sim

链接: youtube 分析&#xff1a;洒一堆点——copy 模型——点和模型符合一定规律 点和点的距离符合上述图中的关系 &#xff08;横纵&#xff09; 横向 但是我们要横向10个点够了&#xff1a; 用modulo 除余 纵向 这里用除法向上取整 /10 eg &#xff1a; 0-9 得0 10-19 得1…

【逗号你真的懂吗?】C++与JAVA中逗号的区别

文章目录一、先上结论二、C中的逗号逗号运算符和逗号表达式三、JAVA中的逗号四、实战验证情况一&#xff1a;在定义&#xff08;或声明&#xff09;变量时利用逗号CJAVA情况二&#xff1a;在for循环条件中使用逗号CJAVA情况三&#xff1a;在函数形参参数列表中使用逗号CJAVA情况…