当前位置: 首页 > article >正文

C++封装红黑树实现mymap和myset和模拟实现详解

文章目录

  • map和set的封装
    • map和set的底层
  • map和set的模拟实现
    • insert
    • iterator实现的思路
    • operator++
    • operator- -

map和set的封装

介绍map和set的底层实现

map和set的底层

一份模版实例化出key的rb_tree和pair<k,v>的rb_tree
rb_tree的Key和Value不是我们之前传统意义上的key/value
在这里插入图片描述
迭代器也是一份模版实例化出两个不同的迭代器
所以说底层上红黑树是有两份的,一份是key的,一份是key/value的
在这里插入图片描述

  1. 通过泛型的思想实现出的模版,第二个参数不是写死的,第二个模版参数是Value决定的,Value可以是key或者是pair<const key, T>,这样既可以实现key的搜索场景,也可以实现key/value的搜索场景
  2. 要注意一下,源码里面模板参数是用T代表value,而内部写的value_type不是我们我们日常key/value场景中说的value,源码中的value_type反而是红黑树结点中存储的真实的数据的类型
  3. rb_tree的第二个模版参数已经控制了红黑树节点的存储的数据类型,为什么还要写第二个模版参数?
    set的两个模版参数都是一样的,都是key,insert的都是key,find和erase的也是key。但是对于map来说,insert的是pair对象,find/erase的是key。所以set为了兼容map就传了两个模版参数

map和set的模拟实现

pair的默认比较的是first,first小就小,first不小,比较second,second小就小。

insert

insert关键是要解决插入的值是Key还pair的问题
上层用仿函数实现一个类来解决下层比较的是key还是pair的问题,下层不知道T是什么,但是上层知道,如果比较的是key上层就传key,是pair就传pair

namespace wbc
{
	template<class K>
	class set
	{
		// 为了解决不知道下层T是key还是pair的逻辑
		struct SetKeyOfT
		{
			// 仿函数可以解决
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		bool insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree<K,K,SetKeyOfT> _t;
	};
}

namespace wbc
{
	template<class K, class V>
	class map
	{
		// 为了解决不知道下层T是key还是pair的逻辑
		struct MapKeyOfT
		{
			// 仿函数可以解决
			const pair<K, V>& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	private:
		RBTree<K, pair<K, V>,MapKeyOfT> _t;
	};
}

iterator实现的思路

  1. map和set的迭代器走的是中序遍历,begin()返回的是中序的第一个节点
  2. operator++核心是不看全局,只看局部,只关心当前局部的下一个节点是什么
  3. 中序访问的顺序:左子树,根,右子树。++,it当前节点已经访问完了,如果右不为空,访问右子树的最左节点。如果右为空,说明当前节点已经访问完了,子树也访问完了,就要访问该节点的祖先,并且要往上找。要找的是孩子是祖先的左边的那个祖先。
    如果孩子在父亲的右,说明父亲访问完了,父亲在爷爷的左,下一个就访问爷爷。
  4. set的iterator也不支持修改,我们把set的第二个模板参数改成const K即可, RBTree<K,const K, SetKeyOfT> _t;
  5. map的iterator不支持修改key但是可以修改value,我们把map的第二个模板参数pair的第一个参
    数改成const K即可, RBTree<K, pair<const K, V>,MapKeyOfT> _t;
    在这里插入图片描述

operator++

