【map、set】C++用红黑树来封装map、set容器

news2025/1/17 4:50:05
图片名称
🎉博主首页: 有趣的中国人

🎉专栏首页: C++进阶

🎉其它专栏: C++初阶 | Linux | 初阶数据结构

在这里插入图片描述

小伙伴们大家好,本片文章将会讲解map和set用红黑树来封装map、set容器的相关内容。


如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!

文章目录

  • `0. 前言`
  • `1. map、set底层源码一览`
    • ==<font color = blue size = 4><b>⌛底层查看⏳==
  • `2. 改造红黑树`
    • ==<font color = blue size = 4><b>⏳map类和set类⌛==
    • ==<font color = blue size = 4><b>⏳仿函数,这思想很重要⌛==
    • ==<font color = blue size = 4><b>⏳第一个K的作用⌛==
  • `3. 迭代器的模拟实现`
    • ==<font color = blue size = 4><b>⏳迭代器++⌛==
    • ==<font color = blue size = 4><b>⏳迭代器- -⌛==
  • `4. map的operator[]`
  • `5. 拷贝构造、析构、赋值重载`
    • ==<font color = blue size = 4><b>⏳析构函数⌛==
    • ==<font color = blue size = 4><b>⏳拷贝构造⌛==
    • ==<font color = blue size = 4><b>⏳赋值重载⌛==



0. 前言


首先借用某位大佬的一句话:学习制造轮子不是为了打造更出色的轮子,而是为了汲取大师们卓越的思想,以此启迪我们的认知与创造力。



1. map、set底层源码一览


我们在模拟红黑树的时候一律用了pairKV模型来进行实现。但是由于mapKV模型的而setK型的,但是底层都是用的红黑树,那么应该如何进行调整呢?

⌛底层查看⏳

在这里插入图片描述

可以看到,对于K类型的set,模板传入了两个相同的K类型给RBTree;对于KV类型的map,模板传入了一个K,还有一个pair<K, V>类型给RBTree。

在RBTree类中将第二个模板参数传入给RBTreeNode,data的数据类型为就是此类型,这样就解决了此问题。



2. 改造红黑树


⏳map类和set类⌛


我们先按照底层的思路实现以下map类和set类:

// mymap.h
#pragma once
#include "RBTtree.h"

namespace dsj
{
	template<class K, class V>
	class map
	{
	public:

	private:
		// pair<K, V> 为节点中的参数类型
		RBTree<K, pair<K, V>> rt;
	};
}

// myset.h
#pragma once
#include "RBTtree.h"

namespace dsj
{
	template<class K>
	class set
	{
	public:

	private:
		// 第二个 K 为节点中的参数类型
		RBTree<K, K> rt;
	};
}

💻RBTreeNode的修改
在这里插入图片描述
💻RBTree的修改
在这里插入图片描述

这样一改,问题就来了,在我们插入数据的时候,如何根据插入节点的K值来判断他插入所在的位置呢?如何进行比较?

可以用仿函数来进行比较。


⏳仿函数,这思想很重要⌛


虽然在RBTree中并不知道如何进行比较,但是在mapset类中知道如何进行比较,即mappairfirst进行比较,set直接用K值进行比较,实现方法如下:

// mymap.h
namespace dsj
{
	// 仿函数
	template <class K, class V>
	struct mapKeyOfT
	{
		// 取pair的first
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};


	template<class K, class V>
	class map
	{
	public:

	private:
		// 多加一个仿函数模板参数,对于map,取pair的first进行比较
		RBTree<K, pair<K, V>, mapKeyOfT<K, V>> rt;
	};
}

// myset.h
namespace dsj
{
	// 仿函数
	template <class K>
	struct setKeyOfT
	{
		// 取出key
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	template<class K>
	class set
	{
	public:

	private:
		// 多加一个仿函数模板参数,对于set,直接对K值进行比较
		RBTree<K, K, setKeyOfT<K>> rt;
	};
}


在这里插入图片描述

⏳第一个K的作用⌛


有一个问题,给红黑树传模板参数的时候,第一个参数K类型的作用是什么呢?

1. 对于insert来说,setmap都可以不要第一个模板参数K,因为set中第二个模板参数是K,可以用来进行插入时的比较,对于map,也是用第二个模板参数来进行比较;

2. 但是对于find接口,他就需要K



3. 迭代器的模拟实现


通过list的迭代器,我们知道如果原生指针的效果达不到我们想要的效果,就通过封装迭代器,再用运算符重载来达到我们预期的效果。


模拟实现:
template<class T>
struct __RBTIterator
{
	typedef RBTNode<T> Node;
	typedef __RBTIterator<T> Self;

