【C++】红黑树模拟实现STL库中的map与set

news2024/11/13 15:25:58

目录

改造红黑树

红黑树的迭代器

map的模拟实现 

 set的模拟实现


在上一篇博客中,我们实现了红黑树,但是红黑树节点中的值是pair<K,V> _kv形式,这种红黑树适用于map的底层,那么如果我们想要红黑树节点中的值是key的形式(适用于set的底层),我们该怎么搞呢?难道我们只把红黑树节点的类型改一下,其它基本保持不变,再来实现一遍吗?这未免太繁琐了!

我们先来看一看stl库中如何实现map和set的:

它们貌似用的同样的红黑树实现的,它们的区别在于红黑树的第二个模版参数,一个是key,一个是kv的pair,我们再来看看stl库里怎么实现的rb_tree,

在stl中,红黑树在实现时,并没有直接确定是key还是kv的pair,而是由模版的第二个参数Value决定,本质上这是一个高级的泛型编程,不得不说,大佬写的模版比我们技高一筹!

改造红黑树

为了达到和stl库中一样的效果,我们需要对上节的红黑树进行改造,即对红黑树中值的类型不指定,使用模版:

//写成一个模版,T有可能是key,有可能是pair
template<class T>
struct RBTreeNode
{
	
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Color _color;
	T _data;
	RBTreeNode(const T& data)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _color(RED)
	{}
};

 调用示意图如下:

肯定有人会有疑问,RBTree中第一个模版参数class K好像没用到,其实在Find函数中可以用到,Find函数肯定要通过Key来查找(Node* Find(const K& key))。

当我们对红黑树改造后,在Insert函数中,会遇到这样的问题:我们需要对待插入的数据节点值进行比较以便插入,但是,待比较数据和节点值有可能是key,也有可能是pair,为了解决这样的问题,在class set和class map中分别增加一个内部类,

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
    ...
};

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

在class RBTree上加模版参数class KeyOfT,然后在类中定义KeyOfT的对象,

template<class K,class T,class KeyOfT>
class RBTree
bool Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root->_color = BLACK;
	return true;
	}
	KeyOfT kot;
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		//K
		//pair<K,V>
		//kot对象,是用来取T类型的data对象中的key
        //如果data是pair,则通过kot仿函数去调用上面的内部类,从而得到pair中的key
		if (kot(cur->_data) > kot(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else if(kot(cur->_data) < kot(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
  ...
}

红黑树的迭代器

我们先来看stl里是怎么实现的:

迭代器里就是一个节点指针去封装,

然后实现operator++、operator->等的重载,我们自己来实现一下红黑树的迭代器:

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* leftMin = _node->_right;
			while (leftMin && leftMin->_left)
			{
				leftMin = leftMin->_left;
			}
			_node = leftMin;
		}
		else
		{
			//下一个,孩子等于父亲左的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}
};

上面中最复杂的是operator++的实现,我们分情况讨论一下:

        1.当前结点的右子树不为空,中序下一个访问的节点,是右子树的最左节点。

        2.右为空,下一个访问,倒着在祖先里找,孩子是父亲左的祖先

在红黑树的实现中,我们typedef一下:

typedef __RBTreeIterator<T, T&, T*> Iterator;

在红黑树中,我们要实现begin()、end()等接口:

//begin是最左节点
Iterator begin()
{
	Node* leftMin = _root;
	while (leftMin && leftMin->_left)
	{
		leftMin = leftMin->_left;
	}
	return Iterator(leftMin);
}
//end是最后一个节点的下一个,也就是nullptr
Iterator end()
{
	return Iterator(nullptr);
}

map的模拟实现 

如果我们想要实现map或set的拷贝构造,我们知道默认的拷贝构造,对于内置类型完成值拷贝,针对自定义类型会调用它的拷贝构造,这样会造成浅拷贝,所以,我们需要自己写拷贝构造函数:

//强制生成默认构造函数
RBTree() = default;

RBTree(const RBTree<K, T, KeyOfT>& t)
{
	_root = Copy(t._root);
}
private:
	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		//构造新节点
		Node* newroot = new Node(root->_data);
		//改变新节点的颜色
		newroot->_color = root->_color;
		//把递归Copy得到的子树链接到newroot的左侧
		newroot->_left = Copy(root->_left);
		//让newroot->_left向上链接到newroot
		if (newroot->_left)
			newroot->_left->_parent = newroot;
		//把递归Copy得到的子树链接到newroot的右侧
		newroot->_right = Copy(root->_right);
		//让newroot->_left向上链接到newroot
		if (newroot->_right)
			newroot->_right->_parent = newroot;

		return newroot;
	}

除此之外,我们还要写析构函数:

~RBTree()
{
	Destroy(_root);
	_root = nullptr;
}
void Destroy(Node* root)
{
	//走一个后序遍历
	if (root == nullptr)
		return;
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
	root = nullptr;
}

在map中我们还要实现operator[]重载,我们之前学过,库里的[]重载是调用insert函数,operator[]返回值是key所在的迭代器,

因此,我们主要就是调整insert函数,insert函数的返回值类型是pair<Iterator,bool>,修改insert函数的返回值:

pair<Iterator,bool> Insert(const T& data)

实现operator[]:

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

完整的map模拟实现代码如下:

namespace ghs
{
	template<class K,class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		//此时RBtree还没有实例化,需要加typename告诉编译器,等实例化了再去确认
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, MapKeyOfT>::ConstIterator const_iterator;

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

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

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

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

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
			//ret.first.operator->()->second
			return ret.first->second;
		}
	private:
		//由于RBTree的第二个模版参数是用来确定节点的类型,map要求pair中给的key不能变,但是val可以变
		//因此,pair<const K, V>中第一个参数加const,第二个参数不加const
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

}

 set的模拟实现

