一、思路
1. 改造RBTree
现在我们有一棵 R B T r e e RBTree RBTree,那么如何用它实现 m a p map map和 s e t set set?我们知道 m a p map map是 KV 结构, s e t set set是 K 结构,传统思路是两份 R B T r e e RBTree RBTree的代码一个改成 m a p map map,一个改成 s e t set set。这样可以实现但不够完美,重复性较高。那么如何优雅地实现呢?查看源码,学习大佬的思路:
m a p map map:
s e t set set:
查看源码发现:
m
a
p
map
map和
s
e
t
set
set向
R
B
T
r
e
e
RBTree
RBTree传模板参数时都是<key, value>
,
m
a
p
map
map传的是<Key, pair<const Key, Value>>
,
s
e
t
set
set传的是<Key, Key>
。
再看 R B T r e e RBTree RBTree源码的实现:
也就是说map和set传进RBTree的Key(第一个模板参数)在RBTree的节点里是没有使用的,RBTree的节点只保存Value。
综上一起看:
R B T r e e RBTree RBTree这一层,第二个模板参数就能决定 R B T r e e RBTree RBTree是 m a p map map还是 s e t set set,那为什么要传第一个模板参数?
不要忘了
R
B
T
r
e
e
RBTree
RBTree这一层有一个查找操作:iterator find(const Key& key)
是要直接使用第一个模板参数的。
namespace RB
{
// map --> RBTree<K, pair<const K, V>, MapKeyOfT> _t
// set --> RBTree<K, K, SetKeyOfT> _t
template<class K, class T, class KeyOfT>
class RBTree
{
public:
// 节点
typedef RBTreeNode<T> Node;
private:
Node* _root = nullptr;
};
}
2. 改造RBTree的节点
namespace RB
{
// 红黑树的颜色
enum Color
{
RED,
BLACK
};
// 红黑树的节点
// KV 改造为 T(T是 K 或 pair<const K, V>)
// KV由map和set层传过来,RBTree这一层看到的都是T
//template<class K, class V>
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent; //指向父节点的指针
//pair<K, V> _kv;
T _data;
Color _col;
//RBTreeNode(const pair<K, V>& kv)
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
}
这样一来新的问题出现了:我们在RBTree这一层看到的类型是T
,T
是 K
或 pair<const K, V>
,那么在查找、插入比较大小时RBTree不知道是其中的哪一个,K
可以直接比较大小,库中的pair
实现了关系运算符的重载也可以直接比较大小,但是库中的实现并不符合我们的预期。
template <class T1, class T2>
bool operator== (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs)
{ return lhs.first==rhs.first && lhs.second==rhs.second; }
template <class T1, class T2>
bool operator!= (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs)
{ return !(lhs==rhs); }
库中的实现是先比较first
,再比较second
,如果直接使用,在RBTree中就会出现[2, 3] < [2, 4]
,[2, 3]
插在[2, 4]
的左。
如何解决?看看源码在比较大小时是如何做的:
源码中是使用第三个模板参数KeyOfValue
传入一个仿函数用来获取Value中的Key,
二、实现
1. map和set的实现框架
namespace nb
{
template<class K, class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
private:
RB::RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
RB::RBTree<K, K, SetKeyOfT> _t;
};
}
2. 改造insert
对于RBTree的insert只需要把所有比较大小的地方用KeyOfT
获取key之后再比较
bool Insert(const T& data)
{
// 是空树直接将新增节点作为根
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
// KeyOfT
KeyOfT kot;// kot的作用:获取Key用于比较大小
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
// pair的 > 不能满足要求
// 获取插入data的key和cur->_data的key,比较大小
if (kot(data) > kot(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(data);
cur->_col = RED;
if (kot(data) > kot(parent->_data))
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
// parent为空,则cur是根节点不用继续更新
// parent节点颜色为红,需要继续相信向上调整
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
// p是g的左孩子
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
// 所有情况 p、cur都是红,g都是黑,
// 情况一:u存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;// cur变g继续更新
parent = cur->_parent;//更新p
}
else// u不存在 或 u存在且为黑
{
// 情况二:cur是p的左,p是g的左
if (cur == parent->_left)
{
// g
// p
// cur
// 对g右单旋
RotateR(grandfather);
// p变黑,g变红
parent->_col = BLACK;
grandfather->_col = RED;
}
else// 情况三:cur是p的右,p是g的左
{
// g
// p
// cur
RotateL(parent);
// g
// cur
// p
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;// 旋转完成后即可退出
}
}
else// p是g的右孩子
{
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)
{
// g
// p
// cur
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// 情况三:
// g
// p
// cur
RotateR(parent);
// g
// cur
// p
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;// 旋转完成后即可退出
}
}
}
// 避免更新出错,把根节点颜色直接给黑色
_root->_col = BLACK;
return true;
}
3. 实现迭代器
3. 1 RBTree的迭代器类
要实现map和set的迭代器实际上就是要实现RBTree的迭代器。
//RBTree迭代器类
template<class T, class Ref, class Ptr>
struct _RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef _RBTreeIterator<T, Ref, Ptr> Self;
Node* _node;// 与list迭代器相同,有一个节点成员
_RBTreeIterator(Node* node)// 构造函数:用节点构造一个迭代器
:_node(node)
{}
// * 和 ->
Ref operator* ()
{
return _node->_data;
}
Ptr operator-> ()
{
return &_node->_data;
}
bool operator!= (const Self& s)
{
return _node != s._node;
}
// ++ 和 --
// ……
};
STL明确规定,begin()
与end()
代表的是一段前闭后开的区间,而对红黑树进行中序遍历后, 可以得到一个有序的序列
因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪里? 能否给成nullptr
呢?
答案是不能,因为对end()位置的迭代器进行--
操作,必须要能找最 后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置
本文的实现与STL实现不同:不使用header节点。begin()一样是最左节点,end()是nullptr,end()位置的迭代器不能进行--
操作。
template<class K, class T, class KeyOfT>
class RBTree
{
public:
// 节点
typedef RBTreeNode<T> Node;
// 迭代器
typedef _RBTreeIterator<T, T&, T*> iterator;
typedef _RBTreeIterator<T, const T&, const T*> const_iterator;
// 普通迭代器
iterator begin()
{
Node* Left = _root;
// 找最左节点
while (Left && Left->_left)
{
Left = Left->_left;
}
return iterator(Left);
}
iterator end()
{
return iterator(nullptr);
}
// const迭代器
const_iterator begin() const
{
Node* Left = _root;
// 找最左节点
while (Left && Left->_left)
{
Left = Left->_left;
}
return const_iterator(Left);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
3. 2 迭代器类的operator++
我们不需要考虑整棵树(无需递归),只需要考虑局部的子树的中序遍历(左 根 右),++
走到中序的下一个,不需要考虑其他子树的节点。
Self& operator++()
{
if (_node->_right)
{
Node* minRight = _node->_right;
while (minRight->left)
{
minRight = minRight->_left;
}
_node = minRight;// ++到minRight
}
else
{
Node* parent = _node->_parent;
Node* cur = _node;
// cur 不是 parent 的左就一直往上走
// cur 是 parent 的右说明以 parent 为根的树走完了
// parent 为空说明走到根节点的 _parent
while (parent && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
3. 3 迭代器类的operator--
operator--
与operator++
是相反的逻辑
Self& operator--()
{
if (_node->_left)
{
// 左子树的最大节点
Node* maxLeft = _node->_left;
while (maxLeft->_right)
{
maxLeft = maxLeft->_right;
}
_node = maxLeft;// --到maxLeft
}
else
{
Node* cur = _node;
Node* parent = _node->_parent;
while (parent && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
3. 4 map的迭代器:
template<class K, class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
// 要使用关键字 typename 显式地指明iterator是一个类型
// 否则编译器会误认为是一个静态成员变量而产生编译错误
typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
// 实现map的迭代器
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
const_iterator begin() const
{
return _t.begin();
}
const_iterator end() const
{
return _t.end();
}
void Insert(const pair<K, V>& _kv)
{
_t.Insert(_kv);
}
private:
RB::RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
3. 5 set的迭代器:
set的迭代器像下面这样实现是有问题的:
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
// 要使用关键字 typename 显式地指明const_iterator是一个类型
// 否则编译器会误认为是一个静态成员变量而产生编译错误
typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
// 实现set的迭代器
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
const_iterator begin() const
{
return _t.begin();
}
const_iterator end() const
{
return _t.end();
}
void Insert(const K& key)
{
_t.Insert(key);
}
private:
RB::RBTree<K, K, SetKeyOfT> _t;
};
我们知道set的值是不能修改的,因为它要保证RBTree的结构、性质。所以迭代器也不能修改值。
// 普通迭代器和const迭代器都是const_iterator
typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
像下面的代码就会编译报错:
auto it = s.begin();
while (it != s.end())
{
std::cout << (*it) << " ";
*it *= 10;// 不能修改
++it;
}
但是将修改操作的代码注释掉后:
下面代码中,_t.begin()
是普通迭代器,而 set 的 iterator
是const迭代器,这两种类型是不兼容的,不能直接进行转换。
// 实现set的迭代器
iterator begin()
{
return _t.begin();
}
源码里只提供普通迭代器的const修饰版本:
把set的普通迭代器用const修饰,删除const_iterator的:
// 实现set的迭代器
iterator begin() const
{
return _t.begin();
}
iterator end() const
{
return _t.end();
}
4. 实现map的operator[]
map的operator[]
底层调用的是insert,所以我们要改造RBTree中的insert:
在所有返回的位置返回pair<iterator, bool>
即可。
pair<iterator, bool> Insert(const T& data)
{
// 是空树直接将新增节点作为根
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
// 返回新增节点位置的迭代器和是否插入成功
return std::make_pair(iterator(_root), true);
}
KeyOfT kot;// kot的作用:获取Key用于比较大小
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
// pair的 > 不能满足要求
// 获取插入data的key和cur->_data的key,比较大小
if (kot(data) > kot(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else
{
// 返回已存在值的迭代器,充当查找
return std::make_pair(iterator(cur), false);;
}
}
cur = new Node(data);
// cur可能会向上调整发生变化
// 需要一个newNode用于返回新增节点的迭代器
Node* newNode = cur;
cur->_col = RED;
if (kot(data) > kot(parent->_data))
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
// parent为空,则cur是根节点不用继续更新
// parent节点颜色为红,需要继续相信向上调整
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
// p是g的左孩子
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
// 所有情况 p、cur都是红,g都是黑,
// 情况一:u存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;// cur变g继续更新
parent = cur->_parent;//更新p
}
else// u不存在 或 u存在且为黑
{
// 情况二:cur是p的左,p是g的左
if (cur == parent->_left)
{
// g
// p
// cur
// 对g右单旋
RotateR(grandfather);
// p变黑,g变红
parent->_col = BLACK;
grandfather->_col = RED;
}
else// 情况三:cur是p的右,p是g的左
{
// g
// p
// cur
RotateL(parent);
// g
// cur
// p
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;// 旋转完成后即可退出
}
}
else// p是g的右孩子
{
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)
{
// g
// p
// cur
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// 情况三:
// g
// p
// cur
RotateR(parent);
// g
// cur
// p
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;// 旋转完成后即可退出
}
}
}
// 避免更新出错,把根节点颜色直接给黑色
_root->_col = BLACK;
return std::make_pair(iterator(newNode), true);
}
map中的insert和operator[]:
template<class K, class V>
class map
{
// ……
public:
// 要使用关键字 typename 显式地指明iterator是一个类型
// 否则编译器会误认为是一个静态成员变量而产生编译错误
typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
// 实现map的迭代器
// ……
//void Insert(const pair<K, V>& kv)
//{
// _t.Insert(kv);
//}
pair<iterator, bool> Insert(const pair<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:
RB::RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
但是当我们把 set 中 insert 也改成:
pair<iterator, bool> Insert(const K& key)
{
return _t.Insert(key);
}
又出现了下面的错误:
出现错误的原因是:RBTree层的迭代器是普通迭代器,set层的迭代器是const迭代器,无法直接转换。
那能不能像前面的begin一样在后面加一个 const 修饰呢?
答案是不能。因为使用 const 修饰符的成员函数,它们不会修改对象的状态,但是insert是需要向树中插入值的,会修改对象的状态。
那么如何解决?
源码是这样解决的:
照着源码解决:
pair<iterator, bool> Insert(const K& key)
{
//return _t.Insert(key);
pair<typename RB::RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
return pair<iterator, bool>(ret.first, ret.second);
}
除了上面的方法,还可以实现下面的拷贝构造函数,迭代器一般是不需要写拷贝构造的,用一个迭代器拷贝出另一个迭代器,我们的期望就是两个迭代器指向同一个节点,系统默认生成的(浅拷贝)就符合要求。
下面的构造函数,对象是普通迭代器时,使用拷贝构造的方式来初始化新的迭代器,对象是 const 迭代器时,使用构造的方式来初始化 新的迭代器。
实现这个函数之后,就可以将普通迭代器直接构造转换成const迭代器,set的insert也就可以直接return了。
// 普通迭代器的时候,它是拷贝构造
// const迭代器的时候,它是构造,支持用普通迭代器构造const迭代器
template<class T, class Ref, class Ptr>
typedef _RBTreeIterator<T, T&, T*> iterator;
_RBTreeIterator(const iterator& cit)
:_node(cit._node)
{
//std::cout << "_RBTreeIterator(const iterator& cit)" << std::endl;
}
之前实现的list的如果没有下面这个函数是不能将普通迭代器直接构造转换成const迭代器
// 普通迭代器的时候,它是拷贝构造
// const迭代器的时候,它是构造,支持用普通迭代器构造const迭代器
typedef __list_iterator<T, T&, T*> iterator;
__list_iterator(const iterator& cit)
:_pnode(cit._pnode)
{
//std::cout << "__list_iterator(const __list_iterator& cit)" << std::endl;
}
下图是没有上面的函数,会编译报错。
整体代码GitHub