【C++进阶】封装红⿊树实现mymap和myset

目录

本节学习目标

[1 SGI‑STL 源码框架分析](#1 SGI‑STL 源码框架分析)

[1.1 源码模板拆解](#1.1 源码模板拆解)

[set 源码片段](#set 源码片段)

[map 源码片段](#map 源码片段)

[rb_tree 红黑树模板](#rb_tree 红黑树模板)

✨核心设计思想(面试重点)

[2 模拟实现框架设计](#2 模拟实现框架设计)

[2.1 KeyOfT 仿函数](#2.1 KeyOfT 仿函数)

[2.2 分层封装](#2.2 分层封装)

红黑树结点定义

[3 红黑树迭代器实现(难点)](#3 红黑树迭代器实现(难点))

[3.1 迭代器模板](#3.1 迭代器模板)

迭代器规则梳理

[RBTree 内部定义两套迭代器](#RBTree 内部定义两套迭代器)

[4 RBTree 的 Insert 接口改造](#4 RBTree 的 Insert 接口改造)

[5 封装 bit::set](#5 封装 bit::set)

[6 封装 bit::map,重点 operator \[\]](#6 封装 bit::map,重点 operator [])

📔本篇核心考点总结(面试必背)

面试简答


本节学习目标

  1. 看懂 SGI‑STL 中map/set底层复用同一套rb_tree红黑树的源码框架;理解为什么一颗红黑树可以同时支持 set(纯 key)和 map(key‑value)。
  2. 掌握KeyOfT 仿函数核心设计:红黑树结点存的数据类型 T 不确定,通过仿函数从 T 里面提取 key 做比较。
  3. 手写红黑树迭代器:重点实现++、--,基于红黑树中序遍历 规则;理解begin()、end()。
  4. 区分普通迭代器、const 迭代器;理解map的pair<const K,V>,key 不能修改,value 可以修改;set 全部元素不可修改。
  5. 实现map::operator[],依赖 Insert 返回pair<iterator,bool>。
  6. 完整实现bit::set、bit::map封装类。

1 SGI‑STL 源码框架分析

STL 的set、map并不是各自独立实现一棵树 ,二者复用同一套rb_tree红黑树。

1.1 源码模板拆解

set 源码片段
cpp 复制代码
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
    typedef Key key_type;
    typedef Key value_type; // set:key与value_type完全一样
private:
    // identity<value_type>:仿函数,直接把key本身返回
    typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;
    rep_type t;
};
  • set 结点存的是Key,value_type=Key。
  • identity仿函数:输入 T,直接返回 T 本身,用于提取 key。
map 源码片段
cpp 复制代码
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
    typedef Key key_type;
    typedef T mapped_type;
    typedef pair<const Key, T> value_type; // map结点存pair<const Key,T>
private:
    // select1st<value_type>仿函数:取出pair的first作为key
    typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;
    rep_type t;
};
  • map 结点存储pair<const Key,T>;pair 的 first 是 const K,禁止修改 key,防止破坏红黑树有序性。
  • select1st仿函数:接收 pair 对象,返回pair.first,拿到 key。
rb_tree 红黑树模板
cpp 复制代码
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
class rb_tree
{
protected:
    typedef __rb_tree_node<Value> rb_tree_node;
public:
    pair<iterator,bool> insert_unique(const value_type& x);
    iterator find(const key_type& x);
private:
    link_type header; // 哨兵头结点(STL源码特有)
};

template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
    Value value_field; // 结点真正存储的数据
};

✨核心设计思想(面试重点)

  1. 红黑树第二个模板参数Value:决定结点里面存什么 。
    • set 实例化 rb_tree:Value = Key,结点存普通 key;
    • map 实例化 rb_tree:Value = pair<const K,V>,结点存键值对。
  2. KeyOfValue是仿函数:统一从结点存储的 Value 对象中提取出用于比较的 key 。
    • set:传入identity,输入 key 返回 key;
    • map:传入select1st,输入 pair 返回 pair.first。
  3. rb_tree 第一个模板参数Key:给find、erase使用,find 传参只需要 key,不需要传完整 value 对象。

吐槽:STL 源码命名混乱,rb_tree 的Value不是 map 里面的 mapped‑value,是结点完整存储的数据 value_type。
STL 源码 rb_tree 有header哨兵头结点,begin 指向 header 的 left(树最小结点),end 指向 header;我们手写简化版本直接用nullptr代表 end。

2 模拟实现框架设计

2.1 KeyOfT 仿函数

问题:红黑树模板类RBTree<K,T,KeyOfT>,T 不确定:

  • set:T 就是 K;
  • map:T 是pair<const K,V>。 做插入、查找比较时,不能直接拿 T 比较,要提取出 key,仿函数干提取 key 这件事。
cpp 复制代码
// set用的仿函数:直接返回key本身
struct SetKeyOfT
{
	const K& operator()(const K& key)
	{
		return key;
	}
};

// map用的仿函数:提取pair的first(key)
struct MapKeyOfT
{
	const K& operator()(const pair<K, V>& kv)
	{
		return kv.first;
	}
};

pair 原生<运算符:先比较 first,如果 first 相等才比较 second。 如果直接拿 pair 做整对象比较,会把 value 也参与比较;红黑树只允许拿 key 做比较,所以必须 KeyOfT 只拿 key 比较。

2.2 分层封装

  1. RBTree<K,T,KeyOfT>底层红黑树模板,通用,同时给 map 和 set 复用。
  2. bit::set<K>:内部私有成员RBTree<K,const K,SetKeyOfT> _t;

set 的 T 用const K,迭代器解引用是 const,禁止修改元素,防止破坏树。

  1. bit::map<K,V>:内部私有成员RBTree<K,pair<const K,V>,MapKeyOfT> _t;

pair 的 first 是 const K,key 不可修改,second (V) 可以修改。

红黑树结点定义

cpp 复制代码
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)
		,_col(RED)
	{}
};

3 红黑树迭代器实现(难点)

set/map 迭代器本质是中序遍历迭代器 ,++走到中序下一个结点,--走到中序上一个结点。 底层不是链表,结点左右孩子指针,封装迭代器类,内部保存结点指针,重载++ -- * -> == !=。

3.1 迭代器模板

cpp 复制代码
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)
	{}

	// ++前置,中序找下一个结点
	Self& operator++()
	{
		if (_node->_right != nullptr)
		{
			//右子树不为空:下一个 = 右子树的最左结点
			Node* leftMost = _node->_right;
			while (leftMost->_left)
			{
				leftMost = leftMost->_left;
			}
			_node = leftMost;
		}
		else
		{
			//右为空,向上找祖先:找到第一个孩子是父亲左的祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	// --前置,中序找前一个结点
	Self& operator--()
	{
		if (_node == nullptr)
		{
			// --end()特殊处理:end是nullptr,要跳转到整棵树最右结点
			Node* rightMost = _root;
			while (rightMost && rightMost->_right)
			{
				rightMost = rightMost->_right;
			}
			_node = rightMost;
		}
		else if (_node->_left != nullptr)
		{
			//左不为空:下一个是左子树的最右结点
			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;
	}

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

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

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

迭代器规则梳理

  1. begin():中序第一个结点,整棵树最左结点。
  2. end():简化实现用nullptr;STL 源码是哨兵 header 结点。
  3. ++逻辑
    • 如果当前结点右孩子不为空:下一个结点是右子树的最左结点;
    • 如果右孩子为空:向上循环找祖先;当cur != parent->_right停止,parent 就是下一个结点;找到根 parent=nullptr,变成 end。
  4. --逻辑
    • 如果迭代器是end()(_node=nullptr):特殊处理,直接跳转到整棵树最右结点;
    • 如果左孩子不为空:下一个是左子树最右结点;
    • 左孩子为空:向上找祖先,找到第一个cur != parent->_left的祖先。

RBTree 内部定义两套迭代器

cpp 复制代码
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	//普通迭代器 Ref=T&, Ptr=T*
	typedef RBTreeIterator<T, T&, T*> Iterator;
	//const迭代器 Ref=const T&, Ptr=const T*
	typedef RBTreeIterator<T, const T&, const T*> ConstIterator;

	Iterator Begin()
	{
		Node* leftMost = _root;
		while(leftMost && leftMost->_left)
			leftMost = leftMost->_left;
		return Iterator(leftMost, _root);
	}
	Iterator End()
	{
		return Iterator(nullptr, _root);
	}

	ConstIterator Begin() const
	{
		Node* leftMost = _root;
		while(leftMost && leftMost->_left)
			leftMost = leftMost->_left;
		return ConstIterator(leftMost, _root);
	}
	ConstIterator End() const
	{
		return ConstIterator(nullptr, _root);
	}
//......
};

关键点:

  • set 底层存const K,所以不管普通迭代器还是 const 迭代器,解引用都是 const 引用,set 元素禁止修改。
  • map 底层存pair<const K,V>,first是 const 不可修改,second可以修改。

4 RBTree 的 Insert 接口改造

原来的 Insert 返回 bool;现在必须返回pair<Iterator,bool> ,给 map 的operator[]使用。

返回值含义:

  • .first:插入元素的迭代器;如果 key 重复,返回已经存在结点的迭代器。
  • .second:bool,true 插入成功;false 代表 key 已经存在,插入失败。
cpp 复制代码
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
		{
			//key重复,插入失败,返回已存在结点迭代器
			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(parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			if(uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if(cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else
		{
			Node* uncle = grandfather->_left;
			if(uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if(cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}
	_root->_col = BLACK;
	return make_pair(Iterator(newnode,_root), true);
}

Find 接口:传入 key,返回 Iterator,找不到返回 End ()。

cpp 复制代码
Iterator Find(const K& key)
{
	KeyOfT kot;
	Node* cur = _root;
	while(cur)
	{
		if(kot(cur->_data) < key)
			cur = cur->_right;
		else if(kot(cur->_data) > key)
			cur = cur->_left;
		else
			return Iterator(cur,_root);
	}
	return End();
}

5 封装 bit::set

cpp 复制代码
namespace bit
{
	template<class K>
	class set
	{
	private:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		//底层T使用const K,禁止修改元素
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator 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);
		}
	private:
		RBTree<K, const K, SetKeyOfT> _t;
	};
}

set 注意点:

  1. 底层const K,迭代器解引用是 const,不能修改集合里面的元素;
  2. 不支持operator[],set 没有这个接口。

6 封装 bit::map,重点 operator \[\]

cpp 复制代码
namespace bit
{
	template<class K, class V>
	class map
	{
	private:
		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>::ConstIterator 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);
		}

		//⭐operator[]核心实现
		V& operator[](const K& key)
		{
			//如果key不存在,插入key+V()默认对象;存在则拿到已有迭代器
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

operator[]三合一功能回顾:

  1. key 不存在:插入pair(key,V()),返回 value 引用;
  2. key 已经存在:不插入,直接返回已有 value 引用;
  3. 返回引用,支持读取、修改 value。
    map 重要限制:pair<const K,V>,不能修改 it->first(key),只能修改 it->second。

📔本篇核心考点总结(面试必背)

  1. STL 中map和set底层复用同一套红黑树 rb_tree:
    • set 结点存Key;map 结点存pair<const Key,T>;
    • 通过KeyOfT仿函数从存储的数据对象提取 key,做比较。
  2. KeyOfT 仿函数:解决 T 类型不确定,统一提取 key,不能拿 pair 整体做比较。
  3. 红黑树迭代器本质中序遍历迭代器 ;++、--不需要递归,靠结点 parent 指针向上回溯;end()简化版本用 nullptr,--end()特殊处理跳转到最右结点。
  4. set 底层存储const K,全部元素禁止修改;map 中pair<const K,V>,key 不可修改,value 可以修改。
  5. RBTree 的 Insert 返回pair<iterator,bool>:迭代器 + 是否插入成功,这是实现 mapoperator[]的基础。
  6. map::operator[]:调用 insert,key 不存在插入默认构造 value,返回 value 的引用;不要用 \[\] 单纯做查找,不存在会插入垃圾数据。

面试简答

Q:为什么 set/map 的迭代器不能修改 set 的 key,map 不能修改 pair 的 first? A:底层是红黑树,key 是维持红黑树有序平衡的依据,如果修改 key,直接破坏红黑树搜索树性质,造成逻辑错误。所以 set 的 T 是 const K;map 结点存pair<const K,V>,first 被 const 修饰。
Q:map 的 operator \[\] 底层原理? A:调用 insert,传入make_pair(key,V());key 不存在执行插入;key 存在直接拿到原有结点迭代器;返回iterator->second即 value 的引用,实现读、写。
Q:红黑树迭代器 ++ 是怎么实现,是递归中序遍历吗? A:不是递归,利用 parent 父指针向上回溯。右子树不为空找右子树最左;右子树空向上找祖先,找到第一个孩子是父亲左的祖先,就是中序下一个结点。
拓展:multiset/multimap 底层 rb_tree 使用insert_equal,允许 key 重复;我们当前实现是insert_unique,key 唯一,对应 set/map。

相关推荐
边境悍匪1 小时前
蜗牛学苑 Java 智能体学习 Day49|贯穿项目 5 订单下单业务 思维导图复盘
java·开发语言·spring boot·学习·阿里云
“AI国潮设计-小江”1 小时前
【AIGC实战】Python+SDXL打造潮汕国潮IP:英歌舞麻将的自动化生成与商用思路
开发语言·人工智能·python·prompt·aigc
YYYing.1 小时前
【设计模式系列 (四) 】建造者模式
c++·后端·设计模式·建造者模式·c/c++
羑悻的小杀马特1 小时前
Docker高阶实战:从Redis集群到C++微服务,全面解析镜像优化与生产环境部署+镜像制作常见问题详解
c++·redis·docker·镜像制作·dockefile
别动我齐刘海1 小时前
ROS2 Jazzy + C++ 时间系统——Time / Clock / Duration
linux·c++·人工智能·学习·机器学习·机器人·自动驾驶
重生之小比特2 小时前
【C++进阶】unordered_set 和unordered_map 深度解析
c++·哈希算法
Roadinforest2 小时前
Spring Boot 4 六步入门实录:从 Hello World 到一个能交付的 Todo API
java·开发语言·spring boot·spring
88号技师2 小时前
2026年9月SCI-天气状态蚂蚁优化算法Weather State Ants Optimizer-附Matlab免费代码
开发语言·算法·matlab·优化算法
孙启超2 小时前
【AI开发之Rust】第 20 课:壳侧 API 封装 —— 把 Rust 能力接进真实 UI
开发语言·后端·rust