基于红黑树对map和set容器的封装

news2024/12/23 22:50:44

在这里插入图片描述

本章代码gitee仓库:map和set模拟实现、stl_map_set_tree源码

文章目录

  • 🐱1. 红黑树的泛型
    • 🐈1.1 红黑树节点
    • 🐈1.2 红黑树迭代器
    • 🐈1.3 仿函数
  • 🐯2. 对set的封装
  • 🦄3. 对map的封装

🐱1. 红黑树的泛型

我们通过查看源码,发现mapset的底层都是红黑树,用的同一个类模板,通过控制传不同的模板参数,从而实例化出不同的类
image-20230913160728813

🐈1.1 红黑树节点

因为要对mapset进行封装,set是K模型,map是KV模型,所以采用模板来对数据类型进行控制

enum Colour
{
	RED,
	BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;

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

🐈1.2 红黑树迭代器

STL库里面红黑树设置了一个头节点,这样在而我们采用的不是库里面方式,用的自己手搓的红黑树,所以在迭代器++--的时候,采用遍历树的方式
在这里插入图片描述

迭代器结构:

这里有三个模板参数,PtrRef可以通过传过来的是普通参数还是const参数来控制采用什么样的迭代器(普通迭代器或者const迭代器);同时也为mappair的两个参数通过了很好的访问方式Ref operator*()Ptr operator->()

template<class T,class Ptr,class Ref>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, Ptr, Ref> Self;
	typedef __TreeIterator<T, T*, T&> Iterator;
	Node* _node;
	//根据实例化的迭代器选择是构造还是拷贝构造
	__TreeIterator(const Iterator&it)
		:_node(it._node)
	{}

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

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

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

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			//右不为空,访问右子树的最左节点(最小节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}
			_node = subLeft;
		}
		else
		{
			//右为空
			Node* cur = _node;
			Node* parent = cur->_parent;
			//孩子是父亲左的祖先节点
			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		else
		{
			//孩子是父亲右的节点
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

迭代器:

template<class K,class T,class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;

public:
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;	//const迭代器

	//迭代器
	iterator begin()
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return leftMin;
	}

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

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

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

🐈1.3 仿函数

当对元素进行插入或者查找的时候,都要进行比较,而mappair无法直接比较,所以我们设置了仿函数,用来提取key
这里以Find接口为例,具体可以看代码仓库的完整代码

Node* Find(const K& key)
{
	Node* cur = _root;
	//仿函数
	KeyOfT kot;
	while (cur)
	{
		//提出key值
		if (kot(cur->_data) > key)
		{
			cur = cur->_left;
		}
		else if (kot(cur->_data) < key)
		{
			cur = cur->_right;
		}
		else
			return cur;
	}
	return nullptr;
}

🐯2. 对set的封装

对于set的封装相对简单,在整个设计中,set由于只有一个K值,所以很多模板参数对于set而已作用并不大

image-20230914174644288

set的K值是不允许修改的,所以不管是普通迭代器还是const迭代器,都是采用的const迭代器

这里关于插入的操作,本质上也是为了兼容map,放到下面和map一起说

namespace My_map_set
{
	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;

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

		const_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;
	};
}

我们在封装的时候,采用了一个仿函数SetKeyOfT,这是因为在数据比较的时候,mappair需要取出里面的key值,为了保持统一,我们对set也使用了仿函数

🦄3. 对map的封装

  • mapkey值是不允许修改的,而value是允许修改的,使用有const修饰pair里面的key

    image-20230914185106723

  • map是支持[]的,而[]底层又是调用的insert,所以对于insert的返回值需要进行更改,而map有普通迭代器和const迭代器,不需要指定调用树里面的

    set的迭代器都是const迭代器,所以我们直接调用树里面的迭代器,库里面就是这么实现的:

    image-20230914193944187

    当这个类被实例化成const迭代器的时候,是一个构造函数,支持了普通迭代器构造const迭代器;

    当这个类被实例化为普通迭代器,那就是拷贝构造

    image-20230914195622767

