漫步STL-模拟实现map和set
1. 为什么使用红黑树
1.1 AVLTree
AVLTree树利用高度来控制平衡,是一种严格平衡
1.2 Red-Black Tree
红黑树通过颜色互斥来控制平衡,是一种近似平衡,它保证了最长路径不超过最短路径的2倍
因为这个不同,如果插入相同的节点,红黑树绝对会比AVL树旋转少的次数
1.2.1 红黑树特性回顾
⚡️ 节点非红即黑
⚡️ 树中没有连续的红色节点
⚡️ 每一条路上的黑色节点数量是相等的
⚡️ 根节点是黑色的
⚡️ 最短路径:全黑 ; 最长路线:一红一黑
2.适配key型和key-value型
有时候我们需要的是key型,比如说是set,有时候我们需要的是key-value型的参数,那就比如说是map,那么难道我要重新写一份代码来一个支持key,一个支持key-value吗,其实不用
2.1 统一传T类型
看看源码可以发现通过传入的key和value,key一定是传的key进去,value却会有不同
那么我们这里选择将之前写的红黑树进行一些改造,来实现既可以支持map也可以支持set的结构,这是T可以传普通的key也可以传pair类型,根据需要传就可以了
template<class K,class T>
class RBTree
{
typedef RBTreeNode<T> Node;
}
2.2 仿函数控制
在RBTree的某一些接口中,比如Find和Insert就需要去比较数据的大小,才能判断位置
在源码规定中,pair的比大小有时候不仅需要first还需要second
所以如果只是按照T类型去比较大小的话,pair的比较大小方式和key的比较方式是不同的,这样直接来的话会造成Insert可以正常插入但是Find的时候找不到,因为Find的参数是K类型,而不是T类型
pair<Node*, bool> Insert(const T& data)
Node* Find(const K& key)
这也是为什么RBTree这里我们要两个参数的模板,而不是只一个T就可以了,毕竟我们有时候要的是K类型,map的查找是用key来找,不是用piar来找的
template<class K,class T>
class RBTree
{
typedef RBTreeNode<T> Node;
···
对于红黑树来说不知道自己存的数据类型存的是key还是pair,这是上层控制的,所以我们必须要一个高维度的泛型来控制
源码这里其实是通过在传一个上层函数来控制的
所以我们在这里要使用一个仿函数放在模板中,仿函数的目的就是取出key
2.2.2 set_KeyOfT
template<class K, class V>
class set
{
//内部类封装一个仿函数,用来返回key
struct set_KeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
bool insert(const K& k)
{
_t.Insert(k);
return true;
}
private:
RBTree<K, K,set_KeyOfT> _t;
};
2.2.2 map_KeyOfT
template<class K, class V>
class map
{
//内部类封装一个仿函数,用来返回key
struct map_KeyOfT
{
const K& operator()(const pair<const K,V>& kv)
{
return kv.first;
}
};
public:
bool insert(const pair<const K, V>& kv)
{
_t.Insert(kv);
return true;
}
private:
RBTree<K, pair<const K, V>,map_KeyOfT> _t;
};
2.2.3 RBTree template
红黑树的模板这里就可以更新了
template<class K,class T,class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
...
2.3 revised Insert
pair<Node*, bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(_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(cur, false);
}
}
Node* newnode = new Node(data);
newnode->_col = RED;
if (kot(parent->_data) < kot(data))
{
parent->_right = newnode;
newnode->_parent = parent;
}
else
{
parent->_left = newnode;
newnode->_parent = parent;
}
cur = newnode;
// 如果父亲存在,且颜色为红色就需要处理
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
// 关键是看叔叔
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
// 情况1:uncle存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else // 情况2+3:uncle不存在 uncle存在且为黑
{
// 情况2:单旋
if (cur == parent->_left)
{
_Rotate_R(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else // 情况3:双旋
{
_Rotate_L(parent);
_Rotate_R(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else // parent == grandfather->_right
{
Node* uncle = grandfather->_left;
// 情况1:
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else // 情况2:+ 情况3:
{
if (cur == parent->_right)
{
_Rotate_L(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else // cur == parent->_left
{
_Rotate_R(parent);
_Rotate_L(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
// 插入结束
break;
}
}
}
_root->_col = BLACK;
return make_pair(newnode, true);
}
2.5 revised Find
Node* Find(const K& key)
{
KeyOfT kot;
Node* cur = _root;
while (cur)
{
if (kot(cur->_data) > key)
{
cur = cur->_left;
}
else if (kot(cur->_data) < key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
总结一下就是:
一个模板参数控制红黑树里面存key还是存pair,实现key结构和key_value结构
一个仿函数去控制值里面用什么去比较
3. 红黑树迭代器
一个容器必须的就是迭代器,红黑树也不能少,之前AVL树中没有实现,因为红黑树很类似,于是下面来实现一下红黑树的迭代器
而map和set的迭代器本质上用的都是红黑树的迭代器,因此模拟实现map和set的难点在于红黑树
From 《STL源码剖析》 红黑树迭代器的源码结构关系
3.0 简单的基本结构
如下这些重载和构造实现起来比较简单,难的主要在后面
struct __TreeIterator
{
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T, Ref, Ptr> Self;
Node* _node;
__TreeIterator(Node* node)
:_node(node)
{}
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;
}
}
3.1 begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?
答案是行不通的,因为对end()位置的迭代器进行--
操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置,也就是说是一个哨兵位的头节点,不存数据
当然也是可以不加头节点做的
typedef __TreeIterator < T, T&, T* > iterator;
iterator begin()
{
Node* left = _root;
while (left && left->_left)
{
left = left->_left;
}
return iterator(left);
}
iterator end()
{
return iterator(nullptr);
}
3.2 operator++()
Situation | 寻找方式 | 节点 | 描述 |
---|---|---|---|
当前节点的右树不为空 | 右树中,中序遍历的第一个节点 | 右子树最左节点 | 无需描述 |
右子树等于空 | 沿着三叉链往上走,找出孩子不是父亲右的那个祖先 | 孩子不是父亲右的那个祖先 | 因为cur的右为空,所以说明cur所在的子树已经访问完了,cur是parent右的话,说明parent也已经访问完了,继续往上找 |
Self& operator++()
{
if (_node->_right)
{
//右树中中序的第一个节点
Node* left = _node->_right;
while (left->_left)
{
left = left->_left;
}
_node = _left;
}
else
{//右树为空
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur==parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
3.3 operator–()
访问顺序是 右子树 根 左子树
Situation | 寻找方式 | 节点 |
---|---|---|
左不为空 | 左树中,中序遍历的最后节点 | 左子树的最右节点 |
左为空 | 沿着三叉链往上走,找出孩子不是父亲左的那个祖先 | 孩子不是父亲左的那个祖先 |
Self& operator--()
{
if (_node->_left)
{
Node* right = _node->_left;
while (right->_right)
{
right = right->_right;
}
_node = right;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur==parent->_left)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
3.4 反向迭代器
反向迭代器可以利用一个正向迭代器来适配实现,这里就不采用迭代器萃取,因为太麻烦,所以说这里的反向迭代器采取的是一个迭代器适配器
反向迭代器的++相当于是正向迭代器的operator--
,–就是正向迭代器的operator++
// 反向迭代器--迭代器适配器
template<class Iterator>
struct Reverse_Iterator
{
typedef typename Iterator::reference Ref;
typedef typename Iterator::pointer Ptr;
typedef Reverse_Iterator<Iterator> Self;
Iterator _it;
ReverseIterator(Iterator it)
:_it(it)
{}
Ref operator*()
{
return *_it;
}
Ptr operator->()
{
return _it.operator->();
}
Self& operator++()
{
--_it;
return *this;
}
Self& operator--()
{
++_it;
return* this;
}
bool operator!=(const Self& s) const
{
return _it != s._it;
}
bool operator==(const Self& s) const
{
return _it == s._it;
}
};
3.5 rbegin()和rend()
reverse_iterator rbegin()
{
Node* right = _root;
while (right && right->_right)
{
right = right->_right;
}
return reverse_iterator(iterator(right));
}
reverse_iterator rend()
{
return reverse_iterator(iterator(nullptr));
}
4. 利用红黑树迭代器
map和set的迭代器关键在于直接利用红黑树的迭代器,所以说红黑树的迭代器要写好
注意这里的typename一定要加
4.1 set
//这里需要加一个typename因为这里的红黑树还没有实例化,没有实例化不能去类内找内置类型,所以说要加
typedef typename RBTree<K,K,set_KeyOfT>::iterator iterator;
typedef typename RBTree<K, K, set_KeyOfT>::reverse_iterator reverse_iterator;
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
4.2 map
typedef typename RBTree<K, pair<const K, V>, map_KeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, map_KeyOfT>::reverse_iterator reverse_iterator;
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
5. map 和 set 模拟实现
这里有了迭代器,可以把pair中的first改成iterator去替换原来的Node*,比如在insert中的修改
也就是说其实map和set这里涉及的都是封装,本质上是把红黑树实现好,这里红黑树的erase没有实现,所以map和set这里也不封装了
5.1 map
template<class K, class V>
class map
{
//内部类封装一个仿函数,用来返回key
struct map_KeyOfT
{
const K& operator()(const pair<const K,V>& kv)
{
return kv.first;
}
};
public:
typedef typename RBTree<K, pair<const K, V>, map_KeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, map_KeyOfT>::reverse_iterator reverse_iterator;
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
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(make_pair(key, V()));
return ret.first->second;
}
private:
RBTree<K, pair<const K, V>, map_KeyOfT> _t;
};
5.2 set
template<class K>
class set
{
//内部类封装一个仿函数,用来返回key
struct set_KeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
//这里需要加一个typename因为这里的红黑树还没有实例化,没有实例化不能去类内找内置类型,所以说要加
typedef typename RBTree<K,K,set_KeyOfT>::iterator iterator;
typedef typename RBTree<K, K, set_KeyOfT>::reverse_iterator reverse_iterator;
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const K& k)
{
return _t.Insert(k);
}
private:
RBTree<K, K,set_KeyOfT> _t;
};
代码在我的github仓库https://github.com/Allen9012/cpp/tree/main/STL/%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0map%E5%92%8Cset