Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C++进阶篇】红黑树的封装(赋源码)

【C++进阶篇】红黑树的封装(赋源码)

作者头像
熬夜学编程的小王
发布于 2025-05-28 00:39:18
发布于 2025-05-28 00:39:18
8900
代码可运行
举报
文章被收录于专栏:编程小王编程小王
运行总次数:0
代码可运行
红黑树逆袭:手把手拆解STL容器set/map底层引擎

一. 红黑树封装

核心思路:一颗红黑树通过泛型编程思想分别实现set和map。既然是红黑树,依然要满足红黑树和二叉搜索树的规则。

1.1 基本结构

  • 红黑树模版结构:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 枚举值表示颜色
enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	T _data;

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	Colour _col;

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

template<class K, class T, class KeyOfT>//T是插入数据的类型
class RBTree
{
	typedef RBTreeNode<T> Node;//红黑树节点的结构
public:
private:
	Node* _root = nullptr;
};

通过模版参数T决定存储数据的类型,可能读者觉得第一个K有点冗余,它主要用于Find函数的参数, 默认key值要支持比较大小,而string类型等不支持,咱们就可以使用仿函数自己来实现可以支持比较大小的仿函数。

1.1.1 插入
  • 示例代码:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pair<Iterator,bool> Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(Iterator(_root,_root),true);
	}

	KeyOfT kot;
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (kot(cur->_data) < kot(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(cur->_data) > kot(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return make_pair(Iterator(cur, _root), false);
		}
	}

	cur = new Node(data);
	Node* newnode = cur;
	cur->_col = RED;
	if (kot(parent->_data) < kot(data))
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	cur->_parent = parent;

	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (grandfather->_left == parent)
		{
			//   g
			// p   u
			//
			Node* uncle = grandfather->_right;
			// uncle存在且为红
			if (uncle && uncle->_col == RED)
			{
				// 变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else  // uncle不存在,或者存在且为黑
			{
				if (cur == parent->_left)
				{
					// 旋转+变色
					//    g
					//  p   u
					//c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					// 旋转+变色
					//    g
					//  p   u
					//    c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
		else
		{
			//	 g
			// u   p
			Node* uncle = grandfather->_left;
			// 叔叔存在且为红,-》变色即可
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else // 叔叔不存在,或者存在且为黑
			{
				// 情况二:叔叔不存在或者存在且为黑
				// 旋转+变色
				//    g
				//  u   p
				// c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//    g
					//  u   p
					// c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
	}

	_root->_col = BLACK;

	return make_pair(Iterator(newnode, _root), true);
}

插入数据之后不满足红黑树的规则需要进行旋转,插入的数据由上一层传递给本层,插入的逻辑与上节一致,唯一不同的是它支持插入任意数据的类型,返回值,插入成功返回pair类型数据,如果key已经存在则返回可以已经存在的迭代器和false,否则返回新插入数据的迭代器和true。使用key值比较大小时,需套一层仿函数,原因同上。

1.1.2 查找

通过key值比较,上层都会传入自己的仿函数给KeyOfT,提取键值,从而实现比较大小。返回也有讲究,为什么返回Node* 主要用于map实现修改value值的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		KeyOfT kot;  
		if (kot(cur->_data) < key) 
		{
			cur = cur->_right;
		}
		else if (kot(cur->_data) > key)  
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

1.2 迭代器(重点)

遍历红黑树,使用前序遍历。即 左 - 根 - 右,begin节点为最左节点。

1.2.1 开始与结尾节点迭代器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Iterator Begin()//中序第一个是最左节点
{
	Node* cur = _root;
	while (cur && cur->_left)
	{
		cur = cur->_left;
	}

	return Iterator(cur,_root);
}

遍历至结尾,即空节点为end。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Iterator End()
{
	return Iterator(nullptr,_root);
}

const迭代器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ConstIterator Begin()const
{
	Node* cur = _root;
	while (cur && cur->_left)
	{
		cur = cur->_left;
	}

	return ConstIterator(cur, _root);
}
ConstIterator End() const
{
	return ConstIterator(nullptr, _root);
}
1.2.2 迭代器基本结构:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
	Node* _root;

	RBTreeIterator(Node* node, Node* root)
		:_node(node)
		, _root(root)
	{}
}

使用节点指针和根节点指针构造迭代器。

typedef RBTreeIterator<T,T&,T*> Iterator; typedef RBTreeIterator<T, const T&, const T*> ConstIterator;

1.2.3 重载operator*()

用于取节点指针的数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Ref operator*()
{
	return _node->_data;
}
1.2.4 重载operator->()

用于取节点指针数据的地址。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Ptr operator->()
{
	return &_node->_data;
}
1.2.5 前置++

对节点进行++,分析场景如下:

  • 如果当前节点的左孩子不为空,则直接找左孩子的最左节点。
  • 如果左为空且右孩子不为空,则找右孩子的最左节点。
  • 如果左为空,且右也为空,只需要往回找,孩子是parent节点左孩子的第一个。 示例代码如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Self& operator++()
{
	if (_node->_right)
	{
		Node* minleft = _node->_right;
		//while (minleft->_left)
		while (minleft && minleft->_left)
		{
			minleft = minleft->_left;
		}

		_node = minleft;
	}
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_right)
		{
			cur = parent;
			parent = parent->_parent;
		}

		_node = parent;
	}

	return *this;
}
1.2.6 operator–()

分析:

  • 当前节点的左子树不为空,进入右子树的右孩子找最右节点。
  • 当前节点左子树为空,需要向上回溯找孩子是父亲右的第一个节点。
  • 上述两种场景都不会访问到最大节点,需要特殊判断。 示例代码:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Self& operator--()
{
	if (_node == nullptr) // end()
	{
		// --end(),特殊处理,走到中序最后一个结点,整棵树的最右结点
		Node* rightMost = _root;
		while (rightMost && rightMost->_right)
		{
			rightMost = rightMost->_right;
		}
		_node = rightMost;
	}
	else if (_node->_left)
	{
		// 左子树不为空,中序左子树最后一个
		Node* rightMost = _node->_left;
		while (rightMost->_right)
		{
			rightMost = rightMost->_right;
		}
		_node = rightMost;
	}
	else
	{
		// 孩子是父亲右的那个祖先
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_left)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}

	return *this;
}

下面以一个具体的实例来详细说明一下这个特殊情况。

  1. 例如当前节点cur指向50,它的parent指向40,进入循环判断,parent在但cur节点不是parent的左,跳出循环,_node指向parent节点即40,未访问到50这个节点。
  2. _node指向40,左不为空进入循环,无法进入右分支,更访问不到最大节点。
  3. 所以当_node为空时,特殊判断查找右子树的最右节点,即是最大节点。
  • 重载operator!=()和operator==() 直接判断节点的指针是否相等即可。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool operator!=(const Self& s)
{
	return _node != s._node;
}
bool operator==(const Self& s)
{
	return _node == s._node;
}

二. 封装set/map

2.1 封装set

RBTree<K,const K,SetKeyOfT> _rbtree; 通过第二个参数,实现存储不同数据的类型。 直接调用接口即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace SET
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

		iterator begin()
		{
			return _rbtree.Begin();
		}
		iterator end()
		{
			return _rbtree.End();
		}

		const_iterator begin()const
		{
			return _rbtree.Begin();
		}
		const_iterator end()const
		{
			return _rbtree.End();
		}

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

		iterator find(const K& key)
		{
			return _rbtree.Find(key);
		}
	private:
		RBTree<K,const K,SetKeyOfT> _rbtree;
	};
	}

