目录
改造红黑树
红黑树的迭代器
map的模拟实现
set的模拟实现
在上一篇博客中,我们实现了红黑树,但是红黑树节点中的值是pair<K,V> _kv形式,这种红黑树适用于map的底层,那么如果我们想要红黑树节点中的值是key的形式(适用于set的底层),我们该怎么搞呢?难道我们只把红黑树节点的类型改一下,其它基本保持不变,再来实现一遍吗?这未免太繁琐了!
我们先来看一看stl库中如何实现map和set的:
它们貌似用的同样的红黑树实现的,它们的区别在于红黑树的第二个模版参数,一个是key,一个是kv的pair,我们再来看看stl库里怎么实现的rb_tree,
在stl中,红黑树在实现时,并没有直接确定是key还是kv的pair,而是由模版的第二个参数Value决定,本质上这是一个高级的泛型编程,不得不说,大佬写的模版比我们技高一筹!
改造红黑树
为了达到和stl库中一样的效果,我们需要对上节的红黑树进行改造,即对红黑树中值的类型不指定,使用模版:
//写成一个模版,T有可能是key,有可能是pair
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Color _color;
T _data;
RBTreeNode(const T& data)
: _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _color(RED)
{}
};
调用示意图如下:
肯定有人会有疑问,RBTree中第一个模版参数class K好像没用到,其实在Find函数中可以用到,Find函数肯定要通过Key来查找(Node* Find(const K& key))。
当我们对红黑树改造后,在Insert函数中,会遇到这样的问题:我们需要对待插入的数据和节点值进行比较以便插入,但是,待比较数据和节点值有可能是key,也有可能是pair,为了解决这样的问题,在class set和class map中分别增加一个内部类,
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
...
};
template<class K,class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
...
};
在class RBTree上加模版参数class KeyOfT,然后在类中定义KeyOfT的对象,
template<class K,class T,class KeyOfT>
class RBTree
bool Insert(const T& data)
{
if (_root == nullptr)
{
_root->_color = BLACK;
return true;
}
KeyOfT kot;
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//K
//pair<K,V>
//kot对象,是用来取T类型的data对象中的key
//如果data是pair,则通过kot仿函数去调用上面的内部类,从而得到pair中的key
if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else if(kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
...
}
红黑树的迭代器
我们先来看stl里是怎么实现的:
迭代器里就是一个节点指针去封装,
然后实现operator++、operator->等的重载,我们自己来实现一下红黑树的迭代器:
template<class T,class Ref,class Ptr>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
Node* _node;
__RBTreeIterator(Node* node)
:_node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_right)
{
//下一个,右树最左节点
Node* leftMin = _node->_right;
while (leftMin && leftMin->_left)
{
leftMin = leftMin->_left;
}
_node = leftMin;
}
else
{
//下一个,孩子等于父亲左的那个祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
};
上面中最复杂的是operator++的实现,我们分情况讨论一下:
1.当前结点的右子树不为空,中序下一个访问的节点,是右子树的最左节点。
2.右为空,下一个访问,倒着在祖先里找,找孩子是父亲左的祖先。
在红黑树的实现中,我们typedef一下:
typedef __RBTreeIterator<T, T&, T*> Iterator;
在红黑树中,我们要实现begin()、end()等接口:
//begin是最左节点
Iterator begin()
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
{
leftMin = leftMin->_left;
}
return Iterator(leftMin);
}
//end是最后一个节点的下一个,也就是nullptr
Iterator end()
{
return Iterator(nullptr);
}
map的模拟实现
如果我们想要实现map或set的拷贝构造,我们知道默认的拷贝构造,对于内置类型完成值拷贝,针对自定义类型会调用它的拷贝构造,这样会造成浅拷贝,所以,我们需要自己写拷贝构造函数:
//强制生成默认构造函数
RBTree() = default;
RBTree(const RBTree<K, T, KeyOfT>& t)
{
_root = Copy(t._root);
}
private:
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
//构造新节点
Node* newroot = new Node(root->_data);
//改变新节点的颜色
newroot->_color = root->_color;
//把递归Copy得到的子树链接到newroot的左侧
newroot->_left = Copy(root->_left);
//让newroot->_left向上链接到newroot
if (newroot->_left)
newroot->_left->_parent = newroot;
//把递归Copy得到的子树链接到newroot的右侧
newroot->_right = Copy(root->_right);
//让newroot->_left向上链接到newroot
if (newroot->_right)
newroot->_right->_parent = newroot;
return newroot;
}
除此之外,我们还要写析构函数:
~RBTree()
{
Destroy(_root);
_root = nullptr;
}
void Destroy(Node* root)
{
//走一个后序遍历
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
在map中我们还要实现operator[]重载,我们之前学过,库里的[]重载是调用insert函数,operator[]返回值是key所在的迭代器,
因此,我们主要就是调整insert函数,insert函数的返回值类型是pair<Iterator,bool>,修改insert函数的返回值:
pair<Iterator,bool> Insert(const T& data)
实现operator[]:
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
//ret.first.operator->()->second
return ret.first->second;
}
完整的map模拟实现代码如下:
namespace ghs
{
template<class K,class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
//此时RBtree还没有实例化,需要加typename告诉编译器,等实例化了再去确认
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree<K, const K, MapKeyOfT>::ConstIterator const_iterator;
const_iterator begin()const
{
return _t.begin();
}
const_iterator end()const
{
return _t.end();
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
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 = _t.Insert(make_pair(key, V()));
//ret.first.operator->()->second
return ret.first->second;
}
private:
//由于RBTree的第二个模版参数是用来确定节点的类型,map要求pair中给的key不能变,但是val可以变
//因此,pair<const K, V>中第一个参数加const,第二个参数不加const
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}
set的模拟实现
namespace ghs
{
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
//没有实例化的类,去取,需要加typename
typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;
const_iterator begin()const
{
return _t.begin();
}
const_iterator end()const
{
return _t.end();
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
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的第二个模版参数是用来确定节点的类型,set的key是不可以改变的,所以要加const
RBTree<K, const K, SetKeyOfT> _t;
};
}
在学完vector、list、map、set的迭代器iterator后,我们可以进一步理解一下封装:它们底层的operator++、operator*、operator->完全不一致,但是对外提供的方式是一致的(统一的迭代器行为)。