C++map_set封装 : 红黑树底层迭代器以及仿函数的运用


观众老爷们大家好 我是邪修KING 本文属于系列C++ 进阶篇 ,欢迎来到C++进阶篇博客 C++重点语法运用! >本文属于 **《C++ 进阶篇系统教程》第 7 篇**,上一篇我们讲透了 红黑 树的自平衡原理,今天我们深入 STL 的核心底层 ------红黑树。红黑树是map/set的底层实现,它能同时支持 key 模型和 key/value 模型的核心秘密是仿函数重载,而让它能像容器一样被遍历和使用的关键是迭代器设计,map最常用的\[\]运算符更是基于红黑树接口的巧妙封装。

很多人学红黑树只关注旋转和变色,却忽略了 STL 红黑树最精妙的工程设计:

1.用KeyOfT 仿函数 解耦比较逻辑和存储类型,一颗树同时实现 set 和 map

2.用双向迭代器封装 中序遍历逻辑,让红黑树支持范围 for 和标准算法

3.用 **\[\]运算符 ** 封装插入 + 查找逻辑,让 map 的使用体验媲美字典

本文将从这三个核心维度出发,结合 SGI-STL 源码的设计思想,一步步拆解红黑树的完整实现,搞懂每一个设计背后的考量。

一、(回顾)先搞懂:什么是仿函数?

1.1 仿函数的定义

仿函数(Functor)也叫函数对象,是一个重载了operator()运算符的类,它可以像函数一样被调用。

cpp 复制代码
// 一个简单的仿函数
class Add {
public:
    int operator()(int a, int b) {
        return a + b;
    }
};

int main() {
    Add add;
    cout << add(1, 2) << endl; // 像函数一样调用,输出:3
    return 0;
}

1.2 仿函数 vs 函数指针

仿函数相比函数指针有三个核心优势:

1.可以保存状态 :仿函数是类,可以有成员变量,保存调用时的状态

2.可以内联 :编译器可以内联仿函数的调用,性能更高

3.类型安全 :仿函数有明确的类型,而函数指针容易出现类型不匹配的问题

这也是为什么 STL 几乎所有的算法和容器都使用仿函数而不是函数指针的原因。

二、红黑树的核心设计难题:如何同时支持 set 和 map?

set 是key 模型 ,只存储单个 key;map 是key/value 模型 ,存储pair<const Key, Value>。如果我们分别为 set 和 map 写一颗红黑树,会导致大量的代码重复。

SGI-STL 的解决方案非常巧妙:

红黑树不关心存储的数据类型,只关心如何从存储的数据中提取出用于比较的 key

这个 "提取 key" 的逻辑,就交给KeyOfT 仿函数来实现。红黑树只需要通过这个仿函数,从任意类型的数据中提取出 key,然后进行比较和排序。

2.1 红黑树的模板参数设计

SGI-STL 的红黑树有 5 个模板参数,其中最核心的是前 4 个:

cpp 复制代码
template <
    class Key,        // 用于比较的key的类型
    class Value,      // 红黑树节点存储的数据类型(set是Key,map是pair<const Key, Value>)
    class KeyOfValue, // 仿函数:从Value中提取出Key
    class Compare = less<Key>, // 比较函数:比较两个Key的大小
    class Alloc = alloc
>
class rb_tree;

Key:用于比较的 key 的类型,find/erase等函数的参数类型

Value:节点实际存储的数据类型

KeyOfValue:仿函数,接收一个Value类型的参数,返回其中的Key

Compare:比较两个Key大小的仿函数,默认是less(升序)

2.2 set 和 map 如何实例化红黑树

set 的实例化

set 只存储 key,所以Value就是Key,KeyOfValue仿函数直接返回传入的参数:

cpp 复制代码
#include"RBTree.h"

namespace bit
{
	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 _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);
		}
	private:
		RBTree<K, const K, SetKeyOfT> _t;
	};
}

};

map 的实例化

map 存储pair<const Key, Value>,所以KeyOfValue仿函数需要返回 pair 的第一个元素:

cpp 复制代码
#include"RBTree.h"