namespace ghs
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:

		//没有实例化的类,去取,需要加typename
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;
		const_iterator begin()const
		{
			return _t.begin();
		}

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

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

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

		iterator find(const K& key)
		{
			return _t.Find(key);
		}
	private:
		//由于RBTree的第二个模版参数是用来确定节点的类型,set的key是不可以改变的,所以要加const
		RBTree<K, const K, SetKeyOfT> _t;
	};
}

在学完vector、list、map、set的迭代器iterator后,我们可以进一步理解一下封装:它们底层的operator++、operator*、operator->完全不一致,但是对外提供的方式是一致的(统一的迭代器行为)。

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

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

相关文章

神经网络处理器模拟器的一点思考

一 神经网络处理器 通常基于FPGA的神经网络处理器进行部署某种网络&#xff0c;考虑的因素较多&#xff0c;具体包括网络模型的不同&#xff0c;涵盖不同的算子、激活函数、调度策略等等&#xff1b;具体硬件实现&#xff0c;涉及神经网络处理器并行度、硬件资源消耗&#xff0…

【C++高阶】哈希函数底层原理探索:从算法设计到实现优化

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;模拟实现 map与set &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀哈希 &#x1f4da;1. unord…

MinerU、Magic-PDF、Magic-Doc

文章目录 一、关于 MinerU二、Magic-PDF1、简介2、项目全景3、流程图4、子模块仓库 三、Magic-PDF 上手指南1、配置要求2、安装配置1. 安装Magic-PDF2. 下载模型权重文件3. 拷贝配置文件并进行配置4. 使用CUDA或MPS加速推理CUDAMPS 3、使用说明1) 通过命令行使用直接使用更多用…

鑫创SSS1700USB音频桥芯片USB转IIS芯片

鑫创SSS1700支持IIC初始外部编&#xff08;EEPROM选项),两线串行总线&#xff08;I2C总线&#xff09;用于外部MCU控制整个EEPROM空间可以通过MCU访问用于主机控制同步的USB HID外部串行EEPROM&#xff08;24C02~24C16&#xff09;接口&#xff0c;用于客户特定的USB视频、PID、…

有空多刷刷算法题:回溯理论基础、leecode-77:组合、leecode:组合总和 III

回溯算法 一、理论基础 参考代码随想录&#xff0c;仅作记录学习之用 回溯是递归的副产品&#xff0c;只要有递归就会有回溯因为回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案&#xff0c;如果想让回溯法⾼效⼀些&#xff0c;可以加⼀些剪枝…

华为OD机试2024年C卷D卷 - 构成指定长度字符串的个数/字符串拼接(Java)

华为OD机试&#xff08;C卷D卷&#xff09;2024真题目录 题目描述&#xff1a;构成指定长度字符串的个数 (本题分值200) 给定 M&#xff08;0 < M ≤ 30&#xff09;个字符&#xff08;a-z&#xff09;&#xff0c;从中取出任意字符&#xff08;每个字符只能用一次&#x…

PostgreSql创建触发器并增加IF判断条件

在 PostgreSQL 中&#xff0c;可以使用触发器&#xff08;Trigger&#xff09;来在表上定义自定义的插入&#xff08;INSERT&#xff09;、更新&#xff08;UPDATE&#xff09;和删除&#xff08;DELETE&#xff09;操作的行为。触发器是与表相关联的特殊函数&#xff0c;它们在…

docker的学习(二):docker常用的高级技术总结

简介 docker的一些知识点的总结 UnionFS 分层&#xff0c;轻量级&#xff0c;高性能的文件系统&#xff0c;支持一层层的叠加功能来修改文件系统。 一次同时加载多个文件系统&#xff0c;把各层文件系统叠加起来&#xff0c;最终文件系统会包含所有底层的文件和目录&#xf…

【java SE语法篇】1.运算符