	Node* _node;
	
	__RBTIterator(Node* node)
		:_node(node)
	{}

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

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

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

我们在模拟实现list的迭代器的时候讲过,对于const迭代器和普通迭代器,他们只有解引用->的的返回值不一样,其他都相同,如果重新写一遍那么代码肯定非常冗余,因此我们这里还用类似的方法来实现(传入模板参数)。

改造后:

template<class T, class Ref, class Ptr>
struct __RBTIterator
{
	typedef RBTNode<T> Node;
	typedef __RBTIterator<T, Ref, Ptr> Self;

	Node* _node;

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

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

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

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

RBTree中:

// 编译器自动推演,根据类型调用对应的迭代器
typedef __RBTIterator<T, T&, T*> Iterator;
typedef __RBTIterator<T, const T&, const T*> Const_Iterator;

Iterator begin()
{
	Node* leftMin = _root;
	// 加上leftMin是为了预防_root为空的情况
	if (leftMin && leftMin->_left)
	{
		leftMin = leftMin->_left;
	}
	return Iterator(leftMin);
}

Iterator End()
{
	return Iterator(nullptr);
}

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

Const_Iterator End() const
{
	return Const_Iterator(nullptr);
}

map和set中:

template<class K, class V>
class map
{
public:
	typedef typename RBTree<K, pair<K, V>, mapKeyOfT<K, V>>::Iterator iterator;
	typedef typename RBTree<K, pair<K, V>, mapKeyOfT<K, V>>::Const_Iterator const_iterator;
	const_iterator begin() const
	{
		return rt.Begin();
	}

	const_iterator end() const
	{
		return rt.End();
	}

	iterator begin()
	{
		return rt.Begin();
	}

	iterator end()
	{
		return rt.End();
	}

private:
	RBTree<K, pair<K, V>, mapKeyOfT<K, V>> rt;
};

template<class K>
class set
{
public:
	typedef typename RBTree<K, K, setKeyOfT<K>>::Iterator iterator;
	typedef typename RBTree<K, K, setKeyOfT<K>>::Const_Iterator const_iterator;

	const_iterator beign() const
	{
		return rt.Begin();
	}

	const_iterator end() const
	{
		return rt.End();
	}

	iterator beign()
	{
		return rt.Begin();
	}

	iterator end()
	{
		return rt.End();
	}
private:
	RBTree<K, K, setKeyOfT<K>> rt;
};

调用逻辑:
在这里插入图片描述

⏳迭代器++⌛


set和map迭代器的++按照中序遍历的顺序进行加加的,顺序为:左子树 根 右子树。

逻辑如下:

  1. 右子树存在,下一个访问节点是右子树的最左节点;
  2. 右子树不存在,向上找,找到孩子是父亲左节点的那个祖先节点。

代码:

Self& operator++()
{
	Node* cur = _node;
	if (cur->_right)
	{
		Node* rightMin = cur->_right;
		while (rightMin->_left)
		{
			rightMin = rightMin->_left;
		}
		_node = rightMin;
	}
	else
	{
		Node* parent = cur->_parent;
		// 这里加parent是为了预防访问最右节点后parent为空的情况
		while (parent && cur == parent->_right)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

⏳迭代器- -⌛

思路同上,但是顺序相反:右子树 根 左子树。

逻辑如下:

  1. 左子树存在,下一个访问节点是左子树的最右节点;
  2. 左子树不存在,向上找,找到孩子是父亲右节点的那个祖先节点。

代码:

Self& operator--()
{
	if (_node->_left)
	{
		Node* cur = _node->_left;
		while (cur->_right)
		{
			cur = cur->_right;
		}
		_node = cur;
	}
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_left)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}


4. map的operator[]


mapoperator[]的用法在之前的文章中讲过,它的作用是根据key的值进行查找,如果存在,那么就返回当前节点的value,如果不存在,就先插入此节点,然后再返回这个节点的value

实现代码:

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

Insert函数修改逻辑:

Insert函数,我们要改变它的返回值,返回值是pair

  • 如果插入成功,返回插入节点所在的迭代器;
  • 如果插入失败,返回K值相等的节点的迭代器。
  • 如果要进行旋转,要先记录一下当前cur的节点,因为旋转后cur的节点可能就不是我们新插入的节点了。

在这里插入图片描述



5. 拷贝构造、析构、赋值重载


⏳析构函数⌛


析构函数要用后序,这样更为简单:

~RBTree()
{
	Destroy(_root);
}
void Destroy(Node* root)
{
	// 左右根的顺序进行析构
	if (root == nullptr)
	{
		return;
	}
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
}

⏳拷贝构造⌛


拷贝构造要用先序:

RBTree(const RBTree<K, T, KeyOfT>& t)
{
	_root = Copy(t._root);
}

Node* Copy(Node* root)
{
	if (root == nullptr)
		return nullptr;
	
	Node* newroot = new Node(root->_data);
	newroot->_col = root->_col;

	newroot->_left = Copy(root->_left);
	if (newroot->_left)
		newroot->_left->_parent = newroot;

	newroot->_right = Copy(root->_right);
	if (newroot->_right)
		newroot->_right->_parent = newroot;

	return newroot;
}

⏳赋值重载⌛


下面是现代写法,之前讲过:

const RBTree<K, T, KeyOfT>& operator=(const RBTree<K, T, KeyOfT> t)
{
	swap(_root, t._root);
	return *this;
}

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

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

相关文章

Star CCM+中边界模式交界面与接触模式交界面的生成差异

前言 前文已经介绍过将零部件分配至区域的方法与步骤&#xff0c;根据接触创建边界模式交界面与根据接触创建接触模式交界面两种交界面模式对初始化时间的影响。两者除了对初始化时间的影响差异外&#xff0c;其生成的边界面也是存在差异的。本文将对两者的生成的交界面的差异…

【BUG】Edge|联想电脑 Bing 搜索报错“Ref A: 乱码、 Ref B:乱码、Ref C: 日期” 的解决办法

文章目录 省流版前言解决办法 详细解释版前言问题描述与排查过程解决办法与总结 省流版 前言 我也不清楚咋滴了&#xff0c;Bing 搜索突然偶尔报错&#xff1a; 换了代理关了插件都报错。 参考&#xff1a; 我在用bing搜索时出现了如下代码&#xff0c;导致bing无法使用&am…

只需5步帮你有效监控员工上网记录

监控员工上网记录是企业实施网络管理、确保工作效率、保护信息安全和遵循合规要求的一种常见做法。这一过程通常涉及使用专业的上网行为管理软件&#xff0c;如安企神、域智盾等&#xff0c;这些软件具备多样化的功能来帮助企业管理者有效地监控和控制员工的上网行为。以下是监…

【Java基础】IO流(4) —— 转换流、打印流

【Java基础】IO流(1) —— 简介 【Java基础】IO流(2) —— 字符流 【Java基础】IO流(3) —— 字节流 【Java基础】IO流(4) —— 转换流、打印流 【Java基础】IO流(5) —— 序列流、内存流 【Java基础】IO流(6) —— 随机访问文件流、数据流 转换流 InputStreamReader 是字节输…

DataGrip测试连接时出现报错解决方案

&#xff08;一&#xff09;报错情况描述&#xff1a; DBMS: MySQL (无版本) 区分大小写: 普通形式mixed&#xff0c;分隔形式exact Connection refused: connect. &#xff08;二&#xff09;解决方案&#xff1a; 1、 首先打开命令指示符&#xff0c;选择以管理员身份运行。…

在排序数组中查找元素的第一个位置和最后一个位置 ---- 二分查找

题目链接 题目: 分析: 如果我们查找元素的第一个位置, 随便假设一个位置为x, 如果这个数>target, 说明 [left,x-1] 是我们要找的位置, [x,right] 可以舍去, 让right mid-1,如果这个数target, 说明[left,x] 是我们要找的位置, [x1,right] 可以舍去, 让right mid,(因为当…

做抖音小店不想赔钱,这几个功能必须关掉!

大家好&#xff0c;我是电商糖果 有很多新手刚开始运营店铺&#xff0c;对店铺的有些设置并不了解。 前期将所有的设置都打开了&#xff0c;等到店铺出单之后&#xff0c;才发现麻烦一大堆。 这里糖果就跟自己开店的经验&#xff0c;劝告各位新手朋友&#xff0c;这几个功能…

数据仓库实验四:聚类分析实验

目录 一、实验目的二、实验内容和要求三、实验步骤1、建立数据表2、建立数据源视图3、建立挖掘结构Student.dmm4、部署项目并浏览结果5、挖掘模型预测 四、实验结果分析五、实验总结体会 一、实验目的 通过本实验&#xff0c;进一步理解基于划分的、基于层次的、基于密度的聚类…

【STM32项目】基于stm32智能鱼缸控制系统的设计与实现(完整工程资料源码)

实物演示效果 基于stm32智能鱼缸控制系统的设计与实现 目录&#xff1a; 实物演示效果 目录&#xff1a; 一、 绪论 1.1 项目研究目的及意义 1.1.1 选题目的 1.1.2 选题意义 1.2 国内外研究现状 1.2.1 国外发展现状 1.2.2 国内发展现状 1.3 项目研究内容 二、智能鱼缸系统总体设…

森林消防高压灭火泵的功能特点

我国森林面积广阔&#xff0c;自然资源丰富&#xff0c;而森林火灾是常见的自然灾害&#xff0c;具有范围大、损失惨、时间长、火势猛等特点。森林火灾会烧毁成片的森林&#xff0c;伤害林内的动物&#xff0c;而且还降低森林的更新能力&#xff0c;引起土壤的贫瘠和破坏森林涵…

【Qt 学习笔记】Qt常用控件 | 布局管理器 | 网格布局Grid Layout

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 布局管理器 | 网格布局Grid Layout 文章编号&#xff1a…

iPhone实况照片从Windows资源管理器复制的JPG+MOV无法正常还原到iPhone

背景&#xff1a; 之前使用的iPhone 15 Pro&#xff0c;使用的Windows资源管理器当中复制导出的实况照片&#xff0c;复制出来的格式例如IMG_0001.JPG, IMG_0001.MOV。之后手机就卖掉了。现在使用的iPhone 14 Pro Max&#xff0c;想要导回之前备份的实况照片。尝试使用爱思助手…

92.网络游戏逆向分析与漏洞攻防-游戏技能系统分析-利用哈希表实现快速读取文本内容

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

车间人员作业行为智能检测 AI视觉在生产车间制造中的应用

车间人员作业行为智能检测系统基于神经网络人工智能视觉算法&#xff0c;车间人员作业行为智能检测通过对车间监控摄像头获取的视频图像进行分析和识别&#xff0c;实现了对人员操作行为的智能检测。系统对工人的操作环节进行分解&#xff0c;根据时间、动作标准等方面制定了规…

Django5+React18前后端分离开发实战13 使用React创建前端项目

先将nodejs的版本切换到18&#xff1a; 接着&#xff0c;创建项目&#xff1a; npx create-react-app frontend接着&#xff0c;使用webstorm打开这个刚创建的项目&#xff1a; 添加一个npm run start的配置&#xff1a; 通过start启动服务&#xff1a; 浏览器访问&…

STranslate即开即用、即用即走的翻译(OCR)工具 v1.1.3.514

软件介绍 STranslate 是一款面向 Windows 操作系统用户设计的翻译软件&#xff0c;该软件具备开源性质并且免费。它结合了翻译功能与光学字符识别&#xff08;OCR&#xff09;技术&#xff0c;允许用户高效且直接地在屏幕上进行文字的翻译和识别。在开发过程中&#xff0c;STr…

Nginx/阿里云/二级域名的配置和使用

阿里云域名解析配置如下&#xff1a; nginx配置如下&#xff1a; 访问地址&#xff1a; zhadmin.iotzzh.com image.png

二叉搜索数之删除节点

看题目&#xff1a; 给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除节点可分为两个步…

详解ArcGIS 水文分析模型构建

目录 前言 项目环境、条件 Dem 数据预览 ArcGIS模型构建器 模型搭建 填洼 流向 流量 河流长度 栅格计算器 河流链接 河网分级 栅格河网矢量化 绘制倾泻点 栅格流域提取 集水区 盆域分析 栅格转面 模型应用 导出 py 文件 完善脚本 最终效果 结束语 前言 …