namespace bit
{
	template<class K, class V>
	class map
	{
		//重点研究!!!仿函数,仿函数是一个类,重载了operator()运算符,
		// 使得它的对象可以像函数一样被调用
		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;
		//问题一迭代器的类型,迭代器访问的元素类型是pair<const K, V>,
		// 而不是pair<K, V>,因为map中的key是不能修改的,所以要加上const修饰符
		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<K, V>& kv)
		{
			return _t.Insert(kv);
		}

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

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

通过不同的KeyOfT 仿函数,同一个红黑树类就可以同时支持 set 和 map 两种完全不同的模型,没有任何代码重复。

三、仿函数在红黑树核心操作中的应用

红黑树的所有核心操作(插入、查找、删除)都依赖于KeyOfT仿函数来提取 key 进行比较。下面我们以插入和查找为例,看仿函数是如何被使用的。

3.1 插入操作中的仿函数应用

插入操作的核心是找到新节点应该插入的位置,这需要不断比较新节点的 key 和当前节点的 key。

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)
    {}
};
// 实现步骤:
//  1、实现红⿊树
//  2、封装map和set框架,解决KeyOfT 
//  3、iterator 
//  4、const_iterator 
//  5、key不⽀持修改的问题
//  6、operator[] 
template<class K, class T, class KeyOfT>
class RBTree
{
private:
    typedef RBTreeNode<T> Node;
    Node* _root = nullptr;
    
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(cur->_data) < kot(data))
                {
                        parent = cur;
                        cur = cur->_right;
                }
                else if (kot(cur->_data) > kot(data))
                {
                        parent = cur;
                        cur = cur->_left;
                }
                else
                {
                        return 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;
        //...
        return true;
    }
}

注意:Find函数的参数是Key类型,而不是Value类型,这就是为什么红黑树需要第一个模板参数Key------ 它是find/erase等函数的参数类型。

四、红黑树迭代器实现思路详解(核心难点)

迭代器是容器的 "访问接口",它让我们可以用统一的方式遍历不同的容器。红黑树的迭代器是双向迭代器 ,支持++和--操作,按照中序遍历的顺序访问节点(左→根→右)。

4.1 迭代器的整体设计框架

红黑树迭代器的设计思路和list迭代器完全一致:

用一个类封装节点的指针,通过重载*、->、++、--等运算符,让迭代器像指针一样访问节点的数据。

cpp 复制代码
template <typename T, typename Ref, typename Ptr>
struct RBTreeIterator {
    typedef RBTreeNode<T> Node;
    typedef RBTreeIterator<T, Ref, Ptr> Self;

    Node* _node; // 指向当前节点的指针
    Node* _root; // 指向红黑树根节点的指针(用于--end()的特殊处理)

    // 构造函数
    RBTreeIterator(Node* node, Node* root)
        : _node(node)
        , _root(root)
    {}

    // 重载运算符...
};

4.2 核心难点:operator++ 的实现

iterator实现思路分析

• iterator实现的⼤框架跟list的iterator思路是⼀致的,⽤⼀个类型封装结点的指针,再通过重载运算

符实现,迭代器像指针⼀样访问的⾏为。

• 这⾥的难点是operator++和operator--的实现。之前使⽤部分,我们分析了,map和set的迭代器⾛的是中序遍历,左⼦树->根结点->右⼦树,那么begin()会返回中序第⼀个结点的iterator也就是10所在结点的迭代器。

• 迭代器++的核⼼逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点。

• 迭代器++时,如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。

• 迭代器++时,如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上找。

• 如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结点的⽗亲;如下图:it指向25,25右为空,25是30的左,所以下⼀个访问的结点就是30。

• 如果当前结点是⽗亲的右,根据中序左⼦树->根结点->右⼦树,当前当前结点所在的⼦树访问完了,当前结点所在⽗亲的⼦树也访问完了,那么下⼀个访问的需要继续往根的祖先中去找,直到找到孩⼦是⽗亲左的那个祖先就是中序要问题的下⼀个结点。如下图:it指向15,15右为空,15是10的右,15所在⼦树话访问完了,10所在⼦树也访问完了,继续往上找,10是18的左,那么下⼀个访问的结点就是18。

• end()如何表⽰呢?如下图:当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18到根没有⽗亲,没有找到孩⼦是⽗亲左的那个祖先,这是⽗亲为空了,那我们就把it中的结点指针置为nullptr,我们⽤nullptr去充当end。需要注意的是stl源码空,红⿊树增加了⼀个哨兵位头结点做为end(),这哨兵位头结点和根互为⽗亲,左指向最左结点,右指向最右结点。相⽐我们⽤nullptr作为end(),差别不⼤,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处理⼀下,让迭代器结点指向最右结点。具体参考迭代器--实现。

• 迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右⼦树->根结点->左⼦树,具体参考下⾯代码实现。

• set的iterator也不⽀持修改,我们把set的第⼆个模板参数改成constK即可,RBTree<K, const K,SetKeyOfT> _t;

