前面我们实现了模拟实现了红黑树,今天就通过复用之前的代码来封装 map 和 set。
map、set的底层都是红黑树,但是set在使用时需要传一个 K 模板参数就可以了,但是 map 则需要传一个 pair
是的,我们可以使用模板让编译器自己生成,虽说在编译器生成后还是两棵独立的红黑树代码,但是我们手写部分明显少了很多,也更方便后续的维护。
前面我实现红黑树的时候是使用 K,V 模板实现的,但是我们这里可以对其修改一下,变成 K,T 模板,T指的是这棵树存储的是 K 类型还是 pair
还有一个问题,我们这里使用的是 K,T 模板参数,既然 T 参数可以表示底层存储类型,为什么还要一个 K 模板参数呢?这样会不会很冗余?
你可以想想 Insert、find、erase 的场景,在 Insert 的时候确实是可以直接用 T 没错。但是如果你是 map ,你在 find、erase 的时候,是使用 pair
所以我们的 RBTreeNode 直接用 T 模板参数表示它的存储的类型。
这里又引申出来个问题,我们之前使用 K,V 的时候可以直接使用它的 first 进行比较,但是这里我们并不知道它的类型到底是 K 还是 pair ,所以这里我们需要引入一个KeyOfT 仿函数来帮我们支持比较。
// 修改前
template<class K, class V>
struct RBTreeNode
{
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
{
}
};
template<class K, class V>
class RBTree {}
// 修改后
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)
{
}
};
template<class K, class T, class KeyOfT>
class RBTree {}
在 map 里面实现 MapKeyOfT,在 set 里面实现 SetKeyOfT,用于各自支持比较
template<class K, class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
}
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
RBTree<K, const K, SetKeyOfT> _t;
};
我们前面说过在红黑树类前有一个 KeyOfT 模板参数,等需要使用的时候,就在要使用的地方通过 KeyOfT 实例化一个对象,再把要比较的对象传过去再进行比较。
// 类似于这样的使用方式
KeyOfT kot;
if (kot(cur->_data) < kot(data))
因为我们在 map、set 类中分别存在着一个对应的红黑树对象,所以我们想的应该是直接通过调用底层红黑树的 Insert 函数来实现,我们外层的 map、set 仅仅只是一层壳。
template<class K>
class set
{
bool insert(const K& key)
{
return _t.Insert(key);
}
};
template<class K, class V>
class map
{
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
}
要想支持 map、set 的迭代器,得现在底层支持 RBTree 的迭代器
还是老规矩,迭代器模板参数数量为三个,分别为 T、Ref、Ptr,以便于在使用时通过控制参数是否为 const 类型来实例化为 普通迭代器 和 const 迭代器。
RBTree 的迭代器和链表的迭代器比较相似,因为都是链式结果。最大的区别在于它们++、-- 的方式不同。因为RBTree 的底层是一棵树,所以相对而言会复杂许多。
operator++
因为红黑树是一棵自平衡的二叉搜索树,所以我们对它 ++ 的设计就是通过模仿中序的方式 来实现的。
那么就来讨论一下中序。
中序代表着这一节点已经使用,而下一节点是右子树的最左(最小)节点,所以:
如何表示 begin 呢?
按照二叉搜索树的性质,它第一个访问位置为整棵树的最左节点,那么就是它
如何表示 end 呢?
按照上面++的思路,当我们找完最后一个有效节点之后,你再按照上面的思路进行一次++,你会发现你到了 _root 节点的父节点的位置。_root 节点的父节点为空,所以我们将 end 设置为 nullptr
operator–
迭代器 – 的实现刚好和 ++ 反过来,它的访问顺序是 右->根->左。
其他的和之前实现其他迭代器的方式是一样的
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 = _root->_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;
}
};
都是通过调用底层红黑树的迭代器来实现的。
// map:
namespace zkp
{
template<class K, class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
// 这里 typename 是为了声明这是类型
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();
}
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
};
// set中:
namespace zkp
{
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();
}
private:
// 反正 set 的key不允许修改,直接加 const
RBTree<K, const K, SetKeyOfT> _t;
};
}
我们在使用库中的 map 时,是可以直接使用 [] 进行插入和修改的,而要支持这个,则需要修改底层红黑树 Insert 的返回值,和 map 中 insert 的返回值
// map 中:
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
// set 中:
// 这里只是为了维护代码堆统一性,并无实际作用
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
// 底层红黑树的插入
pair<Iterator, bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
//return pair(Iterator(_root, _root), true);
return { Iterator(_root, _root), true }; // 直接用 C++11 的方式写吧
}
// 插入部分
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)
{
// 变色
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
// 将 g 作为新的 c 继续向上调整
cur = grandfather;
parent = cur->_parent;
}
else
{
// 到这就该考虑 p、c 同侧还是异侧的情况了
// p、c同侧单旋,异侧双旋
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
{
// 还是考虑同侧异侧的问题,代码和上面刚好对称
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 };
}