前言
前面两个章节我们介绍了AVL树和红黑树,也了解到了c++stl里的map和set底层实现是应用了红黑树,今天我们就来自己封装一下map和set。在上两节的代码里,我们的AVL树和红黑树只固定了一种类型,也就是需要写两份来满足map和set,今天我们就要用泛型的思想通过模板参数来同时实现set和map。底层就用到我们上一章的红黑树。
迭代器的实现
在最开始,我们首先要搞定迭代器,有了它我们才能实现++、--这些操作。我们需要定义一个结构体__TreeIterator。
__TreeIterator
template<class T>
struct __TreeIterator
{
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T> Self;
Node* _node;
__TreeIterator(Node* node)
:_node(node)
{
}
};
首先先给RBTreeNode<T> 、__TreeIterator<T>创建一个别名方便后面使用,然后写一个简单的构造函数。接下来我们先来实现简单的!=和==的函数重载。
!=和==的函数重载
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
这两个比较简单,就是比较两个Node的值是否相等来确定返回true还是false。
++、-- 迭代器
我先奉上代码再来解释:
Self& operator--()
{
if (_node->_left)
{
Node* subRight = _node->_left;
while (subRight->_right)
{
subRight = subRight->_right;
}
_node = subRight;
}
else
{
// 孩子是父亲的右的那个节点
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator++()
{
if (_node->_right)
{
// 下一个就是右子树的最左节点
Node* cur = _node->_right;
while (cur->_left)
{
cur = cur->_left;
}
_node = cur;
}
else
{
// 左子树 根 右子树
// 右为空,找孩子是父亲左的那个祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
先来解释++函数重载,不论是++还是--,其实都是根据中序遍历来确定的,也就是左子树、根、右子树的顺序。
规律如下:
如果该节点右子树不为空,那么就找右子树的最左节点。
如果该节点右子树为空,说明已经访问完了这棵子树,那么就需要找该节点(孩子)是父亲左的那个节点(祖先),这个祖先就是我们++之后的位置,也就是根,可以理解为这个祖先的左子树已经访问完了,根据中序遍历需要访问根了。
我们拿一个红黑树来说明:
1.以15这个节点来举例,我们知道 ++之后应该到17这个位置。
根据上面的规律,先访问右子树,发现右子树为空 ,那么就需要找15是父亲左的那个节点,循环判断后,发现17就是++之后的位置。
2.以17这个节点来举例,我们知道 ++之后应该到22这个位置。
根据上面的规律,先访问右子树,发现右子树不为空,那么就寻找右子树的最有节点,直到找到22位置。
--的思路和++类似,只不过是反过来。
规律如下:
如果该节点左不为空,那么就找左子树的最右节点。
如果该节点左为空,那么就需要找该节点(孩子)是父亲右的那个节点(祖先),这个祖先就是我们--之后的位置。
大家可是自己带入一下,这里就不举例子了。
*和->的重载
这个比较简单我直接把代码奉上:
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
RBTree
在之前我们是把他的数据类型写死的,不能同时实现map和set,今天我们就要使用模板来解决这个问题,在此之前,我先把map和set的代码写出来。
map
template<class K, class V>
class map
{
public:
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
// 对类模板取内嵌类型,加typename告诉编译器这里是类型
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
private:
RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
set
template<class K>
class set
{
public:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
private:
RBTree<K, K, SetKeyOfT> _t;
};
我们不难发现,map和set并没有多少东西,他只是进行了封装,底层调用的红黑树。 而红黑树的模板参数增多了,这就是为什么我们可以同时实例化出map和set的原因。接下来我们来详细看一下增加的模板参数
insert
template<class K, class V, class KeyOfV>
K表示key,V表示value,KeyOfv表示在比较时是通过key还是value来比较。
这样在set中实现了一个SetKeyofV,在map中实现了一个MapKeyofV,只需要在传参时将他们两个传入,便可以实例化出两个不同的版本。
SetKeyofV本身没有变化,他就是返回K这个类型的数据,MapKeyofV是将pair类型的first返回,这样就都可以进行比较了。
库里的insert并不是简单的bool,而是一个pair<iterator, bool>,若插入成功返回节点的迭代器和true,失败返回cur的迭代器和false。
pair<iterator, bool> Insert(const V& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(iterator(_root), true);
}
Node* parent = nullptr;
Node* cur = _root;
KeyOfV kot;
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;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
// g
// p u
// c
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{
// 变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上更新处理
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
// 单旋
// g
// p
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// 双旋
// g
// p
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else // parent == grandfather->_right
{
// g
// u p
// c
//
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
{
// g
// u p
// c
//
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return make_pair(iterator(newnode), true);
}
这就是改变后的insert代码。
[]重载
在map中[]的使用非常方便,既能添加又能修改,这里我们就来实现一下它,他其实就是用了红黑树里的insert,然后返回值变成了pair的second的引用,代码如下:
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
const的迭代器稍微复杂,有时间我会更新给大家。