因为set的key值不允许修改,所以被const修饰。

2.2 map封装

2.2.1 map[]

map[]底层是用insert实现的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
V& operator[](const K& key)
{
	pair<iterator, bool> ret = _rbtree.Insert({ key,V() });
	return ret.first->second;
}

返回value值得引用允许修改value的值。 与set类似,直接调用接口即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace MAP
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<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>::ConstIterator const_iterator;

		iterator begin()
		{
			return _rbtree.Begin();
		}
		iterator end()
		{
			return _rbtree.End();
		}

		const_iterator begin()const
		{
			return _rbtree.Begin();
		}
		const_iterator end()const
		{
			return _rbtree.End();
		}

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

源码:set/map封账-Gitee

三. 最后

本文详细阐述了基于红黑树实现STL容器set/map的核心技术。首先构建红黑树基础结构,通过模板参数支持泛型数据存储,插入操作采用红黑树平衡算法维护树结构,查找通过仿函数提取键值。迭代器重点实现中序遍历逻辑,包含边界处理及双向遍历操作符重载。在容器封装层面,set直接存储键值并限制修改,map通过键值对存储实现operator[]功能,二者均复用红黑树核心逻辑,通过模板特化与仿函数机制实现高效数据管理

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++】红黑树的应用(封装map和set)
红黑树类的实现参考上一篇文章:【C++】红黑树的全面探索和深度解析-CSDN博客
IsLand1314
2024/10/15
1290
【C++】红黑树的应用(封装map和set)
C++: 使用红黑树模拟实现STL中的map和set
这里额外增加了一个header指针, 有了这个指针可以更方便的找到根节点, 并且可以比较容易的实现反向遍历, 可以看到set和map都是双向迭代器, 但是缺点就是需要不断的维护begin()这个要返回的节点, 所以我们这里为了也是先正反向迭代器, 也避免过于麻烦, 我们暂且讲_root也一并传过来, 方便我们找到根节点
用户11317877
2024/10/16
1820
C++: 使用红黑树模拟实现STL中的map和set
【C++】封装红黑树实现map和set
SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等几个头文件 中。 map和set的实现结构框架核心部分截取出来如下:
ZLRRLZ
2025/08/04
1090
【C++】封装红黑树实现map和set
【C++/STL】map和set的封装(红黑树)
💬 hello! 各位铁子们大家好哇。 今日更新了map和set封装的相关内容
秦jh
2024/08/07
1690
【C++/STL】map和set的封装(红黑树)
【C++】封装红黑树实现的map和set
SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等⼏个头⽂件
用户11290673
2025/01/13
1000
【C++】封装红黑树实现的map和set
封装红黑树实现mymap和myset
SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等⼏个头⽂件 中。
用户11375356
2024/12/24
1150
封装红黑树实现mymap和myset
【C++】基于红黑树封装set和map
前面我们介绍了set和map的底层细节,在上篇文章中也简单实现了红黑树,而我们知道set和map的底层就是依靠红黑树实现的,那么在本文我们将学习如何基于红黑树来封装出set和map。 本篇文章会带你深入理解C++的三大特性之一——封装。
_小羊_
2024/10/16
1500
【C++】基于红黑树封装set和map
【C++修炼之路】21.红黑树封装map和set
与之前的双参数<class K, class V>相比,改良之后的T作为了全部的数据域,即T也可以代表pair类型。
每天都要进步呀
2023/03/28
3770
【C++修炼之路】21.红黑树封装map和set
【C++】map和set的封装
map与set在STL中实现是一样的 对于value_type,map的第二个模板参数是pair,而set的第二个模板参数是key 这样写是为了map和set使用同一颗红黑树去复用map和set
lovevivi
2023/10/16
2030
【C++】map和set的封装
【c++】map和set的模拟实现
set和map是基于红黑树实现的,但是传的参数不一样,如果硬要按上面的参数匹配,我们需要两个红黑树,我们前面实现的红黑树都是pair实现的,下面我们看库中的实现方法:
用户11029103
2024/05/24
1000
【c++】map和set的模拟实现
【红黑树封装map和set】—— 我与C++的不解之缘(二十六)
部分源码如上,我们通过源码可以看到源码中rb_tree使用了泛型思维实现;其中rb_tree是实现key搜索场景还是实现key/value的搜索场景不是写死的,而是通过了第二个模版参数来决定的。
星辰与你
2025/03/23
920
【红黑树封装map和set】—— 我与C++的不解之缘(二十六)
【c++】map和set&&AVL树&&红黑树详解&&模拟实现&&map和set封装
在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?
用户10925563
2024/06/04
3970
【c++】map和set&&AVL树&&红黑树详解&&模拟实现&&map和set封装
C++:map和set的封装
关于红黑树的模拟实现,大家不清楚的先去看看博主的博客再来看这篇文章,因为set和map的封装底层都是利用用的红黑树。所以这里不会过多介绍红黑树的相关内容,而更多的是去为了契合STL中的红黑树去进行改造,让封装的set和map能够去复用我们的这份代码
小陈在拼命
2024/05/04
1640
C++:map和set的封装
初识C++ · 基于红黑树封装map + set
这部分是挺有难度的,因为套了好几层关系,涉及到关系层大概有4层左右,但是呢,多花点时间即可,更重要的还是细心部分,其次就是逐个的去捋清楚每层的关系即可,细心 + 耐心,这里就通关了。
_lazy
2024/10/16
1370
初识C++ · 基于红黑树封装map + set
【C++】红黑树 --- map/set 底层
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black.
YoungMLet
2024/03/01
2800
【C++】红黑树 --- map/set 底层
【C++进阶】map与set的封装实践
通过观察这些typedef就可以看到,map和set的封装基本都是套用的红黑树的迭代器来封装实现的,所以我们的map和set也可以通过完成的红黑树来进行封装。
用户11305458
2024/10/09
1260
【C++进阶】map与set的封装实践
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树
红黑树,是一种二叉搜索树,但在每个节点上增加了一个存储位表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
枫叶丹
2024/07/12
1250
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树
【C++深度探索】红黑树实现Set与Map的封装
  前面我们学习过map、set、multimap、multiset的使用,这四种容器都是使用红黑树作为其底层结构。红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(
大耳朵土土垚
2024/08/13
1540
【C++深度探索】红黑树实现Set与Map的封装
在代码的红与黑间——红黑树实现 map 和 set 的美丽旅程
红黑树,这种平衡二叉搜索树以其独特的颜色标记和平衡机制,成为实现 map 和 set 等容器的核心。它在保证有序性和高效性之间取得了微妙的平衡,为C++标准库带来了无与伦比的查找效率。本篇博客将带你走进红黑树的世界,从原理到实现,揭开其在 map 和 set 中的应用奥秘。
suye
2024/11/19
2240
C++效率掌握之STL库:map && set底层剖析及迭代器万字详解
map、set 的封装可以说是很天才的底层结构了,本篇将对其结构进行详细的解析,虽然会很复杂且难以理解,但是学完成就感满满,而且对底层理解和面试很有帮助
DARLING Zero two
2025/05/15
1110
C++效率掌握之STL库:map && set底层剖析及迭代器万字详解
相关推荐
【C++】红黑树的应用(封装map和set)
更多 >
领券
一站式MCP教程库,解锁AI应用新玩法
涵盖代码开发、场景应用、自动测试全流程,助你从零构建专属AI助手
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验