  1. 右不为空,中序的下一个节点就是右子树中的最左节点
  2. 右为空,分为两种情况:
    情况1:一直往上找直到找到父亲的左是当前节点,那么下一个节点就是这个父亲节点
    情况2:就是一直找,找不到父亲的左是当前节点,也就是当前节点一直是父亲的右,直到找到父亲是空都没有找到,那么这棵树就走完了
    在这里插入图片描述

在这里插入图片描述

Self operator++()
{
	if (_node->_right)
	{
		// 如果右不为空,中序下一个要访问的节点就是最左(最小)节点
		Node* min = _node->_right;
		while (min->_left)
		{
			min = min->_left;
		}
		_node = min;
	}
	else
	{
		// 如果右为空,祖先里面的孩子是父亲左的那个祖先
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_right)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

operator- -

访问节点

  1. 右子树,根,左子树。如果左树不为空,访问左树的最右节点(最大节点)。如果左树为空,说明这棵子树访问完了,如果该节点还是父亲的左,说明也访问完了,要找到节点是父亲的右,下一个访问的节点就是父亲。如果都不存在,下一个就要访问空节点了,说明这棵树访问完了。
  2. operator- - 和operator++正好反过来了
    在这里插入图片描述
// RBTree.h
Self operator--()
{
	if (_node == nullptr) // --end()
	{
		// --end(),找到整棵树的最右节点,中序的最后一个节点
		// 找最右节点
		Node* mostright = _root;
		// 空树(无节点)和找最右节点
		while (mostright && mostright->_right)
		{
			mostright = mostright->_right;
		}
		_node = mostright;
	}
	else if (_node->_left)
	{
		// 如果左不为空,下一访问的是左树中的最右节点
		Node* most = _node->_left;
		while (most->_right)
		{
			most = most->_right;
		}
		_node = most;
	}
	else
	{
		// 如果左为空,下一个访问的是孩子是父亲右的,那个祖先
		Node* cur = _node;
		Node* parent = _node->_parent;
		while (parent && cur == parent->_left)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}

	return *this;
}

// Myset.h
void Print(const set<int>& s)
{
	set<int>::const_iterator it = s.end();
	while (it != s.begin())
	{
		--it;
		cout << *it << " ";
	}
	cout << endl;
}

// test.cpp
int main()
{
	wbc::set<int> s;
	s.insert(5);
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	s.insert(6);

	s.Print(s);
}

在这里插入图片描述
库里面实现的是有哨兵位的头节点,我们实现是end()是nullptr,但是也可以实现–end()的功能,无非就是要_root,去找整棵树的最右节点(最后一个节点),end()是最后一个节点的下一个节点
在这里插入图片描述

在这里插入图片描述


http://www.kler.cn/a/521656.html

相关文章:

  • ARM嵌入式学习--第十天(UART)
  • 四.3 Redis 五大数据类型/结构的详细说明/详细使用( hash 哈希表数据类型详解和使用)
  • 【Linux笔记】Day4
  • deepseek-r1 本地部署
  • 不只是mini-react第二节:实现最简fiber
  • 解决.NET程序通过网盘传到Linux和macOS不能运行的问题
  • python学opencv|读取图像(四十六)使用cv2.bitwise_or()函数实现图像按位或运算
  • 窗户11 JH小记(xswl 随时失效版)
  • 【Python】 python实现我的世界(Minecraft)计算器(重制版)
  • 快速清除PPT所有幻灯片动画的三种方法
  • 使用大语言模型在表格化网络安全数据中进行高效异常检测
  • doris: ARRAY数据类型
  • JAVA学习-练习试用Java实现“向一个文本文件中写入一些内容。”
  • [STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器
  • 【Redis】缓存+分布式锁
  • Bambu A1 Mini 不工作,轴无法归零,进料面板检测到未知材料解决方案
  • 【深度学习|DenseNet-121】Densely Connected Convolutional Networks内部结构和参数设置
  • 开源项目Umami网站统计MySQL8.0版本Docker+Linux安装部署教程
  • 12 款开源OCR发 PDF 识别框架
  • c高级复习
  • 算法题(48):反转链表
  • C++中左值和右值的概念
  • 视频外绘技术总结:Be-Your-Outpainter、Follow-Your-Canvas、M3DDM
  • 提供ZYNQ,MPSOC,RFSOC生成BOOT.BIN的小工具
  • Oracle-Java JDBC 连接超时之后的认知纠正
  • 【Ubuntu 20.04】AX211网卡驱动安装