本文为AVL树和红黑树的额外文章,阅读本文至少需要阅读其中一篇,主要讲解红黑树的iterator,以及作为multiset,multimap底层容器时的更改,红黑树插入删除的旋转平衡请见RedBlackTree 红黑树
文章目录
对红黑树节点的改造
enum RBColor { RED, BLACK };
template<class T>
struct RBTreeNode
{
private:
typedef T data_type;
typedef RBTreeNode<data_type> self;
typedef self* self_pointer;
public:
explicit RBTreeNode(const data_type& data)
: _data(data)
{}
explicit RBTreeNode(data_type&& data = data_type()) //右值移动构造_data
: _data(std::forward<data_type>(data))
{}
template<class... Args>
RBTreeNode(Args&&... args) //可变参数模板构造_data(实质上,上面两种构造都可以被改构造替代,出于阅读性建议保留)
: _data(args...)
{}
void swap(self & node)
{
std::swap(_data, node._data);
std::swap(_color, node._color);
std::swap(_pLeft, node._pLeft);
std::swap(_pRight, node._pRight);
std::swap(_pParent, node._pParent);
}
data_type _data = data_type();
RBColor _color = RBColor::RED;
self_pointer _pLeft = nullptr;
self_pointer _pRight = nullptr;
self_pointer _pParent = nullptr;
};
对比先前红黑树的节点
可以看到,先前红黑树节点中存储的是一个pair,但改造后的节点中只存储了一个模板参数T(data_type),这是为了适配map以及set进行的改造。
先前存储pair时,我们使用pair的first作为key,second作为value,组成键值对,但这种写法是不是就直接固定了,假如用户想要用pair的second作为key,first作为value时,就只能更改红黑树的原码了,这是非常不好的一种情况。
所以将其改为模板T类型,那怎么拿取key进行比较
使用仿函数,将仿函数作为拿去key的方法,传入红黑树的模板参数中,那么只需在红黑树中将该仿函数实例化,使用其对T类型中的key进行拿取
当T为单个类型时,传入key返回key(在set中使用的仿函数)
struct SetKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
当T为pair类型时,传入pair返回pair的first(在map中使用的仿函数)
typedef pair<K, V> data_type;
struct MapKeyOfValue
{
const K& operator()(const data_type& kv)
{
return kv.first;
}
};
那么,同理,无论是什么类型,我们都可以使用KeyOfValue仿函数对其进行取key。
对红黑树模板参数的改造
template<class K, class T, class KeyOfValue, class Compare = less<K>>
class RBTree;
class T
根据上述中对红黑树节点的改造,可以得知,在红黑树的插入删除过程中,需要KeyOfValue仿函数对T类型取key进行比较,故在模板参数中添加该模板。
class K
可能有些读者会疑惑,为什么还需要class K模板,已经有了T模板作为红黑树节点中存储的值,也有KeyOfValue可以取到T类型中的key,那么模板类型K是不是有点多余?
但有一点需要考虑的是,find函数,find函数的原型:
iterator find(const K& key)
假设我们将模板类型K去掉之后,find的参数我们就无法构造了。
class Compare
使用STL库中的map与set时,可以看出,库中的容器允许传入less等仿函数,对其中数据的排序方式进行调整。
所以在构造红黑树作为容器的底层结构时也需要支持,但一个Compare怎么做到对key值比较大于,小于,等于操作?
使用一个比较符号完成大于等于小于比较
if (a > b) //a > b
{ ... }
else if (b > a) //a < b
{ ... }
else //a == b
{ ... }
iterator的实现
先讲解一个iterator之后,再去讲解insert操作和erase操作的更改
红黑树其本质也是一颗二叉搜索树,只不过增加了一些规则限制,使得红黑树可以完成高度相对平衡,高度可以维持在logN,所以当对红黑树进行中序遍历的时候,就可以得到一个有序序列,所以使用iterator对红黑树的遍历也是中序遍历。
那么根据二叉搜索树中序遍历的性质,可以得出以下结论
1.begin一定是最左侧节点(最小节点)
2.end - 1 一定是最右侧节点(最大节点)
3.自增时,下一个节点一定大于/等于自身(multi系列允许存储相同key值的元素)
4.自减时,下一个节点一定小于/等于自身
5.一个节点的左侧节点一定小于/等于自身,右侧节点一定大于/等于自身
自增
首先应该判断是否有右子树,若有右子树,则下一个节点,一定是右子树中最小的节点。
若没有右子树,则该节点一定为以自身为根的子树中最大的节点,以自身为根的子树中序遍历完成,向上查找,寻找比自己大的双亲节点
若自身为父亲的左子树,则父亲节点即为下一个节点。
若自身为父亲的右子树,则父亲节点小于等于自身,根据中序遍历的性质,一定先遍历了父亲节点后再遍历了自身,则继续向上查找。
自减
首先应该判断是否有左子树,若有左子树,则下一个节点,一定是左子树中最大的节点。
若没有左子树,则该节点一定为以自身为根的子树中最小的节点,以自身为根的子树中序遍历开始,向上查找,寻找比自己小的双亲节点
若自身为父亲的右子树,则父亲节点即为下一个节点。
若自身为父亲的左子树,则父亲节点大于等于自身,根据中序遍历的性质,一定先遍历了自身后再遍历双亲节点,则继续向上查找。
但这样还有一个最大的问题,该节点为起始节点时自减或者最后节点自增的话,程序就会崩掉,而且end也不能使用nullptr构造,若使用nullptr构造end的话,end--就无法判断了。
所以,需要在红黑树中添加一个_pHead头节点,其左指针指向最左侧节点,右指针指向最右侧节点就可以了。end就可以使用_pHead构造。(树中节点无需指向_pHead,只需在class iterator中也存储一个_pHead,这样就不用担心增加头节点后会导致红黑树性质被破坏)
template<class TreeNode, class K, class T, class KeyOfValue, class Compare>
struct __tree_const_iterator
{
private:
typedef TreeNode Node;
typedef Node* NodePtr;
typedef __tree_const_iterator<TreeNode, K, T, KeyOfValue, Compare> self;
friend class AVLTree<K, T, KeyOfValue, Compare>; //erase操作时需要用到iterator中的_node,为了不破坏封装,使用友元
friend class RBTree<K, T, KeyOfValue, Compare>;
NodePtr _node = nullptr;
NodePtr _head = nullptr;
public:
typedef T data_type;
typedef const T& reference_type;
typedef const T* pointer_type;
explicit __tree_const_iterator(NodePtr node = nullptr, NodePtr head = nullptr)
: _node(node)
, _head(head)
{}
reference_type operator*() const {
return _node->_data;
}
pointer_type operator->() const {
return &(_node->_data);
}
bool operator!=(const self& it) const {
return (_node != it._node);
}
bool operator==(const self& it) const {
return (_node == it._node);
}
self& operator++() {
if (_node == _head) //当_node位置为_pHead时,下一个位置变为最小节点
_node = _head->_pLeft;
else if (_node == _head->_pRight) //当_node为最大节点时,下一个位置为_pHead
_node = _head;
else if (_node->_pRight) //若右子树存在,则下一个位置为右子树最小节点
{
_node = _node->_pRight;
while (_node->_pLeft)
_node = _node->_pLeft;
}
else //若右子树不存在,则下一个节点为第一个非右子树的节点的双亲结点
{
NodePtr parent = _node->_pParent;
while (_node == parent->_pRight) //找到下一个节点
{
_node = parent;
parent = _node->_pParent;
}
_node = parent;
}
return *this;
}
self operator++(int) {
self it = *this;
++(*this);
return it;
}
self& operator--() {
if (_node == _head->_pLeft) //当_node为起始位置时,变为_pHead
_node = _head;
else if (_node == _head) //当_node位置为_pHead时,上一个位置为最大节点
_node = _node->_pRight;
else if (_node->_pLeft) //若左子树存在,则上一个位置为左子树中最大节点
{
_node = _node->_pLeft;
while (_node->_pRight)
_node = _node->_pRight;
}
else //若左子树不存在,则上一个位置为第一个非左子树的节点的双亲结点
{
NodePtr parent = _node->_pParent;
while (_node == parent->_pLeft)
{
_node = parent;
parent = _node->_pParent;
}
_node = parent;
}
return *this;
}
self operator--(int) {
self it = *this;
--(*this);
return it;
}
void swap(self& it) {
std::swap(_node, it._node);
std::swap(_head, it._head);
}
};
template<class TreeNode, class K, class T, class KeyOfValue, class Compare>
struct __tree_iterator : public __tree_const_iterator<TreeNode, K, T, KeyOfValue, Compare>
{ //public继承const_iterator,使得iterator可以转化为const_iterator
private:
typedef TreeNode Node;
typedef Node* NodePtr;
typedef __tree_iterator<TreeNode, K, T, KeyOfValue, Compare> self;
typedef __tree_const_iterator<TreeNode, K, T, KeyOfValue, Compare> base_class;
friend class AVLTree<K, T, KeyOfValue, Compare>;
friend class RBTree<K, T, KeyOfValue, Compare>;
public:
typedef T data_type;
typedef data_type& reference_type;
typedef data_type* pointer_type;
explicit __tree_iterator(NodePtr node = nullptr, NodePtr head = nullptr)
: base_class(node, head)
{}
reference_type operator*() const {
return const_cast<reference_type>(base_class::operator*());
}
pointer_type operator->() const {
return (&operator*());
}
self& operator++() {
base_class::operator++();
return *this;
}
self operator++(int) {
self ret = *this;
base_class::operator++();
return ret;
}
self& operator--() {
base_class::operator--();
return *this;
}
self operator--(int) {
self ret = *this;
base_class::operator--();
return ret;
}
};
insert
插入操作中需要增加emplace以及右值插入的版本,但普通的insert与右值insert和emplace的插入代码除去构造节点时的不同,其余逻辑完全相同,所以我们需要将整体的逻辑抽离出去,将构造好的节点传入即可完成复用。
红黑树私有成员(非全部,仅放出变量的以及重命名)
template<class K, class T, class KeyOfValue, class Compare = less<K>>
class RBTree
{
//类型重命名,使用重命名的话,AVL树的接口什么的基本完全一样,改造AVL树就只用直接copy就行
typedef T data_type;
typedef data_type& reference;
typedef const data_type& const_reference;
typedef data_type* pointer;
typedef const data_type* const_pointer;
typedef RBTreeNode<data_type> Node;
typedef Node* NodePtr;
typedef RBTree<K, data_type, KeyOfValue, Compare> self;
const NodePtr get_minNode()
{
NodePtr cur = _root;
while (cur && cur->_pLeft != nullptr)
cur = cur->_pLeft;
return cur;
}
const NodePtr get_maxNode()
{
NodePtr cur = _root;
while (cur && cur->_pRight != nullptr)
cur = cur->_pRight;
return cur;
}
void keep_HeadNode()
{
_pHead->_pLeft = get_minNode();
_pHead->_pRight = get_maxNode();
}
NodePtr _root; //根节点
NodePtr _pHead; //头节点
}
插入逻辑抽离
pair<NodePtr*, NodePtr> _insert_find_position(const K& key)
{
KeyOfValue kov = KeyOfValue();
Compare cmp = Compare();
NodePtr parent = nullptr;
NodePtr* ptr = &_root;
for (NodePtr cur = *ptr; cur != nullptr; cur = *ptr)
{
if (cmp(key, kov(cur->_data)))
{
parent = cur;
ptr = &cur->_pLeft;
}
else if (cmp(kov(cur->_data), key))
{
parent = cur;
ptr = &cur->_pRight;
}
else
break;
}
return make_pair(ptr, parent);
}
void _insert_keep_balance(NodePtr cur, NodePtr parent)
{
while (cur != _root && parent->_color == RBColor::RED)
{
NodePtr grandparent = parent->_pParent;
NodePtr uncle = grandparent->_pLeft == parent ? grandparent->_pRight : grandparent->_pLeft;
if (uncle == nullptr || uncle->_color == RBColor::BLACK)
{ //cur为红,parent为红,grandparent为黑,uncle不存在/存在且为黑
if (parent == grandparent->_pLeft)
{
if (cur == parent->_pRight) //当parent在grandparent的左侧而cur在parent的右侧时,先对parent进行左旋
parent = rotate_left(parent);
rotate_right(grandparent); //再对grandparent进行右旋
}
else
{
if (cur == parent->_pLeft) //当parent在grandparent的右侧而cur在parent的左侧时,先对parent进行右旋
parent = rotate_right(parent);
rotate_left(grandparent); //再对grandparent进行左旋
}
parent->_color = RBColor::BLACK;
grandparent->_color = RBColor::RED;
}
else if (uncle->_color == RBColor::RED)
{ //cur为红,parent为红,grandparent为黑,uncle存在且为红
parent->_color = uncle->_color = RBColor::BLACK;
grandparent->_color = RBColor::RED;
cur = grandparent;
parent = cur->_pParent;
}
}
_root->_color = RBColor::BLACK;
}
pair<iterator, bool> _insert(NodePtr node)
{
KeyOfValue kov = KeyOfValue();
pair<NodePtr*, NodePtr> ret = _insert_find_position(kov(node->_data));
if (*(ret.first) != nullptr)
{
delete node;
return make_pair(iterator(*(ret.first), _pHead), false);
}
else
{
node->_pParent = ret.second;
*(ret.first) = node;
}
_insert_keep_balance(node, ret.second);
keep_HeadNode();
return make_pair(iterator(node, _pHead), true);
}
插入函数
pair<iterator, bool> insert(const data_type& data)
{
return _insert(new Node(data));
}
pair<iterator, bool> insert(data_type&& data)
{
return _insert(new Node(qyc::forward<data_type>(data)));
}
template<class... Args>
pair<iterator, bool> emplace(Args&&... args)
{
return _insert(new Node(args...));
}
template<class InputIterator>
void insert_range(InputIterator first, InputIterator last)
{
while (first != last)
{
insert(*first);
++first;
}
}
相同key值元素的插入
pair<iterator, bool> _insert_equal(NodePtr node)
{
KeyOfValue kov = KeyOfValue();
pair<NodePtr*, NodePtr> ret = _insert_equal_find_position(kov(node->_data));
node->_pParent = ret.second;
*(ret.first) = node;
_insert_keep_balance(node, ret.second);
keep_HeadNode();
return make_pair(iterator(node, _pHead), true);
}
pair<NodePtr*, NodePtr> _insert_equal_find_position(const K& key)
{
KeyOfValue kov = KeyOfValue();
Compare cmp = Compare();
NodePtr parent = nullptr;
NodePtr* ptr = &_root;
for (NodePtr cur = *ptr; cur != nullptr; cur = *ptr)
{
if (cmp(key, kov(cur->_data)))
{
parent = cur;
ptr = &cur->_pLeft;
}
else //key相等放右边,不绝对(旋转后位置会改变)
{
parent = cur;
ptr = &cur->_pRight;
}
}
return make_pair(ptr, parent);
}
pair<iterator, bool> insert_equal(const data_type& data)
{
return _insert_equal(new Node(data));
}
pair<iterator, bool> insert_equal(data_type&& data)
{
return _insert_equal(new Node(qyc::forward<data_type>(data)));
}
template<class... Args>
pair<iterator, bool> emplace_equal(Args&&... args)
{
return _insert_equal(new Node(args...));
}
template<class InputIterator>
void insert_equal_range(InputIterator first, InputIterator last)
{
while (first != last)
{
insert_equal(*first);
++first;
}
}
find
find本身没有什么好讲解的,但当插入了相同key值元素时,find就变得稍微复杂了一些,看库中实现的find,当插入了相同key值时,返回的时中序遍历时第一个得到的元素。
因此可以从begin开始遍历直到找到,但如此效率便失去了
所以改进了一下,首先按照正常的搜索进行查找,找到之后,将该节点构造为iterator,--it,直到与要查找的值不同为止,再++it即得到了目标元素。(需要先仔细将iterator理解清楚才可,注意begin - 1为end,end - 1为最右侧节点)
iterator find(const K& key)
{
KeyOfValue kov = KeyOfValue();
Compare cmp = Compare();
for (NodePtr cur = _root; cur != nullptr; )
{
if (cmp(key, kov(cur->_data)))
cur = cur->_pLeft;
else if (cmp(kov(cur->_data), key))
cur = cur->_pRight;
else
{
iterator prev(cur, _pHead);
for (iterator it(cur, _pHead); it != end() && !cmp(kov(*it), key) && !cmp(key, kov(*it)); --it)
prev = it;
return prev;
}
}
return end();
}
const_iterator find(const K& key) const
{
KeyOfValue kov = KeyOfValue();
Compare cmp = Compare();
for (NodePtr cur = _root; cur != nullptr; )
{
if (cmp(key, kov(cur->_data)))
cur = cur->_pLeft;
else if (cmp(kov(cur->_data), key))
cur = cur->_pRight;
else
{
const_iterator prev(cur, _pHead);
for (const_iterator it(cur, _pHead); it != end() && !cmp(kov(*it), key) && !cmp(key, kov(*it)); --it)
prev = it;
return prev;
}
}
return end();
}
erase
erase直接接收find的值就行,删除,判断性质,保持平衡
bool erase(const K& key)
{
iterator result = find(key);
if (result != end())
{
NodePtr cur = result._node; //需要将RBTree声明为__tree_iterator的友元
NodePtr parent = cur->_pParent;
NodePtr* ptr = parent == nullptr ? &_root : (cur == parent->_pLeft ? &parent->_pLeft : &parent->_pRight);
_erase(ptr);
keep_HeadNode();
return true;
}
else
return false;
}
set & multiset
template<class K, class Compare = less<K>>
class set
{
struct SetKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
typedef RBTree<K, K, SetKeyOfValue, Compare> tree_type;
typedef set<K, Compare> self;
public:
//_tree接口复用...(使用insert)
ptivate:
tree_type _tree;
};
template<class K, class Compare = less<K>>
class multiset
{
struct SetKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
typedef RBTree<K, K, SetKeyOfValue, Compare> tree_type;
typedef multiset<K, Compare> self;
public:
//_tree接口复用...(使用insert_equal)
ptivate:
tree_type _tree;
};
map & multimap
template<class K, class Compare = less<K>>
class map
{
typedef pair<K, V> data_type;
struct MapKeyOfValue
{
const K& operator()(const data_type& kv)
{
return kv.first;
}
};
typedef RBTree<K, data_type, MapKeyOfValue, Compare> tree_type;
typedef map<K, V, Compare> self;
public:
//_tree接口复用...(使用insert)
ptivate:
tree_type _tree;
};
template<class K, class Compare = less<K>>
class multimap
{
typedef pair<K, V> data_type;
struct MapKeyOfValue
{
const K& operator()(const data_type& kv)
{
return kv.first;
}
};
typedef RBTree<K, data_type, MapKeyOfValue, Compare> tree_type;
typedef multimap<K, V, Compare> self;
public:
//_tree接口复用...(使用insert_equal)
ptivate:
tree_type _tree;
};
总结
当然也可以使用其他的方式实现,都可以,本文也不过是一个实现而已(而且也比较挫),也并非为标准的实现,理解大致的思路逻辑就好啊。