• map的iterator不⽀持修改key但是可以修改value,我们把map的第⼆个模板参数pair的第⼀个参数改成constK即可,RBTree<K, pair<const K, V>, MapKeyOfT> _t;

1、当右子树存在时

因为迭代器的++方式是左子树根结点右子树,而右子树又分为左子树根结点右子树

所以右子树存在时,++迭代器,迭代器就应该走到右子树的最左结点

2、当右子树不存在时

因为迭代器++方式是左子树根结点右子树,所以当右子树不存在时,就代表该子树已经走完了,那么就需要去找祖先结点中子为父左的父结点

operator++的实现

cpp 复制代码
// 前置++:中序遍历的下一个节点
Self& operator++() {
    if (_node->_right != nullptr) {
        // 情况1:有右子树,下一个节点是右子树的最左节点
        Node* leftMost = _node->_right;
        while (leftMost->_left != nullptr) {
            leftMost = leftMost->_left;
        }
        _node = leftMost;
    } else {
        // 情况2:没有右子树,向上找第一个孩子是左孩子的祖先
        Node* cur = _node;
        Node* parent = cur->_parent;
        while (parent != nullptr && cur == parent->_right) {
            cur = parent;
            parent = cur->_parent;
        }
        _node = parent;
    }
    return *this;
}

4.3 end () 的表示与 operator-- 的实现

end () 的表示

我们用nullptr表示end(),也就是遍历完所有节点后的位置。当++it向上找完所有祖先都没有找到下一个节点时,就把_node置为nullptr,也就是end()。

注意:STL 源码中用了一个哨兵位头结点作为end(),这个哨兵位和根节点互为父亲,左指向整棵树的最左节点,右指向最右节点。我们用nullptr作为end()更简单,功能完全一致,只是需要对--end()做特殊处理。

operator-- 的实现

--的逻辑和++完全相反,是找到中序遍历的上一个节点:

1、如果当前节点是end()(_node == nullptr),特殊处理:上一个节点是整棵树的最右节点

2、如果当前节点有左子树,上一个节点是左子树的最右节点

3、如果当前节点没有左子树,向上找第一个孩子是右孩子的祖先,这个父亲就是上一个节点

operator-- 代码实现

cpp 复制代码
// 前置--:中序遍历的上一个节点
Self& operator--() {
    if (_node == nullptr) {
        // 特殊情况:--end(),返回整棵树的最右节点
        Node* rightMost = _root;
        while (rightMost != nullptr && rightMost->_right != nullptr) {
            rightMost = rightMost->_right;
        }
        _node = rightMost;
    } else if (_node->_left != nullptr) {
        // 情况1:有左子树,上一个节点是左子树的最右节点
        Node* rightMost = _node->_left;
        while (rightMost->_right != nullptr) {
            rightMost = rightMost->_right;
        }
        _node = rightMost;
    } else {
        // 情况2:没有左子树,向上找第一个孩子是右孩子的祖先
        Node* cur = _node;
        Node* parent = cur->_parent;
        while (parent != nullptr && cur == parent->_left) {
            cur = parent;
            parent = cur->_parent;
        }
        _node = parent;
    }
    return *this;
}

4.4 迭代器的不可修改性

红黑树的 key 是排序的依据,修改 key 会破坏树的结构,导致所有操作失效。所以我们需要保证:

set 的迭代器不能修改任何内容 :set 只存储 key,所以把红黑树的第二个模板参数设为const K,迭代器返回的就是const K&

map 的迭代器不能修改 key,但可以修改 value:map 存储pair<const K, V>,first是const K不能修改,second是V可以修改

cpp 复制代码
// set的红黑树实例化:Value是const K,保证key不能修改
RBTree<K, const K, SetKeyOfT> _t;

// map的红黑树实例化:Value是pair<const K, V>,保证key不能修改
RBTree<K, pair<const K, V>, MapKeyOfT> _t;

五、map 的 \[\] 运算符实现(最常用接口)

map的\[\]运算符是它最常用的接口,它的功能非常强大:

如果 key存在:返回对应 value 的引用,可以读取或修改

如果 key不存在:自动插入一个(key, 默认值)的键值对,然后返回默认值的引用

5.1 \[\] 运算符的实现原理

\[\]运算符的实现完全基于红黑树的Insert接口。Insert的返回值是pair<iterator, bool>:

iterator:指向插入的元素(或已存在的元素)的迭代器

bool:表示是否插入成功(true 表示新插入,false 表示已存在)

