C++进阶——map&set的实现
红黑树的迭代器
迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以前问题:
迭代器的定义
begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:
在这里我们不用设置一个循环迭代器,begin()直接就定义为最小的那棵树枝节点(最左节点),end()不是最右节点那棵树枝而是设置成空节点。
operator++()
只要cur右子树不会空就往右子树的最左子树走,但凡右子树为空就得往回走,朝着根走(中序就是左 、中、 右顺序走),当curparent->left,parent就是返回的节点,但是curparent->right,就接着往上走找父节点的父节点。
Self& operator++()//前置++
{
if (_node->_right)
{
// 1、右不为空,下一个就是右子树的最左节点
Node* subLeft = _node->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
_node = subLeft;
}
else
{
Node* cur = _node;
Node* parent = _node->_parent;
while (parent && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
测试:
operator–()
Self& operator--()//前置--
{
//分三种情况讨论:_pNode 在head的位置,_pNode 左子树存在,_pNode 左子树不存在
// 1. _pNode 在head的位置,--应该将_pNode放在红黑树中最大节点的位置
if (_node->_pParent->_pParent == _node && _node->_color == RED)
_node = _node->_pRight;
else if (_node->_pLeft)
{
// 2. _pNode的左子树存在,在左子树中找最大的节点,即左子树中最右侧节点
_node = _node->_pLeft;
while (_node->_pRight)
_node = _node->_pRight;
}
else
{
// _pNode的左子树不存在,只能向上找
Node* pParent = _node->_pParent;
while (_node == pParent->_pLeft)
{
_node = pParent;
pParent = _node->_pParent;
}
_node = pParent;
}
return *this;
}
set的封装
首先要取set的键值对(很多人疑问set明明K和V是一个值,为什么又要取键值对的值)
那是因为map是一个真正的键值对,键值对怎么取值呢?这就需要自己写一个仿函数了就是这个 KeyOfT。
SetKeyOfT
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
MapKeyOfT
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
修改节点代码
还要改一下红黑树的节点代码
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
这里面我们没再使用K,V双类型了,而是使用T单一类型了,看STL源码发现也是这么做的,可以让编译器自己推演这个类型的是key值还是KV键值对。
主要是利用KeyOfT这样的仿函数进行提取键值。
测试结果
因为实现了迭代器就实现了范围for