爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      【C++】map和set的使用及其模拟实现

      首页 知识中心 软件开发 文章详情页

      【C++】map和set的使用及其模拟实现

      2024-05-28 08:15:10 阅读次数:44

      c++,算法

       

      一、map和set的使用

      1. 关联式容器

      在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

      关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。

      💕 树形结构的关联式容器:

      根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。


      2. 键值对

      用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

      比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

      SGI-STL中关于键值对的定义:

      template <class T1, class T2>
      struct pair
      {
      	typedef T1 first_type;
      	typedef T2 second_type;
      	T1 first;
      	T2 second;
      	pair(): first(T1()), second(T2())
      	{}
      	pair(const T1& a, const T2& b): first(a), second(b)
      	{}
      };
      

      这里我们可以看到,pair类中的first就是键值key,second就是key对应的信息 value;所以以后在定义KV模型的时候,只需要在容器/容器中的每一个节点中定义一个pair对象即可。

      💕 make_pair函数

      由于 pair 是类模板,所以我们通常是以 显式实例化 + 匿名对象 的方式来进行使用,但是由于显式实例化比较麻烦,所以 C++ 还提供了 make_pair 函数,其定义如下:

      【C++】map和set的使用及其模拟实现

      make_pair函数的内部实现如下:

      template <class T1,class T2>
      pair<T1,T2> make_pair (T1 x, T2 y)
      {
        return ( pair<T1,T2>(x,y) );
      }
      

      make_pair 返回的是一个 pair 的匿名对象,匿名对象会自动调用 pair 的默认构造完成初始化;但由于 make_pair 是一个函数模板,所以模板参数的类型可以根据实参来自动推导完成隐式实例化,这样我们就不用每次都显式指明参数类型了。


      3. 关联式容器的使用

      3.1 set

      set是一种key模型的容器,也就是说,set中只有键值key,而没有对应的value,并且每个key都是唯一的,set容器中的元素不允许修改,因为那样会破坏搜索树的规则。但是set允许插入和删除。

      【C++】map和set的使用及其模拟实现
      T: set中存放元素的类型,实际在底层存储<value, value>的键值对。
      Compare: set中元素默认按照小于来比较
      Alloc: set中元素空间的管理方式,使用STL提供的空间配置器管理

      总结:

      • set 是K模型的容器,所以 set 中插入元素时,只需要插入 key 即可,不需要构造键值对;
      • set中的元素不可以重复,因此可以使用set进行去重;
      • 由于 set 底层是搜索树,所以使用 set 的迭代器遍历 set 中的元素,可以得到有序序列,即 set 可以用来排序;
      • set 默认使用的仿函数为 less,所以 set 中的元素默认按照小于来比较;
      • 由于 set 底层是平衡树搜索树,所以 set 中查找某个元素,时间复杂度为 O(logN);
      • set 中的元素不允许修改,因为这可能破坏搜索树的结构;
      • set 中的底层使用平衡二叉搜索树 (红黑树) 来实现。

      💕 set的使用

      构造

      【C++】map和set的使用及其模拟实现

      迭代器

      【C++】map和set的使用及其模拟实现

      修改

      【C++】map和set的使用及其模拟实现

      其他操作

      【C++】map和set的使用及其模拟实现

      set使用范例

      void TestSet()
      {
      	// 用数组array中的元素构造set
      	int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
      	set<int> s(array, array + sizeof(array) / sizeof(array[0]));
      	cout << s.size() << endl;
      
      	// 正向打印set中的元素,从打印结果中可以看出:set可去重
      	for (auto& e : s)
      		cout << e << " ";
      	cout << endl;
      
      	// 使用迭代器逆向打印set中的元素
      	for (auto it = s.rbegin(); it != s.rend(); ++it)
      		cout << *it << " ";
      	cout << endl;
      
      	// set中值为3的元素出现了几次
      	cout << s.count(3) << endl;
      }
      

      【C++】map和set的使用及其模拟实现


      3.2 multiset

      multiset 也是 K模型 的容器,它和 set 唯一的区别在于 multiset 中允许存在重复的 key 值节点 ,所以 multiset 可以用来排序和查找,但是不能用来去重。

      【C++】map和set的使用及其模拟实现

      💕 multiset的使用

      multiset 的使用和 set 几乎一样,这里我们需要注意两点即可:

      • 由于 multiset 中允许存在重复 key 值的节点,所以 multiset 中 count 函数就有作用了,我们可以通过 count 函数来统计同一 key 中在 multiset 中的数量。
      • multiset 中 find 函数的使用也和 set 有所区别 – 由于 set 中没有重复的节点,所以 find 时要么返回该节点位置的迭代器,要么返回 end();而 multiset 中可能有重复的节点,所以 find 时返回的是同一 key 值中的哪一个节点呢?实际上 find 返回的是中序遍历过程中第一个匹配的节点位置的迭代器。

      multiset的使用范例

      void multiset_test() {
      	// 构造multiset
      	int array[] = { 1, 3, 5, 7, 3, 2, 4, 6, 8, 0, 1, 3, 5, 7, 3, 2, 4, 6, 8, 3 };
      	multiset<int> s(array, array + sizeof(array) / sizeof(array[0]));
      	cout << s.size() << endl;
      
      	// 正向打印multiset中的元素,multiset允许重复元素的出现
      	for (const auto& e : s)
      		cout << e << " ";
      	cout << endl;
      
      	//如果查找的key在multiset中有多个,则返回中序遍历中遇到的第一个节点的迭代器
      	multiset<int>::iterator pos = s.find(3);
      	while (pos != s.end()) {
      		cout << *pos << " ";
      		++pos;
      	}
      	cout << endl;
      
      	// 查找+删除
      	cout << s.count(3) << endl; //输出key为3的节点的个数
      	pos = s.find(3);
      	if (pos != s.end())
      		s.erase(pos);
      	cout << s.count(3) << endl;
      }
      

      【C++】map和set的使用及其模拟实现


      3.3 map

      map 和 set 一样都是按照一定次序存储元素的容器,其底层也是一棵平衡二叉搜索树;和 set 不同的是,map 是 KV模型 的容器,在map 中,键值 key 通常用于排序和惟一地标识元素,而值 value中 用于存储与此键值 key 关联的内容,键值 key 和值value的类型可以不同; 在 map 内部,key-value 通过成员类型 pair 绑定在一起,也就是我们文章开始提到的键值对;

      【C++】map和set的使用及其模拟实现

      需要注意的是:map 中的元素是按照键值 key 进行比较排序的,而与 key 对应的 value 无关,同时,map 中也不允许有重复 key 值的节点,也不允许修改key,但是它可以修改节点中key对应的value;map 也可用于排序、查找和去重,且 map 查找的时间复杂度也为 O(logN)。

      💕 map的使用

      构造

      【C++】map和set的使用及其模拟实现

      迭代器

      【C++】map和set的使用及其模拟实现

      修改

      【C++】map和set的使用及其模拟实现

      元素访问

      【C++】map和set的使用及其模拟实现

      [ ]运算符重载的函数定义如下:

      mapped_type& operator[] (const key_type& k) 
      {
      	(*((this->insert(make_pair(k, mapped_type()))).first)).second;
      }
      //拆解后的函数
      V& operator[] (const K& key)
      {
      	pair<iterator, bool> ret = insert(make_pair(key, V()));
      	//return *(ret.first)->second;
      	return ret.first->second;
      }
      

      下面我们来分析一下这个函数:

      • operator[] 函数先向map容器中插入key:如果map中有与该值相等的节点,则插入失败,返回的pair中存放着该节点位置的迭代器和false;如果map中没有与该节点相等的节点,则插入成功,该节点的value值为 V 默认构造的缺省值。
      • 然后,operator[] 会取出pair迭代器(ret,first)然后对迭代器进行解引用得到一个pair<k,v>对象,最后再返回排pair对象中的second的引用,即key对应的value的引用。因此我们可以直接在函数外部修改key对应的value的值。

      map的使用案例

      void map_test() {
      	map<string, string> m;
      
      	m.insert(pair<string, string>("peach", "桃子"));
      
      	//为了方便,我们建议直接用make_pair函数来构造键值对
      	m.insert(make_pair("banan", "香蕉"));
      
      	m["apple"] = "苹果";
      
      	for (auto& e : m)
      		cout << e.first << ":" << e.second << endl;
      	cout << endl;
      
      	// map中的键值对key一定是唯一的,如果key存在将插入失败
      	auto ret = m.insert(make_pair("peach", "桃色"));
      	if (ret.second)
      		cout << "<peach, 桃色>不在map中, 已经插入" << endl;
      	else
      		cout << "键值为 peach 的元素已经存在:" << ret.first->first << "--->" << ret.first->second << " 插入失败" << endl;
      
      	// 删除key为"apple"的元素
      	m.erase("apple");
      	if (1 == m.count("apple"))
      		cout << "apple还在" << endl;
      	else
      		cout << "apple被吃了" << endl;
      }
      

      【C++】map和set的使用及其模拟实现


      3.4 multimap

      同样,multimap与map的区别和multiset与set的区别是一样的。find返回的是中序遍历中遇到的第一个节点的迭代器。count返回和key值相等的节点的个数。

      这里我们需要注意的是:multimap中没有重载[]运算符,因为multimap中的key值是可以重复的。如果使用 [] 运算符,会导致无法确定访问哪个哪一个元素。


      二、map和set的模拟实现

      我们知道,map和set容器的底层都是使用红黑树实现的。但是,set是K模型的的容器,而map是KV模型的容器,他们却都能使用红黑树来封装。下面,我们先来看一下源码中是如何实现的。

      【C++】map和set的使用及其模拟实现

      这里我们发现,set头文件中包含了 stl_set.h 和 stl_multiset.h ,而map头文件中包含了 stl_map.h 和 stl_multimap.h,而stl_tree.h应该就是用来封装实现他们的红黑树。

      【C++】map和set的使用及其模拟实现

      这里我们可以看到:set和map的增删查改功能都是封装的同一个红黑树的接口。

      对于 map来说,key_type 就是我们平常所理解的 K,但是 value_type 却是 pair 类型,它的两个参数分别是模板参数 _Key 和 _Tp,也就是我们认为的传递给 map 的 K 和 V;

      而对于 set 来说,key_type 也是我们平时认为的 K,但是我们发现 set 中居然还有一个变量 value_type,并且 value_type 的类型就是 key_type 的类型。

      下面我们来看一下红黑树的源码,看一下为什么对于这种K模型还需要设计一个value_type变量呢?

      //stl_tree.h
      //红黑树的基类
      struct __rb_tree_node_base
      {
        typedef __rb_tree_color_type color_type;
        typedef __rb_tree_node_base* base_ptr;
      
        color_type color; 
        base_ptr parent;
        base_ptr left;
        base_ptr right;
      };
      
      //红黑树的节点结构
      template <class Value>
      struct __rb_tree_node : public __rb_tree_node_base
      {
        typedef __rb_tree_node<Value>* link_type;
        Value value_field;
      };
      
      //红黑树
      template <class Key, class Value, class KeyOfValue, class Compare,
                class Alloc = alloc>
      class rb_tree {
      protected:
        typedef __rb_tree_node_base* base_ptr;
        typedef __rb_tree_node<Value> rb_tree_node;
      public:
        typedef Key key_type;
        typedef Value value_type;
        typedef rb_tree_node* link_type;
      }
      

      set、map和rbtree之间的关系:

      【C++】map和set的使用及其模拟实现

      通过这张图我们就可以理解,为什么set和map是不同模型的容器,但是他们却都可以使用同一棵树红黑树作为底层容器了。

      如果是 map,则红黑树的模板参数 _Val 被实例化为 pair\u003CK,V>,那么红黑树节点中存储的数据的类型也是 pair\u003CK,V>;如果是 set,则红黑树的模板参数 _Val 被实例化为 K,此时红黑树节点中存储的数据的类型就为 K;

      也就是说,红黑树完全可以根据第二个模板参数 _Val 的实参类型的不同实例化出符合 set 要求和 符合 map 要求的不同的两个类。这里我们来解答一下传递第一个模板参数 _Key 的作用?

      这是因为某些函数需要使用 key,比如 find 函数就只能通过 key 进行查询,即在 map 的 find 中不能通过 _Val 即 pair 类型来进行查询。


      1. 红黑树的实现(封装map和set版本)

      现在,我们需要参考源码中的方式将我们自己实现的红黑树改造一下,使得它既能够封装map,又能够封装set。

      1.1 节点的实现

      //创建红黑树的节点
      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)
      	{}
      };
      template<class K, class T>
      class RBTree
      {
      	typedef RBTreeNode<T> Node;
      private:
      	Node* _root = nullptr;
      };
      

      1.2 KeyOfT(仿函数)

      我们在 insert 时需要比较 key 的大小来确定插入位置,在之前模拟实现的红黑树中,我们直接将节点定义为了pair<K,V> 类型,所以我们可以直接取 kv.first 进行比较;但是现在,节点中的数据既可能是 pair 类型,也可能是 key 的类型,所以我们不能再用 data.first,同时由于stl 中实现的 pair 比较函数并不是单纯比较 first,而是先比较 first,再比较 second。

      为了实现在插入操作中既可以比较Key模型的Key值,又可以比较KV模型的Key值,我们可以自己写一个仿函数来完成红黑树中节点数据 _data大小的比较了。当然,源码中也是这样实现的。

      //set
      namespace cjl
      {
      	template<class K>	
      	class set
      	{
      		struct SetKeyOfT
      		{
      			const K& operator()(const K& key)
      			{
      				return key;
      			}
      		};
      	private:
      		RBTree<K, K, SetKeyOfT> _t;
      	};
      }
      //map
      namespace cjl
      {
      	template<class K, class V>
      	class map
      	{
      		struct MapKeyofT
      		{
      			const K& operator()(const pair<const K,V>& kv)
      			{
      				return kv.first;
      			}
      		};
      	private:
      		RBTree<K, pair<const K,V>, MapKeyofT> _t;
      	};
      }
      

      1.3 红黑树的插入Insert

      template<class K, class T, class KeyOfT>
      class RBTree {
      	typedef RBTreeNode<T> Node;
      public:
      	bool insert(const T& data) {
      		//判断根节点是否为空
      		if (_root == nullptr) {
      			_root = new Node(data);
      			_root->_col = BLACK;  //根节点的颜色一定为黑
      			return true;
      		}
      
      		//寻找插入位置
      		KeyOfT kot;  //仿函数对象
      		Node* parent = nullptr;
      		Node* cur = _root;
      		while (cur) {
      			if (kot(data) < kot(cur->_data)) {
      				parent = cur;
      				cur = cur->_left;
      			}
      			else if (kot(data) > .kot(cur->_data)) {
      				parent = cur;
      				cur = cur->_right;
      			}
      			else {
      				return false;  //不允许重复节点
      			}
      		}
      		
      		Node* newNode = new Node(data);
      		if (kot(data) < kot(parent->_data))
      			parent->_left = newNode;
      		else
      			parent->_right = newNode;
      		newNode->_parent = parent;  //修改父节点指向
              
              //调整节点
              //...
              return true;
          }
      private:
          Node* _root;
      };
      

      1.4 迭代器iterator

      💕 begin() 迭代器

      begin()迭代器的位置是红黑树中序遍历的第一个节点,也就是整棵树的最左节点。

      💕 end() 迭代器

      end()迭代器是红黑树中序遍历最后一个元素的下一个位置。为了让 end() 能够指向最右节点的下一个节点, stl 源码中增加了一个哨兵位的头结点,该节点的 left 指向这棵树的最左节点,也就是begin(),right 指向这棵树的最右节点,parent 指向 nullptr,然后让根的父节点指向它,最右节点的 right 也指向它;所以在 stl 源码的实现中这个哨兵位的头结点就是 end();

      【C++】map和set的使用及其模拟实现

      这里我们直接将end()定义为nullptr即可。

      💕 迭代器的++ 与 - -

      因为红黑树的中序遍历是一个有序序列,所以++就是跳转到红黑树中序遍历的下一个节点的位置。--则是反过来跳转到中序遍历中上一个节点的位置。

      【C++】map和set的使用及其模拟实现

      + +情况的分类

      • 若当前节点有右孩子,那么迭代器++跳转到右子树的最左节点。
      • 若当前节点没有右孩子,则迭代器++跳转到当前节点为父节点

      - - 情况的分类

      • 若当前节点有左孩子,那么迭代器 - - 跳到左子树的最右节点;
      • 若当前节点没有左孩子,则迭代器 - - 跳到 当前节点为 父节点的右孩子 的那一个祖先节点;
      //迭代器的实现
      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;
      	}
      
      	//重载 ++it
      	Self& operator++()
      	{
      		//本质上是找中序遍历的下一个元素——分为两种情况:right == nullptr 和 right != nullptr
      
      		if (_node->_right) // right != nullptr
      		{
      			Node* parent = _node->_right;
      			
      			while (parent->_left)
      			{
      				parent = parent->_left;
      			}
      			_node = parent;
      		}
      		else //right == nullptr
      		{
      
      			Node* parent = _node->_parent;
      			Node* cur = _node;
      
      			while (parent && cur == parent->_right)
      			{
      				cur = cur->_parent;
      				parent = parent->_parent;
      			}
      			_node = parent;
      		}
      		return *this;
      	}
      
      	//重载 --it
      	Self& operator--()
      	{
      		if (_node->_left) // left != nullptr
      		{
      			Node* parent = _node->_left;
      
      			while (parent->_right)
      			{
      				parent = parent->_right;
      			}
      			_node = parent;
      		}
      		else //left == nullptr
      		{
      
      			Node* parent = _node->_parent;
      			Node* cur = _node;
      
      			while (parent && cur == parent->_left)
      			{
      				cur = cur->_parent;
      				parent = parent->_parent;
      			}
      			_node = parent;
      		}
      		return *this;
      	}
      
      };
      

      2. set的模拟实现

      set 的插入、查询函数直接使用适配器模式,调用红黑树对应的函数即可完成,这里最重要的是迭代器的复用。

      【C++】map和set的使用及其模拟实现

      当我们直接复用红黑树的迭代器时,会发现一个问题,我们可以使用迭代器来修改key值,但是如果这样的话必定会破坏搜索树的结构。

      【C++】map和set的使用及其模拟实现

      我们直接让 set 的普通迭代器和 const 迭代器都使用红黑树的 const 迭代器来进行封装即可,这样 set 迭代器解引用得到的就是 const K& 了;但是这样做之后发现虽然的确不能通过迭代器来修改 key 的值了,但是这里又发生了新的错误:

      【C++】map和set的使用及其模拟实现

      其实这是因为在insert中返回的是红黑树的普通迭代器,但是接受返回值的却是红黑树的const迭代器。

      要解决这个问题的方法其实也并不难,我们需要在红黑树的迭代器中实现一个拷贝构造函数。

      • 当模板实例化为 <T, T&, T*> 时,iterator 和 Self 相同,此时这是一个拷贝构造函数;
      • 当模板实例化为 <T, const T&, const T*> 时,Self 是 const 迭代器,而 iterator 是普通迭代器,此时这是一个构造函数,它可以用普通迭代器构造出一个 const 迭代器。

      所以我们可以通过红黑树迭代器类中构造函数来实现用普通迭代器来构造 const迭代器。

      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)
      	{}
      
      	__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& iterator)
      		:_node(iterator._node)
      	{}
      	//运算符的重载...
      };
      

      【C++】map和set的使用及其模拟实现

      💕 set模拟实现源码:

      namespace cjl
      {
      	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()
      		{
      			return _t.begin();
      		}
      
      		iterator end()
      		{
      			return _t.end();
      		}
      
      		const_iterator begin() const 
      		{
      			return _t.begin();
      		}
      
      		const_iterator end() const 
      		{
      			return _t.end();
      		}
      
      		//插入
      		pair<iterator, bool> insert(const K& key)
      		{
      			return _t.Insert(key);
      		}
      
      		//查询
      		iterator find(const K& key)
      		{
      			return _t.Find(key);
      		}
      
      	public:
      		
      	private:
      		RBTree<K, K, SetKeyOfT> _t;
      	};
      }
      

      3. map的模拟实现

      namespace cjl
      {
      	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();
      		}
      
      		pair<iterator,bool> insert(const pair<const K, V>& kv)
      		{
      			return _t.Insert(kv);
      		}
      
      		iterator find(const K& key)
      		{
      			return _t.Find(key);
      		}
      
      		V& operator[](const K& key)
      		{
      			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
      			return ret.first->second;
      		}
      
      	private:
      		RBTree<K, pair<const K,V>, MapKeyofT> _t;
      	};
      }
      

      4. RbTree的完整源码

      #pragma once
      
      //枚举类型,枚举红黑树的颜色红色和黑色
      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)
      	{}
      };
      
      //迭代器的实现
      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)
      	{}
      
      	__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& iterator)
      		:_node(iterator._node)
      	{}
      
      	//重载*
      	Ref operator*()
      	{
      		return _node->_data;
      	}
      	
      	//重载->
      	Ptr operator->()
      	{
      		return &(_node->_data);
      	}
      
      	//重载!=
      	bool operator!=(const Self& s)
      	{
      		return _node != s._node;
      	}
      
      	//重载 ++it
      	Self& operator++()
      	{
      		//本质上是找中序遍历的下一个元素——分为两种情况:right == nullptr 和 right != nullptr
      
      		if (_node->_right) // right != nullptr
      		{
      			Node* parent = _node->_right;
      			
      			while (parent->_left)
      			{
      				parent = parent->_left;
      			}
      			_node = parent;
      		}
      		else //right == nullptr
      		{
      
      			Node* parent = _node->_parent;
      			Node* cur = _node;
      
      			while (parent && cur == parent->_right)
      			{
      				cur = cur->_parent;
      				parent = parent->_parent;
      			}
      			_node = parent;
      		}
      		return *this;
      	}
      
      	//重载 --it
      	Self& operator--()
      	{
      		if (_node->_left) // left != nullptr
      		{
      			Node* parent = _node->_left;
      
      			while (parent->_right)
      			{
      				parent = parent->_right;
      			}
      			_node = parent;
      		}
      		else //left == nullptr
      		{
      
      			Node* parent = _node->_parent;
      			Node* cur = _node;
      
      			while (parent && cur == parent->_left)
      			{
      				cur = cur->_parent;
      				parent = parent->_parent;
      			}
      			_node = parent;
      		}
      		return *this;
      	}
      
      };
      
      //创建红黑树
      template<class K, class T ,class KeyOfT>
      class RBTree
      {
      	typedef RBTreeNode<T> Node;
      public:
      	//析构
      	~RBTree()
      	{
      		_Destroy(_root);
      		_root = nullptr;
      	}
      
      	//迭代器的实现
      	typedef __RBTreeIterator<T, T&, T*> iterator;
      	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
      
      	iterator begin()
      	{
      		Node* cur = _root;
      
      		while (cur && cur->_left)
      		{
      			cur = cur->_left;
      		}
      		return iterator(cur);
      	}
      
      	iterator end()
      	{
      		return iterator(nullptr);
      	}
      
      	const_iterator begin() const 
      	{
      		Node* cur = _root;
      
      		while (cur && cur->_left)
      		{
      			cur = cur->_left;
      		}
      		return iterator(cur);
      	}
      
      	const_iterator end() const
      	{
      		return iterator(nullptr);
      	}
      
      	//查询
      	iterator Find(const K& key)
      	{
      		KeyOfT kot;
      		Node* cur = _root;
      		while (cur)
      		{
      			if (key > kot(cur->_data))
      				cur = cur->_right;
      			else if (key < kot(cur->_data))
      				cur = cur->_left;
      			else
      				return iterator(cur);
      		}
      		return iterator(nullptr);
      	}
      
      	//插入
      	pair<iterator, bool> Insert(const T& data)
      	{
      		//搜索树的插入过程
      		if (_root == nullptr)
      		{
      			_root = new Node(data);
      			_root->_col = BLACK;
      			return make_pair(iterator(_root), true);
      		}
      
      		KeyOfT kot;
      
      		Node* parent = nullptr;
      		Node* cur = _root;
      		while (cur)
      		{
      			if (kot(data) > kot(cur->_data))
      			{
      				parent = cur;
      				cur = cur->_right;
      			}
      			else if (kot(data) < kot(cur->_data))
      			{
      				parent = cur;
      				cur = cur->_left;
      			}
      			else
      			{
      				return make_pair(iterator(cur), false);
      			}
      		}
      
      		//链接节点
      		cur = new Node(data);
      		Node* newnode = cur;
      
      		if (kot(parent->_data) > kot(data))
      			parent->_left = cur;
      		else
      			parent->_right = cur;
      
      		cur->_parent = parent;//链接父节点
      
      		while (parent && parent->_col == RED)
      		{
      			Node* grandfather = parent->_parent;
      
      			if (grandfather->_left == parent)
      			{
      				Node* uncle = grandfather->_right;
      
      				if (uncle && uncle->_col == RED)// 情况1:u存在且为红,变色处理,并继续往上处理
      				{
      					parent->_col = BLACK;
      					uncle->_col = BLACK;
      					grandfather->_col = RED;
      					//继续向上调整
      					cur = grandfather;
      					parent = cur->_parent;
      				}
      				else // 情况2 + 3:u不存在/u存在且为黑 --- 旋转 + 变色
      				{
      					//     g
      					//   p   u
      					// c 
      					if (cur == parent->_left)
      					{
      						//右单旋
      						RotateR(grandfather);
      						parent->_col = BLACK;
      						grandfather->_col = RED;
      					}
      					else
      					{
      						//     g
      						//   p   u
      						//     c
      						//先对parent节点进行左单旋,在对grandfather进行右单旋
      						RotateL(parent);
      						RotateR(grandfather);
      						//调整颜色
      						cur->_col = BLACK;
      						grandfather->_col = RED;
      					}
      					break;
      				}
      			}
      			else // (grandfather->_right == parent)
      			{
      				// u存在且为红,变色处理,并继续往上处理
      				//    g
      				//  u   p
      				//        c
      				Node* uncle = grandfather->_left;
      
      				if (uncle && uncle->_col == RED)
      				{
      					parent->_col = BLACK;
      					uncle->_col = BLACK;
      					grandfather->_col = RED;
      
      					cur = grandfather;
      					parent = cur->_parent;
      				}
      				else
      				{
      					//    g
      					//  u   p
      					//    c
      					if (cur == parent->_left)
      					{
      						RotateR(parent);
      						RotateL(grandfather);
      
      						//调整颜色
      						cur->_col = BLACK;
      						grandfather->_col = RED;
      
      					}
      					else
      					{
      						//    g
      						//  u   p
      						//        c
      						RotateL(grandfather);
      						parent->_col = BLACK;
      						grandfather->_col = RED;
      					}
      					break;
      				}
      			}
      		}
      		_root->_col = BLACK;
      
      		return make_pair(iterator(newnode), true);
      	}
      
      	//中序遍历
      	void InOrder()
      	{
      		_InOrder(_root);
      		cout << endl;
      	}
      
      	//判断是否为红黑树
      	bool IsBalance()
      	{
      		if (_root && _root->_col == RED)
      		{
      			cout << "根节点颜色是红色的" << endl;
      			return false;
      		}
      		int benchmark = 0;
      		Node* cur = _root;
      
      		while (cur)
      		{
      			if (cur->_col == BLACK)
      				benchmark++;
      			cur = cur->_left;
      		}
      
      		//判断是否有连续红色节点
      		return _Check(_root, 0, benchmark);
      	}
      
      	int Height()
      	{
      		return _Height(_root);
      	}
      
      private:
      	//红黑树的高度计算
      	int _Height(Node* root)
      	{
      		if (root == nullptr)
      			return 0;
      		int leftHight = _Height(root->_left);
      		int rightHight = _Height(root->_right);
      
      		return leftHight > rightHight ? leftHight + 1 : rightHight + 1;
      	}
      
      	bool _Check(Node* root, int blackNum, int benchmark)
      	{
      		if (root == nullptr)
      		{
      			if (blackNum != benchmark)
      			{
      				cout << "某条路径黑色节点的数量不相等" << endl;
      				return false;
      			}
      			return true;
      		}
      
      		if (root->_col == BLACK)
      			blackNum++;
      
      		if (root->_col == RED
      			&& root->_parent
      			&& root->_parent->_col == RED)
      		{
      			cout << "存在连续的红色节点" << endl;
      			return false;
      		}
      
      		return _Check(root->_left, blackNum, benchmark)
      			&& _Check(root->_right, blackNum, benchmark);
      	}
      
      	void _Destroy(Node* root)
      	{
      		if (root == nullptr)
      			return;
      		_Destroy(root->_left);
      		_Destroy(root->_right);
      
      		delete root;
      	}
      
      	void _InOrder(Node* root)
      	{
      		if (root == nullptr)
      			return;
      		KeyOfT kot;
      		_InOrder(root->_left);
      		cout << kot(root->_data) << " ";
      		_InOrder(root->_right);
      	}
      	//左单旋
      	void RotateL(Node* parent)
      	{
      		Node* subR = parent->_right;
      		Node* subRL = subR->_left;
      
      		parent->_right = subRL;
      
      		//这里我们需要注意一下,subRL有可能为空
      		if (subRL)
      			subRL->_parent = parent;
      
      		Node* ppnode = parent->_parent;
      		subR->_left = parent;
      		parent->_parent = subR;
      
      		//判断父节点是否为_root节点
      		if (parent == _root) {
      			_root = subR;
      			_root->_parent = nullptr;
      		}
      		else {
      			if (parent == ppnode->_left)
      				ppnode->_left = subR;
      			else
      				ppnode->_right = subR;
      			subR->_parent = ppnode;
      		}
      	}
      
      	//右单旋
      	void RotateR(Node* parent)
      	{
      		Node* subL = parent->_left;
      		Node* subLR = subL->_right;
      
      		parent->_left = subLR;
      		if (subLR)
      			subLR->_parent = parent;
      
      		//保存parent节点的_parent节点
      		Node* ppnode = parent->_parent;
      
      		subL->_right = parent;
      		parent->_parent = subL;
      
      		//判断parent节点是否为_root节点
      		if (parent == _root)
      		{
      			_root = subL;
      			_root->_parent = nullptr;
      		}
      		else
      		{
      			if (parent == ppnode->_left)
      				ppnode->_left = subL;
      			else
      				ppnode->_right = subL;
      			subL->_parent = ppnode;
      		}
      	}
      
      private:
      	Node* _root = nullptr;
      };
      

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://chenjiale.blog.csdn.net/article/details/130906347,作者:星河万里᭄ꦿ࿐,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:第一季:14redis持久化【Java面试题】

      下一篇:给定一棵树的头节点head,原本是一棵正常的树, 现在,在树上多加了一条冗余的边, 请找到这条冗余的边并返回。

      相关文章

      2025-05-19 09:04:53

      查看RISC-V版本的gcc中默认定义的宏

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53
      c++ , linux
      2025-05-19 09:04:14

      《剑指Offer》搜索算法题篇——更易理解的思路~

      《剑指Offer》搜索算法题篇——更易理解的思路~

      2025-05-19 09:04:14
      算法
      2025-05-19 09:04:14

      复杂度的OJ练习

      复杂度的OJ练习

      2025-05-19 09:04:14
      代码 , 复杂度 , 思路 , 数组 , 算法
      2025-05-19 09:04:14

      背包问题——“0-1背包”,“完全背包”(这样讲,还能不会?)

      背包问题——“0-1背包”,“完全背包”(这样讲,还能不会?)

      2025-05-19 09:04:14
      动态规划 , 算法
      2025-05-16 09:15:17

      多源BFS问题(2)_飞地的数量

      多源BFS问题(2)_飞地的数量

      2025-05-16 09:15:17
      bfs , grid , 单元格 , 算法
      2025-05-16 09:15:17

      BFS解决最短路问题(4)_为高尔夫比赛砍树

      BFS解决最短路问题(4)_为高尔夫比赛砍树

      2025-05-16 09:15:17
      BFS , lt , 复杂度 , 算法
      2025-05-16 09:15:17

      递归,搜索,回溯算法(3)之穷举,暴搜,深搜,回溯,剪枝

      递归,搜索,回溯算法(3)之穷举,暴搜,深搜,回溯,剪枝

      2025-05-16 09:15:17
      回溯 , 子集 , 数组 , 算法 , 递归
      2025-05-16 09:15:17

      多源BFS问题(4)_地图分析

      多源BFS问题(4)_地图分析

      2025-05-16 09:15:17
      单元格 , 算法 , 网格 , 距离
      2025-05-16 09:15:10

      BFS解决FloodFill算法(3)_岛屿的最大面积

      BFS解决FloodFill算法(3)_岛屿的最大面积

      2025-05-16 09:15:10
      grid , 复杂度 , 算法
      2025-05-14 10:33:31

      【数据结构】第一章——绪论(2)

      【数据结构】第一章——绪论(2)

      2025-05-14 10:33:31
      函数 , 实现 , 打印 , 理解 , 算法 , 输入 , 输出
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5248219

      查看更多

      最新文章

      复杂度的OJ练习

      2025-05-19 09:04:14

      【数据结构】详细介绍串的简单模式匹配——朴素模式匹配算法

      2025-05-14 10:33:31

      超级好用的C++实用库之sha256算法

      2025-05-14 10:33:25

      【C/C++算法】从浅到深学习--- 简单模拟算法(图文兼备 + 源码详解)

      2025-05-06 09:19:30

      软件设计师教程(第5版)第4章 操作系统知识(更新中)

      2025-04-15 09:25:57

      选择排序(附代码详解)(C语言)

      2025-04-15 09:18:54

      查看更多

      热门文章

      Lambda函数

      2023-02-08 10:33:56

      Python:关于有序序列元素查找

      2023-02-13 07:38:09

      QT中多线程的使用

      2023-02-07 10:34:04

      0030 简单的四则运算 c/c++

      2023-03-21 10:39:47

      C++虚函数知识点总结

      2023-02-21 06:21:46

      数据结构与算法之七 栈

      2022-11-17 12:37:20

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      JavaScript 数组操作与排序算法详解

      【C++动态规划 最长公共子序列】1035. 不相交的线|1805

      单调队列代码模板

      vtk_C++入门

      c++获取可执行程序的exe名称

      Province_C_C++_A/2/跳蚱蜢

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号