在这之前我们学习了红黑树的模拟实现,学习了如何使用map和set,同时我们也了解到,map和set底层都是用红黑树来实现的,本文我们就要自己动手封装一下map和set这两大容器,用我们之前学的红黑树来实现一下……
前情回顾:手撕红黑树 传送门
前情回顾:map和set 传送门
前提疑问:
在我们这所有的之前我们知道,map和set这两个容器都是用红黑树来实现的,那么就有了接下来的问题。
答案:
很显然根据STL的设计理念,是不可能是两个容器各自用一棵红黑树的,这不符合泛型编程的理念,实际上确实是用的同一棵树,只是对我们之前的红黑树做了一些改动。
我们来看一下STL官方库的原码:
这样设计的好处,两个容器都可以复用同一棵红黑树,体现了泛型编程的思想。
我们设T为红黑树的结点:
这时我们就有了另一个疑问,两个模板参数的第一个Key,不能省略掉吗??
因为map的这个类中,无论怎么省略都会有一个查找函数…
如果将Key省略的话,map中查找数据就不知道数据的类型了,所以必须保留Key。
综上所述:
enum Colour
{
RED,
BLACK,
};
//上层维度的一个泛型
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data; //数据
Colour _col;
RBTreeNode(const T& data)
: _data(data)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
{}
};
pair比较大小:
很显然这种比较规则不是我们所想要的,并且map和set想要取到用来比较的数据是不同的。
为了取到我们想要的数据,我们引入了仿函数:
因为map和set中需要比较的数据取出方式不同,所以分别在map和set类中实现不同的仿函数。
具体代码如下:
//红黑树的实现
//KeyOfT --> 支持取出T对象中key的仿函数
template<class K, class T, class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
typedef __RBTreeIterator<T, T&, T*> iterator;
typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
//构造 拷贝构造 赋值 和析构 跟搜索树实现方式是一样的
//迭代器中序遍历,要找最左结点
iterator Begin()
{
Node* subLeft = _root;
while (subLeft && subLeft->_left)
{
subLeft = subLeft->_left;
}
//树的迭代器用结点的指针就可以构造
return iterator(subLeft);
}
iterator End()
{
return iterator(nullptr);
}
const_iterator Begin() const
{
Node* subLeft = _root;
while (subLeft && subLeft->_left)
{
subLeft = subLeft->_left;
}
//树的迭代器用结点的指针就可以构造
return const_iterator(subLeft);
}
const_iterator End() const
{
return const_iterator(nullptr);
}
pair<iterator, bool> Insert(const T& data)
{
//1、搜索树的规则插入
//2、看是否违反平衡规则,如果违反就需要处理:旋转
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK; //根节点是黑色
return make_pair(iterator(_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(iterator(cur), 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;
assert(grandfather);
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//情况一:(叔叔存在且为红)
if (uncle && uncle->_col == RED)
{
//祖父和叔叔变成黑色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
//情况二:(叔叔不存在 or 叔叔存在且为黑)
else
{
//单旋
// g
// p
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//双旋
// g
// p
// c
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
//无论父亲和叔叔是左是右都是一样的
//grandfather->_right == parent;
else
{
Node* uncle = grandfather->_left;
//情况一:
if (uncle && uncle->_col == RED)
{
//祖父和叔叔变成黑色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else
{
//单旋
// g
// p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//双旋
// g
// p
// c
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
//父亲为空就出循环,将根节点设置成黑色
_root->_col = BLACK;
return make_pair(iterator(newnode), true);
}
}
红黑树中查找函数:
iterator Find(const K& key)
{
Node* cur = _root;
KeyOfT kot;
while (cur)
{
if (kot(cur->_data) < key)
{
cur = cur->_right;
}
else if (kot(cur->_data) > key)
{
cur - cur->_left;
}
else
{
return iterator(cur);
}
return End();
}
}
虽然map和set这种关联式容器的迭代器使用起来和序列式容器使用起来一样,但是底层实现却不尽相同。
备注:
而库中的红黑树则是设计了一个哨兵位的头结点:
库中实现的end就和我们所实现的不同了,我们实现的是简化版的。
就是正常的运用operator,但是operator->和list中讲的一样,返回的是地址的原因是为了连续访问(连续的operator->会优化成一个->)
operator->连续优化问题: 传送门
这里迭代器的++和- - 需要分类一下,分别是:前置++,- - 、后置++,- -
前置++ :
因为我们是中序遍历,我们访问完自己之后,下一个该访问哪一个结点?
it走到哪,说明哪个结点已经访问过了,接下来我们的操作不是递归,但是胜似递归。
我们大致的思路:
分如下两种情况:(重点)
为什么右子树为空时,不能直接跳到该结点的父亲节点吗?
答案是不行的,如图所示:
如果按照问题中那样右子树为空直接跳到父亲的话,5->6,6的右子树不为空,6->7,然后7的右子树为空,7->6,然后6的右子树不为空,6->7,然后7的右子树为空,7->6……这样就反复横跳了……
所以我们找到一个办法:那就是找孩子是祖先的左的那个祖先节点。
这样就实现了非递归,这种非递归的前提是(一定是三叉链)
我们大致的思路:
前置- -就是倒着走,同样还是对当前位置分两种情况:(重点)
只要前置++理解了,那么前置- -完全就是前置++倒过来走一遍。
后置++、后置- - :
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
Node* _node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
__RBTreeIterator(Node* node)
:_node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
if (_node->_right == nullptr)
{
//找祖先里面,孩子是父亲左的那个
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && parent->_right == cur)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
else
{
//右子树的最左结点
Node* subLeft = _node->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
//左为空
_node = subLeft;
}
return *this;
}
Self& operator--()
{
if (_node->_left == nullptr)
{
//找祖先里面,孩子是父亲
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
else
{
//左子树的最右结点
Node* subRight = _node->_left;
while (subRight->_right)
{
subRight = subRight->_right;
}
_node = subRight;
}
return *this;
}
Self operator++(int)
{
Self tmp(*this);
++(*this);
return tmp;
}
Self operator--(int)
{
Self tmp(*this);
--(*this);
return tmp;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
有了上面的红黑树的改装,我们这里的对map和set的封装就显得很得心应手了。
map的封装:
template<class K, class V>
class map
{
//定义一个内部类
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)//operator() 可以像函数一样去使用
{
return kv.first;
}
};
public:
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;
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 = insert(make_pair(key, V()));
return ret.first->second;
}
private:
RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
这里map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。
对set的封装:
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)//operator() 可以像函数一样去使用
{
return key;
}
};
public:
//加上typename告诉编译器这是个类型,类模板实例化了再去取
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
iterator begin() const
{
return _t.Begin();
}
iterator end() const
{
return _t.End();
}
pair<iterator, bool> insert(const K& key)
{
//pair::iterator, bool> ret = _t.Insert(key);
auto ret = _t.Insert(key);
return pair<iterator, bool>(iterator(ret.first._node), ret.second);
}
iterator find(const K& key)
{
return _t.Find(key);
}
private:
RBTree<K, K, SetKeyOfT> _t;
};