【C++】封装红黑树实现Mymap和Myset

目录

[1. map/set 源码剖析](#1. map/set 源码剖析)

[1.1 整体剖析](#1.1 整体剖析)

[1.2 set源码结构框架](#1.2 set源码结构框架)

[1.3 map源码结构框架](#1.3 map源码结构框架)

[2. 红黑树模拟实现](#2. 红黑树模拟实现)

[2.1 红黑树节点的定义](#2.1 红黑树节点的定义)

[2.2 模拟实现迭代器](#2.2 模拟实现迭代器)

[2.3 模拟实现红黑树](#2.3 模拟实现红黑树)

[3. 封装map和set](#3. 封装map和set)

[3.1 封装set](#3.1 封装set)

[3.2 封装map](#3.2 封装map)

[4. 测试实现](#4. 测试实现)


1. map/set 源码剖析

1.1 整体剖析

一个红黑树------适配两种容器

STL里的set和map,底层其实是同一个rb_tree(红黑树)实现的------靠rb_tree的模版参数"灵活切换存储内容"。

rb_tree的第二个模版参数Value,决定了红黑树节点里实际存什么数据:

  • 当实现set时:Value传的是Key本身--->节点里存的就是key。
  • 当实现map时,Value传的是pair<const Key , T>--->节点里存的是键值对。

这样不用写两个红黑树代码,一个红黑树通过改Value参数,既能当set的底层,也能当map的底层------这就是"泛型思想"的巧妙之处。

STL标准库红黑树的定义

rb_tree的第二个模版参数Value已经控制了红黑树节点中存储的数据类型,为什么还要设计第一个模版参数Key呢?

对于map和set,查找、删除操作都是根据key来的,所以第一个参数是传给find/erase做函数形参类型的。 对于set 而言key和Value是一样的,而对于map而言,insert 的是pair类型对象,find/erase的是Key类型。
rb_tree的第三个模版参数KeyOfValue是一个仿函数 ,作用是从Value中提取Key ,是红黑树泛型设计里的"提取器"。
为什么要设计KeyOfValue?

map场景下,红黑树要按key排序,但节点存的是Value即pair<const K , T>,所以得有一个东西把Value里的Key取出来,KeyOfValue就是取这个pair的first部分。那对于set场景,Value就是Key本身,KeyOfValue就直接返回Value本身。
源码红黑树的实现是带有header哨兵位头结点的,header的left指向整棵树的最小节点(最左节点);header的right指向整棵树的最大节点(最右节点),而header的父节点指针指向树的root,同时root的父节点指针又指向header。红黑树所有"逻辑空"的位置都指向header。

这样设计可以快速获取begin()和end():begin()直接返回head->_left(最左节点),无需遍历整棵树找左;end()直接返回header。还可以简化迭代器的移动:最大节点的right指向header,当迭代器指向最大节点时,执行operator++时,会直接移动到header。

1.2 set源码结构框架

1.3 map源码结构框架

2. 红黑树模拟实现

下面我们模拟实现的红黑树与源码不同,不带有哨兵节点,但整体思想逻辑一致。相比源码进行参数调整:key参数就用K,value参数就用V,红黑树中的数据类型使用T。

2.1 红黑树节点的定义

RBTree.h

复制代码
// 枚举值表示颜色
enum Colour { RED, BLACK };

//红黑树的节点的定义
template<class T>
struct RBTreeNode
{
	T _data;   //T   set: const K    map: pair<const K, V>  
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col; 

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

2.2 模拟实现迭代器

++迭代器:找后继节点(即中序的下一个节点)

  • 情况1:当前节点有右子树,找右子树的最左节点。

  • 情况2:当前节点无右子树,则向上找第一个"当前节点是其父亲左孩子"的祖先,那这个祖先就是后继(因为中序遍历按照左根右原则,当前节点是左子树的最后一个,祖先就是下一个根)。

--迭代器:找前驱节点(即中序的前一个节点)

  • 情况1:迭代器指向end(),operator--需要移动到中序的最后一个节点,即右子树的最右节点(因为需要走到最后一个节点,需要从根开始,那么迭代器就需要增加一个成员变量_root)。
  • 情况2:当前节点有左子树,前驱节点是左子树的最右节点。
  • 情况3:当前节点无左子树,向上找第一个"当前节点是其父亲节点右孩子的祖先"。

RBTree.h

复制代码
//迭代器
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;
	Node* _root;  //是为了支持operator--,--end()访问中序最后一个节点时,需要从根开始找最后一个节点
	
	RBTreeIterator(Node* node, Node* root)
		:_node(node)
		, _root(root)
	{}

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

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

	//前置++ (找后继节点)
	Self& operator++() //左根右  
	{
		if (_node->_right)//右不为空 下一个找右子树的最左节点
		{
			Node* rightmost = _node->_right;
			while (rightmost->_left)
			{
				rightmost = rightmost->_left;
			}

			_node = rightmost;
		}
		else //右为空,下一个找第一个"当前节点是其父亲左孩子"的祖先
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent; 
			}

			_node = parent; //即使parent走到空,_node=nullptr符合条件
		}

		return *this;
	}
	
	//后置++
	Self operator++(int)
	{
		Self tmp(*this);
		++(*this);//调用前置operator++
		return tmp;
	}

	//前置-- (找前驱节点)
	Self& operator--() //右根左 
	{
		if (_node == nullptr)//情况1:迭代器指向end(),operator--需要移动到中序的最后一个节点,即整棵树的最右节点
		{
			Node* most = _root;
			while (most && most->_right)
			{
				most = most->_right;
			}
			_node = most;
		}
		else if (_node->_left)//如果左不为空,下一个找左子树最右节点
		{
			Node* leftmost = _node->_left;
			while (leftmost->_right)
			{
				leftmost = leftmost->_right;
			}

			_node = leftmost;
		}
		else //如果左为空,下一个找第一个"当前节点是其父亲右孩子"的祖先
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}

		return *this;
	}

	//后置--
	Self operator--(int)
	{
		Self tmp(*this);
		--(*this); //调用前置operator--
		return tmp;
	}

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

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

2.3 模拟实现红黑树

RBTree.h

复制代码
//红黑树的定义
template<class K, class T, class KeyOfT, class Compare>
class RBTree
{
	typedef RBTreeNode<T> Node;
private:

	Node* _root = nullptr;

	//核心仿函数
	KeyOfT _kot; //提取Key的仿函数
	Compare _cmp; //比较器对象,用于Key比较

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

	Iterator Begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return Iterator(cur, _root);
	}

	Iterator End()
	{
		return Iterator(nullptr, _root); //用nullptr作为end
	}

	ConstIterator Begin() const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return ConstIterator(cur, _root);
	}

	ConstIterator End() const
	{
		return ConstIterator(nullptr, _root);
	}
	
	RBTree() = default;  //由于定义了拷贝构造函数,编译器不会自动生成默认构造函数,set<int> s默认初始化就会报错,所以显式要求编译器为生成默认的无参构造函数

	//拷贝构造函数(深拷贝)
	RBTree(const RBTree& other)
	{
		_root = Copy(other._root);
	}

	//析构函数
	~RBTree()
	{
		Destroy(_root);
		_root = nullptr;
	}

	//赋值运算符重载
	RBTree& operator=(const RBTree& other)
	{
		if (this != &other)
		{
			Destroy(_root);
			_root = Copy(other._root);
		}

		return *this;
	}

	//插入
	pair<Iterator, bool> Insert(const T& data)
	{
		//空树直接插入根节点(黑色)
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;

			//return pair<Iterator, bool>(Iterator(_root,_root), true);
			return { Iterator(_root,_root), true }; //插入成功,返回的pair类型里面的迭代器是新节点位置的迭代器
		}

		//BST插入逻辑
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (_cmp(_kot(cur->_data), _kot(data)))
			{
				parent = cur;
				cur = cur->_right; //插入右子树
			}
			else if (_cmp(_kot(data), _kot(cur->_data)))
			{
				parent = cur;
				cur = cur->_left; //插入左子树
			}
			else
			{
				return { Iterator(cur,_root), false }; //键已存在,插入失败,返回的pair类型里面的迭代器是与要插入的值相等的位置的迭代器
			}
		}
		//创建新节点并链接到父亲节点
		cur = new Node(data);
		Node* newnode = cur;
		if (_cmp(_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)//父亲节点是爷爷节点的左孩子
			{
				//     g
				//   p   u
				Node* uncle = grandfather->_right;

				//情况1:叔父节点为红色-->仅对叔父爷变色,并向上递归检查
				if (uncle && uncle->_col == RED) 
				{
					//对叔父爷变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//祖父作为新节点继续检查
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2:叔父节点为黑色  旋转+变色
				{
					if (cur == parent->_left) //cur是父亲节点的左孩子  LL直线型
					{						
						RightRotate(grandfather); //右单旋

						//变色
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else  //cur是父亲节点的右孩子 LR折线型
					{						
						LeftRotate(parent);//左单旋
						RightRotate(grandfather);//右单旋

						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break; //修复完成,无需继续循环
				}
			}
			else  //父亲节点是爷爷节点的右孩子 
			{
				//     g
				//   u   p
				Node* uncle = grandfather->_left;

				//情况1:叔父节点为红色-->仅对叔父爷变色,并向上递归检查
				if (uncle && uncle->_col == RED)  
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 祖父作为新节点继续检查
					cur = grandfather;
					parent = cur->_parent;
				}
				else //情况2:叔父节点为黑色  旋转+变色
				{
					if (cur == parent->_right)//cur为父亲节点的右孩子  RR直线型
					{
						LeftRotate(grandfather);//左单旋

						//变色
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else //cur为父亲节点的左孩子  RL折线型
					{
						RightRotate(parent); //右单旋
						LeftRotate(grandfather); //左单旋

						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break; //修复完成,无需继续循环
				}
			}
		}

		_root->_col = BLACK; //确保根节点始终是黑色
		return { Iterator(newnode,_root), true }; //插入成功,返回的pair类型里面的迭代器是新节点位置的迭代器
	}

	//查找
	Iterator Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (_cmp(_kot(cur->_data), key))
				cur = cur->_right;
			else if (_cmp(key, _kot(cur->_data)))
				cur = cur->_left;
			else
				return Iterator(cur, _root);//返回迭代器
		}

		return End(); //没找到返回end()
	}

private:
	//左单旋
	void LeftRotate(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* parentparent = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (parentparent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parentparent->_left == parent)
			{
				parentparent->_left = subR;
			}
			else
			{
				parentparent->_right = subR;
			}
			subR->_parent = parentparent;
		}
	}

	//右单旋
	void RightRotate(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentparent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == parentparent->_left)
			{
				parentparent->_left = subL;
			}
			else
			{
				parentparent->_right = subL;
			}
			subL->_parent = parentparent;
		}		
	}

	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;

		Destroy(root->_left);
		Destroy(root->_right);
		delete 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;
	}
};

3. 封装map和set

3.1 封装set

set 源码实现 "Key 不可修改" 的核心设计

set的底层红黑树,存储的是普通的K类型(非const K)------这是为了让红黑树能被其他容器(如map)复用,set和map可以共用一个红黑树模版RBTree<K, T, KeyOfT, Compare>,只是T分别传K,和pair<const K, V>;编译器会生成一份红黑树核心代码,set和map共享这套逻辑。set的"普通迭代器"和"const迭代器",都强制使用红黑树的ConstIterator:

typedef typename RBTree<K, K, SetKeyOfT, Compare>::ConstIterator iterator;
typedef typename RBTree<K, K, SetKeyOfT, Compare>::ConstIterator const_iterator;

对于自己模拟实现:

由于模拟源码实现还需要处理普通迭代器转换成const迭代器等逻辑,所以这里我们为了简便实现,选择牺牲复用性,使用下面的方式处理:

自己实现的const迭代器不允许修改,但普通迭代器允许修改,但是根据map的性质,不允许修改键值,所以要解决普通迭代器能修改key的问题,核心是让set的"普通迭代器"本质上就是"const迭代器",通过控制迭代器的Ref(引用类型)和Ptr(指针类型),强制普通迭代器也只能返回const引用/指针,从而禁止修改key。
通过将红黑树的T实例化为const K,让迭代器因为无论是Iterator还是ConstIterator,最终的Ref都是const K&,Ptr都是const K*,限制指向的对象不可改------迭代器解引用后得到的是const引用/指针,自然无法修改Set元素。

Myset.h

复制代码
template<class K, class Compare = less<K>>  //默认按Key升序
class set
{
	struct SetKeyOfT  //仿函数
	{
		const K& operator()(const K& key)
		{
			return key; //直接返回自身作为Key
		}
	};

public:

	typedef typename RBTree<K, const K, SetKeyOfT, Compare>::Iterator iterator;
	typedef typename RBTree<K, const K, SetKeyOfT, Compare>::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, Compare> _t;
};

3.2 封装map

map 源码实现 "Key 不可修改、Value 可修改" 的核心设计

map 的底层红黑树节点存储的数据类型不是单独的K或V,而是pair<const K,V>,first成员(Key)被const修饰,无法修改,second成员(Value)是普通类型,可以修改。这个设计直接从存储类型层面限制了Key的修改,同时保留Value的可写性。

map的迭代器本质是红黑树迭代器的实例化,其Ref是pair<const K,V>&,Ptr是pair<const K,V>*------这意味着:

  • 迭代器解引用后得到pair的引用,能访问first(Key)和second(Value);
  • 但first是const K类型-->禁止赋值修改;
  • second是V类型-->可以自由赋值修改;

Mymap.h

复制代码
template<class K, class V, class Compare = less<K>> //默认按Key升序
class map
{
	struct MapKeyOfT  //仿函数
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first; //从Value里提取Key
		}
	};

public:
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT, Compare>::Iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT, Compare>::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);
	}

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

	iterator find(const K& key)
	{
		return _t.Find(key);
	}

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

4. 测试实现

复制代码
#include<iostream>
using namespace std;

#include"RBTree.h"
#include"Myset.h"
#include"Mymap.h"


// 测试set的核心接口
void TestSet()
{
    cout << "=========== TestSet ===========" << endl;

    // 1. 测试默认构造、插入、遍历
    dog::set<int> s;
    s.insert(3);
    s.insert(1);
    s.insert(4);
    s.insert(2);
    s.insert(2); // 插入重复值,预期失败

    cout << "遍历set: ";
    for (auto it = s.begin(); it != s.end(); ++it)
    {
        // *it = 10; // 编译报错:验证set迭代器只读
        cout << *it << " ";
    }
    cout << endl;

    dog::set<int, greater<int>> s2;//按照降序
    s2.insert(7);
    s2.insert(6);
    s2.insert(9);
    s2.insert(8);
    cout << "遍历s2(降序): ";
    for (auto e : s2)
    {
        cout << e << " ";
    }
    cout << endl;

    // 2. 测试查找
    auto findIt = s.find(3);
    if (findIt != s.end())
    {
        cout << "找到元素: " << *findIt << endl;
    }

    // 3. 测试拷贝构造(深拷贝)
    dog::set<int> s3(s);
    cout << "拷贝构造后s3遍历: ";
    for (auto e : s3)
    {
        cout << e << " ";
    }
    cout << endl;

    cout << "倒着遍历s3: ";
    dog::set<int>::const_iterator it = s3.end();
    while (it != s3.begin())
    {
        --it;
        cout << *it << " ";
    }
    cout << endl;
}

// 测试map的核心接口
void TestMap()
{
    cout << "=========== TestMap ===========" << endl;

    // 1. 测试插入、operator[]、遍历
    dog::map<int, string> m;
    m.insert({ 1, "one" });
    m.insert({ 2, "two" });
    m.insert({ 3, "three" });

    cout << "插入后遍历map: ";
    for (auto it = m.begin(); it != m.end(); ++it)
    {
         //it->first = 10; // 编译报错:验证map键只读
        it->second += "_modify"; // 验证值可写
        cout << "(" << it->first << "," << it->second << ") ";
    }
    cout << endl;

    m[2] = "TWO";     // operator[]修改已有元素的值
    cout << "修改后遍历map: ";
    for (auto e : m)
    {
        cout << "(" << e.first << "," << e.second << ") ";
    }
    cout << endl;

    // 2. 测试查找
    auto findIt = m.find(3);
    if (findIt != m.end())
    {
        cout << "找到键3对应的值: " << findIt->second << endl;
    }

    // 3. 测试赋值重载(深拷贝)
    dog::map<int, string> m2;
    m2 = m;
    cout << "赋值后m2遍历: ";
    for (auto e : m2)
    {
        cout << "(" << e.first << "," << e.second << ") ";
    }
    cout << endl;
}

int main()
{
    TestSet();
    TestMap();
    return 0;
}
相关推荐
H_BB33 分钟前
算法详解:滑动窗口机制
数据结构·c++·算法·滑动窗口
坚持就完事了39 分钟前
十大排序算法
数据结构·算法·排序算法
wefg11 小时前
【C++】IO流
开发语言·c++
im_AMBER1 小时前
Leetcode 63 定长子串中元音的最大数目
c++·笔记·学习·算法·leetcode
小白程序员成长日记2 小时前
2025.11.29 力扣每日一题
数据结构·算法·leetcode
咫尺的梦想0072 小时前
链表-反装链表
数据结构·链表
老鱼说AI3 小时前
算法基础教学第一步:数据结构
数据结构·python·算法
极地星光3 小时前
C++链式调用设计:打造优雅流式API
服务器·网络·c++
小陈要努力4 小时前
Visual Studio 开发环境配置指南
c++·opengl