所以\[\]的实现逻辑非常简单:

调用Insert插入(key, V())(V 的默认构造值)

返回迭代器指向的 value 的引用

人话就是可以理解为插入到键值对是在iterator里面的二叉树结点指针封装,bool作用在表层体现

5.2 代码实现

cpp 复制代码
tempalte<template K,typename V>
class map{
public:
  		//运算符
		//重点研究!!!
		V& operator[](const K& key)
		{
		//插入(key,默认值),如果key已存在则插入失败
			pair<iterator, bool> ret = insert({ key, V() });
			//返回value的引用
			return ret.first->second;
		}

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

5.3 使用示例与注意事项

cpp 复制代码
int main() {
    map<string, int> freq;

    // 统计词频:如果单词不存在,自动插入(单词, 0),然后+1
    freq["hello"]++; // 插入("hello", 0),然后变成1
    freq["world"]++; // 插入("world", 0),然后变成1
    freq["hello"]++; // 已存在,直接+1,变成2

    cout << freq["hello"] << endl; // 输出:2
    cout << freq["world"] << endl; // 输出:1

    // 注意!查找不要用[],会插入多余元素
    // if (freq["c++"] == 0) {} // 错误!会插入("c++", 0)
    // 正确做法:用find
    if (freq.find("c++") != freq.end()) {
        cout << freq["c++"] << endl;
    }

    return 0;
}

如果是插入或修改,用\[\]运算符,代码更简洁

如果只是查找,用find接口,避免插入多余的默认值

六、完整的红黑树实现

下面是整合了仿函数、迭代器、插入、查找的完整红黑树代码:

cpp 复制代码
#pragma once

enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	// 这里更新控制平衡也要加入parent指针
	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 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)
		{
			// 右不为空,中序下一个访问的节点是右子树的最左(最小)节点
			Node* min = _node->_right;
			while (min->_left)
			{
				min = min->_left;
			}

			_node = min;
		}
		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(),特殊处理,走到中序最后一个结点,整棵树的最右结点
			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;
	}

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

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

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

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

template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
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);
	}

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

		return ConstIterator(cur, _root);
	}

	ConstIterator End() const
	{
		return ConstIterator(nullptr, _root);
	}

	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 };
		}

		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 { 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)
			{
				//   g
				// p   u
				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)
					{
						//     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
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

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

	void RotateR(Node * parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		Node* pParent = parent->_parent;

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

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

			subL->_parent = pParent;
		}
	}

	void RotateL(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 (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}

private:
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int _Size(Node* root)
	{
		if (root == nullptr)
			return 0;

		return _Size(root->_left) + _Size(root->_right) + 1;
	}

private:
	Node* _root = nullptr;
};

七、用红黑树封装 set 和 map

7.1 封装 set

cpp 复制代码
#pragma once

#include"RBTree.h"

namespace bit
{
	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 _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);
		}
	private:
		RBTree<K, const K, SetKeyOfT> _t;
	};
}

7.2map的封装

cpp 复制代码
#pragma once

#include"RBTree.h"

namespace bit
{
	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 _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<K, V>& kv)
		{
			return _t.Insert(kv);
		}

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

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

八、测试代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<vector>
#include<time.h>
using namespace std;

//#include"RBTree.h"
//#include"AVLTree.h"

//void TestRBTree1()
//{
//	RBTree<int, int> t;
//	// 常规的测试用例
//	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//	// 特殊的带有双旋场景的测试用例
//	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//
//	for (auto e : a)
//	{
//		t.Insert({ e, e });
//	}
//
//	t.InOrder();
//	cout << t.IsBalance() << endl;
//}
//
//void TestTree2()
//{
//	const int N = 10000000;
//	vector<int> v;
//	v.reserve(N);
//	srand(time(0));
//	for (size_t i = 0; i < N; i++)
//	{
//		v.push_back(rand() + i);
//	}
//
//	size_t begin2 = clock();
//	AVLTree<int, int> t;
//	for (auto e : v)
//	{
//		t.Insert(make_pair(e, e));
//	}
//	size_t end2 = clock();
//
//	size_t begin22 = clock();
//	RBTree<int, int> rbt;
//	for (auto e : v)
//	{
//		rbt.Insert(make_pair(e, e));
//	}
//	size_t end22 = clock();
//
//	cout << "AVL Insert:" << end2 - begin2 << endl;
//	cout << "RB Insert:" << end22 - begin22 << endl;
//
//	cout <<"AVL IsBalance:"<< t.IsBalanceTree() << endl;
//	cout << "RB IsBalance:" << rbt.IsBalance() << endl;
//
//
//	cout << "AVL Height:" << t.Height() << endl;
//	cout << "AVL Size:" << t.Size() << endl;
//
//	cout << "RB Height:" << rbt.Height() << endl;
//	cout << "RB Size:" << rbt.Size() << endl;
//
//	size_t begin1 = clock();
//	// 确定在的值
//	for (auto e : v)
//	{
//		t.Find(e);
//	}
//	// 随机值
//	/*for (size_t i = 0; i < N; i++)
//	{
//		t.Find((rand() + i));
//	}*/
//	size_t end1 = clock();
//	cout << "AVL Find:" << end1 - begin1 << endl;
//
//	size_t begin11 = clock();
//	// 确定在的值
//	for (auto e : v)
//	{
//		rbt.Find(e);
//	}
//	// 随机值
//	/*for (size_t i = 0; i < N; i++)
//	{
//		t.Find((rand() + i));
//	}*/
//	size_t end11 = clock();
//	cout << "RB Find:" << end11 - begin11 << endl;
//}
//
//int main()
//{
//	TestTree2();
//
//	return 0;
//}

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