namespace My_map_set
{
	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;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

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

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

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

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

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

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

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

这里的封装相比之前的vectorlist这些容器模拟实现,会麻烦一点,本章也只是对于mapset的核心功能进行实现,具体的可以参考源码进行学习,

那本期的分享就到这里咯,我们下期再见,如果还有下期的话。

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

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

相关文章

android获取设备的IMEI号

在Android 8.0 (API级别26)和更高版本的平台上的 &#xff0c;64位数字(表示为十六进制字符串)&#xff0c;对于应用程序签名密钥、用户和设备的每个组合都是唯一的。ANDROID_ID的值由签名密钥和用户限定作用域。如果在设备上执行出厂重置或如果APK签名密钥更改&#xff0c;则该…

【校招VIP】前端操作系统之存储管理加密

考点介绍 加密算法有很多&#xff0c;如不可逆的摘要算法MD5、SHA&#xff08;安全哈希算法&#xff09;&#xff0c;可逆的Base64编码&#xff0c;对称加密算法DES、AES&#xff0c;还有非对称加密算法DH、RSA等。那是不是说明我们可以使用任何一种加密算法就能保证网站的安全…

认养一头牛———众筹+合伙人商业模式解析

2016年成立以来&#xff0c;认养一头牛致力于打造数字化乳业第一品牌&#xff0c;只为一杯好牛奶。公司在创立三年内完成了10个亿销售目标&#xff0c;被业界称为新消费品牌黑马&#xff0c;一举闯入互联网新消费梯队的视线。未来三年&#xff0c;认养一头牛将着力打造全国最大…

【精品】将两个字符串进行加密和解密的Java工具类

原理 使用一个分隔符将两个字符串连接在一起&#xff0c;并对连接后的字符串进行加密。加密过程中&#xff0c;首先生成一个随机下标列表&#xff0c;然后根据随机下标打乱字符串的顺序&#xff0c;并使用Base64进行编码。解密过程中&#xff0c;根据之前生成的随机下标列表将…

Definition and properties of the Gamma function

See https://math.libretexts.org/Bookshelves/Analysis/Complex_Variables_with_Applications_(Orloff)/14%3A_Analytic_Continuation_and_the_Gamma_Function/14.02%3A_Definition_and_properties_of_the_Gamma_function 勒让德倍元公式&#xff08;Legendre duplication fo…

无涯教程-JavaScript - N函数

描述 N函数返回一个转换为数字的值。 语法 N (value) 争论 Argument描述Required/OptionalValue 要转换的值或对值的引用。 N转换下表中列出的值。 Required 值 N的返回值一个数字那个数字日期,采用Microsoft Excel中可用的内置日期格式之一该日期的序列号 TRUE 1 FALSE…

微视网媒:新媒体时代的宣传与营销新范式

随着数字科技的快速发展&#xff0c;新媒体已经成为了现代社会中不可或缺的一部分。其中&#xff0c;微视网媒作为新兴的网络媒体形态&#xff0c;正在改变着传统的宣传和营销模式&#xff0c;为企业和个人提供了全新的推广平台。本文将对微视网媒的优势、应用场景以及案例进行…

免费开箱即用的微鳄任务管理系统

编者按&#xff1a;基于天翎低代码平台实现的微鳄365任务管理系统&#xff0c;包括有发起任务、重点关注、日程、项目管理等功能&#xff0c;支持私有化部署&#xff0c;免费开箱即用。任务管理系统是组织工作中不可或缺的工具&#xff0c;可以提高工作效率、促进协作、增强任务…

GTS 中testPeakPssOfAllApps fail 详解

0. 前言 GTS 在测试 case armeabi-v7a GtsMemoryHostTestCases 的时候出现下面异常&#xff0c;本文总结一下。 com.google.android.memory.gts.AllAppsMemoryHostTest#testPeakPssOfAllApps 1. error log 09-14 10:16:34 I/TestFailureListener: FailureListener.testFaile…

第十届IEEE电气工程与自动化国际学术论坛(IFEEA 2023)

第十届IEEE电气工程与自动化国际学术论坛&#xff08;IFEEA 2023&#xff09; 2023 10th International Forum on Electrical Engineering and Automation IFEEA论坛属一年一度的国际学术盛会。因其影响力及重要性&#xff0c;IFEEA论坛自创建筹办以来&#xff0c;便受到国内…

【React】面试题5题

1. 说说你对 dangerouslySetInnerHTML 的理解 dangerouslySetInnerHTML是React中的一个属性&#xff0c;用于将HTML代码作为字符串直接插入到组件中的DOM元素中。它可以用来动态地生成HTML内容&#xff0c;但同时也带来了一些潜在的安全风险。 使用dangerouslySetInnerHTML时…

函数递归详解

前言&#xff1a; 函数递归是一种算法&#xff0c;递归是通过将问题分解为更小的子问题来解决问题的办法&#xff0c;递归的优点如下&#xff1a; 简洁性&#xff1a;递归可以用较少的代码实现复杂的功能灵活性&#xff1a;递归可以应对未知深度的数据结构&#xff0c;因为它不…

Spring MVC 中的数据绑定和验证机制是什么,如何使用

在 Spring MVC 应用中&#xff0c;数据绑定和验证是非常重要的一部分&#xff0c;它们可以帮助我们将用户提交的数据绑定到 Java 对象上&#xff0c;并对数据进行验证&#xff0c;保证数据的正确性和可靠性。在 Spring MVC 中&#xff0c;数据绑定和验证机制都是通过注解来实现…

Unity 课时 4 : No.4 模拟面试题

课时 4 : No.4 模拟面试题 C# 1. 请说明字符串中 string str null string str “” string str string.Empty 三者的区别 第一个未作初始化没有值, 第二个为空字符串, 答案&#xff1a; str null 在堆中没有分配内存地址 str "" 和 string.Empty 一样都是…

CentOS7 yum安装报错:“Could not resolve host: mirrorlist.centos.org; Unknown error“

虚拟机通过yum安装东西的时候弹出这个错误&#xff1a; 1、查看安装在本机的网卡 网卡ens33处于disconnected的状态 nmcli d2、输入命令&#xff1a; nmtui3、选择网卡&#xff0c;然后点击edit 4、移动到Automatically connect按空格键选择&#xff0c;然后移动到OK键按空格…

Win7 IIS7解析漏洞复现

一、漏洞说明 文件上传使用白名单做限制&#xff0c;只能上传图片文件&#xff0c;导致脚本文件无法上传&#xff0c;上传图片马绕过白名单文件上传的验证&#xff0c;但是图片马又无法解析&#xff0c;利用IIS7.5文件解析漏洞的特点&#xff1a;任意文件名/任意文件名.php&…

华为云云耀云服务器L实例评测 | Linux系统宝塔运维部署H5游戏

文章目录 前言一、云服务器相对传统服务器有什么优势1.1、可伸缩性&#xff08;Scalability&#xff09;1.2、灵活性&#xff08;Flexibility&#xff09;1.3、高可用性&#xff08;High Availability&#xff09;1.4、备份和恢复&#xff08;Backup and Recovery&#xff09;1…

SVN学习笔记--如何 Merge 分支?保姆级教学,带图片手把手实操。

目录 前言实例具体步骤总结 前言 由于项目中有多个分支&#xff0c;如果修改了一处代码&#xff0c;可能需要保证分支和主分支代码一样&#xff0c;所以需要利用 SVN Merge 代码。 实例 现在有两个分支 一个 trunk&#xff1a;主分支一个 develop&#xff1a;开发分支 我们…

数学实验-迭代(Mathematica实现)

一、实验名称&#xff1a;迭代 二、实验环境&#xff1a;Mathematica 10.3软件 三、实验目的&#xff1a;本实验通过Mathematica 10.3软件利用迭代求解方程的近似解&#xff0c;了解迭代方法在解决问题的收敛速度的异同&#xff0c;认识到函数的迭代是数学研究中的一个非常重…

PHPword setImageValue 设置高度不生效

phpword 版本 "phpoffice/phpword": "^0.18.3 我正在使用这个代码&#xff0c;使用模板&#xff0c;写入动态图片&#xff0c;但问题是图像的大小太小&#xff0c;我需要增加高度和宽度 模板文件 如下 问题写法&#xff1a; $file_name "简历";…