目录
[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;
}