目录 1. 运算符和表达式2. 算数运算符3. 隐式转换4. 强制转换5. 自增自减运算符6. 赋值运算符7. 扩展运算符8. 关系运算符9. 逻辑运算符9.1 & 和 | 的使用&#xff1a;9.2 ^&#xff08;异或&#xff09;的使用&#xff1a;9.3 !&#xff08;取反&#xff09;的使用&#x…

2024年技校大数据实验室建设及大数据实训平台整体解决方案

随着信息技术的迅猛发展&#xff0c;大数据已成为推动产业升级和社会进步的重要力量。为适应市场需求&#xff0c;培养高素质的大数据技术人才&#xff0c;技校作为职业教育的重要阵地&#xff0c;亟需加强大数据实验室的建设与实训平台的打造。本方案旨在提出一套全面、可行的…

如何制作u盘启动盘_制作U盘启动盘详细图文教程

如何制作u盘启动盘&#xff1f;一个U盘如果不做成启动盘的话&#xff0c;就只能当存储用。如果一个U盘做成启动盘的话&#xff0c;可以通过U盘启动进入PE&#xff0c;我们就可以对电脑进行重装系统或分区等一些操作使用了&#xff0c;这个主要用于电脑需要救急的情况下使用。用…

jmeter录制脚本做压力测试

1.录制 第一步 设置浏览器代理&#xff0c;我用的火狐。谷歌、IE也是都可以。 我把端口号改成8082了&#xff0c;这个无所谓的&#xff0c;只要不冲突就可以。 紧接着要往浏览器里添加证书。直接搜索证书。把jmeter的证书导入浏览器。 2.在jmeter里设置 添加线程组、http代…

Another-redis-desktop-manager+ffmpeg

一、Another-redis-desktop-manager 1.点击链接下载&#xff1a;Release v1.6.6 qishibo/AnotherRedisDesktopManager GitHub 2. 傻瓜式安装 二、ffmpeg 1.下载&#xff1a;Builds - CODEX FFMPEG gyan.dev 2.下载对应版本 3.添加环境变量&#xff1a;添加到path里&am…

Intent的基本使用

1.Intent是什么&#xff1f; Intent用于Android程序中各组件&#xff08;Activity、BroadcastReceive、Service&#xff09;的交互&#xff0c;并且可以在组件之间传递数据&#xff0c;分为显式Intent和隐式Intent。 Intent是各个组件之间信息沟通的桥梁&#xff0c;既能在Ac…

【24年7月】最新Hexo+GitHubPages搭建个人博客【一】

Hexo 是一个高效的静态网站生成器&#xff0c;使用简洁的 Markdown&#xff08;或其他模板引擎&#xff09;编写内容&#xff0c;支持丰富的插件和主题&#xff0c;允许用户轻松定制网站。它通过将文本转换为静态HTML页面&#xff0c;使得网站加载速度快&#xff0c;易于部署&a…

Linux操作系统的有关常用的命令

1.linux系统的概述 1.1 什么是Linux系统? Linux&#xff0c;全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦 兹&#xff08;Linus Benedict Torvalds&#xff09;于1991年10月5日首次发布&#xff0c;它主要受…

塔子哥的题解点赞方案-美团2023笔试(codefun2000)

题目链接 塔子哥的题解点赞方案-美团2023笔试(codefun2000) 题目内容 塔子哥写了 n 篇题解&#xff0c;编号从 1 到 n&#xff0c;但是塔子哥忘了每篇题解有多少人点赞了。 现在他有如下两种信息&#xff1a; 1、每篇题解的点赞量都为正数&#xff0c;且不超过 m。 2、第 i 篇…

2024LitCTFmisc复现

secret 这首音乐好听&#xff0c;听完了&#xff0c;中间有段杂音 去AU看看 中间有一段藏了东西&#xff0c;放大 出flag了 flag{Calculate_Step_By_Step} 原铁&#xff0c;启动&#xff01; 解压出来是一张二维码 扫出来是原神 去010看看 看到有压缩包&#xff0c;提取出来 …

关于集成网络变压器的RJ45网口

集成网络变压器的RJ45网口是一种将网络变压器与RJ45接口集成在一起的网络连接解决方案。这种集成设计具有多项优势&#xff0c;使其在现代网络设备中得到广泛应用。 优势与特点 1. **空间节省**&#xff1a;集成设计减少了组件数量和连接线缆长度&#xff0c;有助于节省设备内…

Adobe Premiere Pro(Pr)安装包软件下载

一、简介 Adobe Premiere Pro&#xff08;简称Pr&#xff09;是由Adobe公司开发的一款功能强大的视频编辑软件。它支持多平台使用&#xff0c;包括Windows和Mac系统&#xff0c;并且拥有良好的兼容性和高效的性能。Premiere Pro不仅提供了视频剪辑、特效添加、音频处理等基本功…