void Print(const bit::set<int>& s)
{
	bit::set<int>::const_iterator it = s.end();
	while (it != s.begin())
	{
		--it;
		cout << *it << " ";
	}
	cout << endl;
}

int main()
{
	bit::set<int> s;
	s.insert(5);
	s.insert(1);
	s.insert(3);
	s.insert(2);
	s.insert(6);

	bit::set<int>::iterator sit = s.begin();
	//*sit += 10;
	while (sit != s.end())
	{
		cout << *sit << " ";
		++sit;
	}
	cout << endl;

	for (auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	Print(s);


	bit::map<string, string> dict;
	dict.insert({ "sort", "排序" });
	dict.insert({ "left", "左边" });
	dict.insert({ "right", "右边" });

	dict["left"] = "左边,剩余";
	dict["insert"] = "插入";
	dict["string"];

	bit::map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		// 不能修改first,可以修改second
		//it->first += 'x';
		it->second += 'x';

		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;

	for (auto& kv : dict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}

	return 0;
}

九、核心设计思想总结

1、仿函数解耦 :用KeyOfT仿函数解耦比较逻辑和存储类型,让一颗红黑树同时支持 set 和 map

2、迭代器封装 :用迭代器封装中序遍历逻辑,让红黑树支持范围 for 和标准算法,++和--的实现基于局部节点判断,不需要全局信息

3、接口封装 :map的\[\]运算符基于Insert接口封装,提供了简洁的插入和修改体验

4、const 正确性 :通过模板参数加const,保证 set 的元素和 map 的 key 不能修改,维护红黑树的结构

这些设计思想让 STL 的红黑树具有极高的复用性、灵活性和安全性,是 C++ 泛型编程的经典范例。

红黑树是 STL 的核心底层,仿函数、迭代器和\[\]运算符是它能成为通用容器的关键。理解了这些设计,你就真正理解了 STL 的工程思想。

本文属于 《C++ 进阶篇系统教程》第 7 篇

关注我,第一时间收到更新,不用自己零散找资料,跟着系列系统学,少走 90% 的弯路!

相关推荐
醉颜凉1 小时前
Elasticsearch 核心数据结构:FST 原理与应用场景全解析
数据结构·elasticsearch·jenkins
love_muming1 小时前
从 ArrayList 到 LinkedList:Java 集合中数组与链表的深度对比
java·数据结构·链表
牟师傅敲代码1 小时前
第2章:底层时间驱动机制
c++
AI2中文网1 小时前
App Inventor 2 鸿蒙先行版开发进展:从 Android 到 HarmonyOS 的积木编程迁移实录
android·低代码·华为·harmonyos·app inventor
2301_777998341 小时前
基础IO:IO操作&&重定向
linux·c语言
05候补工程师1 小时前
【408数据结构】核心考点:图(Graph)精炼笔记与算法直觉
数据结构·经验分享·笔记·考研·算法·图论
并不喜欢吃鱼1 小时前
从零开始 C++------ 十四【C++ 数据结构】unordered_map/unordered_set 全解析:从使用到底层模拟实现
开发语言·数据结构·c++
社交怪人1 小时前
【收费】信息学奥赛一本通C语言解法(题号2055)
c语言
夜月yeyue2 小时前
TCP/IP 协议解析
linux·服务器·c语言·网络·网络协议·tcp/ip