目录
map的模拟实现
map的基本框架
map的底层就是一棵红黑树,所以基本框架如下。
#pragma once
#include"RBTree.h";
template<class Key,class T>//这里的T就是Value,写成T是为了和STL标准的一样
class Map
{
public:
Map()
:_rbt()
{}
private:
RBTree<Key,T> _rbt;
};
基础版本的map的插入和删除
前面说过,map的底层就是一棵红黑树,所以其插入删除都完全按照红黑树的插入删除的逻辑走,如果忘记了红黑树的相关细节,请回顾<<红黑树(RBTree)的模拟实现>>一文。
然后要说的是,因为红黑树是一棵搜索树,所以插入删除的逻辑都需要比较节点的大小,以此找到目标节点的位置。在<<红黑树(RBTree)的模拟实现>>一文中,RBTree这个类模板只有一个模板参数T,T是Node节点中存储的有效数据_data成员的类型,所以比较节点大小时是根据T类型的operator>等等relational operators成员函数比较,而我们知道map内部封装的红黑树中的节点所存储的有效数据是一个个pair,即T是pair,pair这个类自己支持operator>等等relational operators函数,所以pair可以根据这些relational operators函数比较大小,所以map中的红黑树中的节点就有了比较大小的依据,但问题来了,pair类自带的比较大小的函数的比较规则是:先根据first比较,谁的first大,谁就大;如果first相等,再根据second比较大小。可以发现这是不符合我们的期望的,因为在STL的map里,不管是插入还是删除,比较节点的大小时只看pair成员的key,也就是只看first,如果双方的first相等,那双方就直接判定为相等了,不受second影响。
综上所述,因为不符合我们的期望,所以需要对RBTree这个类模板稍作修改。如何修改呢?
1.首先将RBTree的模板参数从一个变成两个,即从RBTree<T>变成RBTree<Key,Value>。
问题:有人说这不是多此一举吗?比如说Key是int,Value是double,那RBTree模板参数只有一个T时,给T传pair<int,double>不就行了,干嘛增加一个模板参数Value呢?
答案:如果只看map的Insert,因为插入的数据是pair<int,double>类型的对象,那只要一个模板参数T是没问题的;但map还有删除Erase,Erase的参数的类型不是pair<int,double>,而是int类型的变量,这时如果只有一个模板参数T,即pair<int,double>,是拿不到int这个类型的,所以才需要Key和Value两个模板参数。
2.然后把Insert和Erase接口中比较节点的有效数据(T _data)的地方都作修改,比如都从data1<data2变成data1.first<data2.first,这个工作并不复杂。
稍作修改后的RBTree的代码如下。
template<class Key,class Value>//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
class RBTree
{
typedef RBTreeNode<pair<Key,Value>> Node;
public:
RBTree()
:_root(nullptr)
{}
bool Erase(const Key& k)//RBTree作为map或者set的底层容器时,T一定是pair类模板实例化出的类
{
if (_root == nullptr)
{
cout << "树中已经不存在节点了,无法继续删除" << endl;
return false;
}
Node* cur = _root;
Node* curParent = cur->_parent;
while (cur != nullptr)
{
//if (x < cur->_data)
if (k < cur->_data.first)
{
curParent = cur;
cur = cur->_left;
}
//else if (x > cur->_data)
else if (k > cur->_data.first)
{
curParent = cur;
cur = cur->_right;
}
//找到了要删除的目标节点,开始删除
else
{
/*
这部分代码不需要进行修改,并且篇幅很大,所以省略了,如果有需要,请去RBTree一文中拷贝
*/
}
}
//没有在树中找到目标节点,说明目标节点不存在,直接return false
cout << "你要删除的节点不存在" << endl;
return false;
}
bool Insert(const pair<Key, Value>& x)//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
{
if (_root == nullptr)
{
_root = new Node(x, black);
return true;
}
else
{
Node* cur = _root;
Node* curParent = nullptr;
while (cur != nullptr)
{
//if (x > cur->_data)
if (x.first > cur->_data.first)
{
curParent = cur;
cur = cur->_right;
}
//else if (x < cur->_data)
else if (x.first < cur->_data.first)
{
curParent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//找到了nullptr空位置,开始插入
//if (x > curParent->_data)
if (x.first > curParent->_data.first)
{
cur = new Node(x, red);
curParent->_right = cur;
cur->_parent = curParent;
}
else
{
cur = new Node(x, red);
curParent->_left = cur;
cur->_parent = curParent;
}
//插入新节点成功,开始控制平衡
/*
这部分代码不需要进行修改,并且篇幅很大,所以省略了,如果有需要,请去RBTree一文中拷贝
*/
}
}
private:
Node* _root;
};
RBTree的代码修改完毕后,map的Insert和Erase就简单了,如下代码,只是一层极其简单的封装。(注意下面map的Insert并不是最终版本,在下文中还会做修改)
说一下,STL标准的map的Erase接口是没有返回值的,但RBTree的Erase又有返回值,我们决定这里不对RBTree的返回值作修改了,不用从bool改成void,因为没有必要,直接改map的Erase的返回值类型即可,从bool改成void,如下所示。
#pragma once
#include"RBTree.h";
template<class Key,class T>//这里的T就是value,写成T是为了和STL标准的一样
class Map
{
public:
Map()
:_rbt()
{}
bool Insert(const pair<Key, T>& p)
{
return _rbt.Insert(p);
}
void Erase(const Key& k)
{
_rbt.Erase(k);
}
private:
RBTree<Key,T> _rbt;
};
map的迭代器以及map的begin()、end()等接口
map这个容器就等价于红黑树,只不过树的节点是一个个pair,既然map等价于红黑树,所以map的迭代器就是RBTree的迭代器,直接拷贝RBTree的迭代器的代码即可,不需要作任何修改,并且因为map等价于红黑树,所以map的begin()等接口就等价于RBTree的begin()等接口,所以实现map的begin()等接口就是对RBTree的begin()等接口套一层封装。以下代码就是所有和map(即红黑树)的迭代器相关的代码。
注意为什么RBTreeIterator有两个模板参数T1、T2呢?直接一个T不行吗?答案是不行,原因在下面代码的const_iterator begin()const这个接口的注释中。
template<class T1,class T2>
struct RBTreeIterator
{
typedef RBTreeNode<T1> Node;
RBTreeIterator()
:_n(nullptr)
{}
RBTreeIterator(Node* n)
:_n(n)
{}
T2& operator*()
{
return (*_n)._data;
}
T2* operator->()
{
return &((*_n)._data);
}
bool operator==(const RBTreeIterator it)const //加const是为了让被const iterator也能调用该接口,注意区分const iterator和const_iterator,前者不是const迭代器
{
return _n == it._n;
}
bool operator!=(const RBTreeIterator it)const //加const是为了让被const iterator也能调用该接口,注意区分const iterator和const_iterator,前者不是const迭代器
{
return !(operator==(it));
}
RBTreeIterator operator++()//前置++
{
if (_n->_right == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_left)
{
_n = curParent;
return (*this);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return (*this);
}
else
{
Node* Rmin = _n->_right;
while (Rmin->_left != nullptr)
{
Rmin = Rmin->_left;
}
_n = Rmin;
return (*this);
}
}
RBTreeIterator operator++(int)//后置++
{
Node* temp = _n;
if (_n->_right == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_left)
{
_n = curParent;
return RBTreeIterator(temp);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return
_n = nullptr;
return RBTreeIterator(temp);
}
else
{
Node* Rmin = _n->_right;
while (Rmin->_left != nullptr)
{
Rmin = Rmin->_left;
}
_n = Rmin;
return RBTreeIterator(temp);
}
}
RBTreeIterator operator--()//前置--
{
if (_n->_left == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_right)
{
_n = curParent;
return (*this);//this指针的类型是RBTreeIterator<T>*, this指针的值是调用operator++接口的迭代器对象的地址
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return (*this);
}
else
{
Node* Lmax = _n->_left;
while (Lmax->_right != nullptr)
{
Lmax = Lmax->_right;
}
_n = Lmax;
return (*this);
}
}
RBTreeIterator operator--(int)//后置--
{
Node* temp = _n;
if (_n->_left == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_right)
{
_n = curParent;
return RBTreeIterator(temp);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return RBTreeIterator(temp);
}
else
{
Node* Lmax = _n->_left;
while (Lmax->_right != nullptr)
{
Lmax = Lmax->_right;
}
_n = Lmax;
return RBTreeIterator(temp);
}
}
Node* _n;
};
template<class Key,class Value>//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
class RBTree
{
typedef RBTreeNode<pair<Key,Value>> Node;
public:
typedef RBTreeIterator<pair<Key,Value>, pair<Key, Value>> iterator;
typedef RBTreeIterator<pair<Key, Value>,const pair<Key, Value>> const_iterator;
RBTree()
:_root(nullptr)
{}
iterator begin()
{
if (_root == nullptr)
return iterator(nullptr);
else
{
Node* cur = _root;
while (cur->_left != nullptr)
cur = cur->_left;
return iterator(cur);
}
}
const_iterator begin()const
{
if (_root == nullptr)
return const_iterator(nullptr);
else
{
Node* cur = _root;
while (cur->_left != nullptr)
cur = cur->_left;
return const_iterator(cur);//为什么RBTreeIterator<T1,T2>有两个模板参数T1、T2呢?
//如果RBTreeIterator<T>只有一个模板参数T,本行代码就会出问题,无法通过cur构造const_iterator。因为在当前begin函数中,cur的类型是RBTreeNode<T>*,
//而在const_iterator类中,即RBTreeIterator<const T>类中,const_iterator类的构造函数,即RBTreeIterator<const T>类的构造函数的参数的类型则是RBTreeNode<const T>*,
//因为cur的类型RBTreeNode<T>* 和const_iterator的构造函数的参数的类型RBTreeNode<const T>*不同,所以构造函数就会出现问题,所以RBTreeIterator<T>只有一个模板参数T是不行的。
}
}
iterator end()
{
return iterator(nullptr);
}
const_iterator end()const
{
return const_iterator(nullptr);
}
private:
Node* _root;
};
template<class Key,class T>//这里的T就是value,写成T是为了和STL标准的一样
class Map
{
public:
//文中说过,map就等价于红黑树,所以红黑树的迭代器就是map的迭代器
typedef RBTreeIterator<pair<Key, T>, pair<Key, T>> iterator;
typedef RBTreeIterator<pair<Key, T>, const pair<Key, T>> const_iterator;
Map()
:_rbt()
{}
iterator begin()
{
return _rbt.begin();
}
const_iterator begin()const
{
return _rbt.begin();
}
iterator end()
{
return _rbt.end();
}
const_iterator end()const
{
return _rbt.end();
}
private:
RBTree<Key,T> _rbt;
};
map的operator【】
为了实现map的Insert,在上文中我们已经对RBTree的Insert做过稍许修改,现在如果要实现map的operator【】,还要在之前的基础上,再把RBTree的Insert的返回值的类型做修改,将返回值从bool变成pair<iterator,bool>,然后把return的返回值也改一改,将bool值变成pair<iterator,bool>的对象。因为对于标准的map的operator【】来说,如果红黑树中已经存在了需要插入的目标节点,则不再继续插入,但要返回一个pair,first成员是指向目标节点的迭代器,second成员是bool值,在当前情况下为false;如果黑树中不存在需要插入的目标节点,则要完成插入,然后再返回一个pair,first成员是指向刚插入的目标节点的迭代器,second成员是bool值,在当前情况下为true。
说一下,STL标准的map的Erase接口是没有返回值的,所以RBTree的Erase我们无需进行修改,只需要改RBTree的Insert。
按照上面理论,Insert的代码被修改后,如下所示。
template<class Key,class Value>//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
class RBTree
{
typedef RBTreeNode<pair<Key,Value>> Node;
public:
typedef RBTreeIterator<pair<Key,Value>, pair<Key, Value>> iterator;
typedef RBTreeIterator<pair<Key, Value>,const pair<Key, Value>> const_iterator;
RBTree()
:_root(nullptr)
{}
pair<iterator,bool> Insert(const pair<Key, Value>& x)//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
{
if (_root == nullptr)
{
_root = new Node(x, black);
//return true;
return make_pair(iterator(_root), true);
}
else
{
Node* cur = _root;
Node* curParent = nullptr;
while (cur != nullptr)
{
//if (x > cur->_data)
if (x.first > cur->_data.first)
{
curParent = cur;
cur = cur->_right;
}
//else if (x < cur->_data)
else if (x.first < cur->_data.first)
{
curParent = cur;
cur = cur->_left;
}
else
{
//return false;
return make_pair(iterator(cur), false);
}
}
//找到了nullptr空位置,开始插入
//if (x > curParent->_data)
if (x.first > curParent->_data.first)
{
cur = new Node(x, red);
curParent->_right = cur;
cur->_parent = curParent;
}
else
{
cur = new Node(x, red);
curParent->_left = cur;
cur->_parent = curParent;
}
//插入新节点成功,开始控制平衡
Node* temp = cur;//因为下面发生变色调整或者旋转后,cur指针可能会继续往上走,继续进行调整,从而可能让cur指针不再指向新插入的节点,但在调整完毕后return返回值时,是需要返回指向新插入节点的迭代器的,所以这里设置一个temp指针记录新插入节点的地址,防止找不到新插入的节点
Node* parent = nullptr;
Node* grandParent = nullptr;
while (cur->_col == red)//如果调整到根节点来了,就不能再调整了。因为根节点的颜色必须是黑色,所以这里拿cur的颜色是红色作为继续循环的条件
{
parent = cur->_parent;//父亲节点不可能为nullptr,因此下一行代码无需判空
grandParent = parent->_parent;//注意grandParent可能为nullptr
//如果cur的爷爷节点存在,并且cur和parent是连续红色节点,此时才需要变色控制平衡;否则不需要控制平衡,此时插入新节点成功,直接就可以结束Insert函数了
if (grandParent != nullptr && (cur->_col == red && parent->_col == red))
{
//当cur与parent节点都在grandParent的左子树上时走这里
if (parent == grandParent->_left)
{
Node* uncle = grandParent->_right;
//第一种情况,此时无所谓cur与parent和grandParent是否为一条直线,但要求parent为红,uncle存在且为红
// g
// p u
// c
if (parent->_col == red && uncle != nullptr && uncle->_col == red)
{
parent->_col = black;
uncle->_col = black;
if (grandParent != _root)
grandParent->_col = red;
cur = grandParent;
}
else if ((parent->_col == red && uncle == nullptr) || (parent->_col == red && uncle != nullptr && uncle->_col == black))
{
//第二种情况,此时cur与parent和grandParent是一条直线,parent为红,uncle不存在或者存在且为黑
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp),true);
}
//第三种情况,此时cur与parent和grandParent是一条折线,parent为红,uncle不存在或者存在且为黑
// g
// p u
// c
else if (cur == parent->_right)
{
//先对以p为根节点的树进行左单旋,旋转完毕后将parent指针和cur指针的指向交换,此时节点之间就形成了和第二种情况完全一致的形状,接下来就照着第二种情况的做法去做即可控制平衡。
//先左单旋
RotateL(parent);
swap(parent, cur);//注意一定要交换两个指针的指向,否则本行下面第4行和第5行的代码(即变色的代码)会出问题,会给节点变错颜色。
//然后按照第二种情况的做法去做
RotateR(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
}
}
//当cur与parent节点都在grandParent的右子树上时走这里
else if (parent == grandParent->_right)
{
Node* uncle = grandParent->_left;
//第一种情况,此时无所谓cur与parent和grandParent是否为一条直线,但要求parent为红,uncle存在且为红
// g
// u p
// c
if (parent->_col == red && uncle != nullptr && uncle->_col == red)
{
parent->_col = black;
uncle->_col = black;
if (grandParent != _root)
grandParent->_col = red;
cur = grandParent;
}
else if ((parent->_col == red && uncle == nullptr) || (parent->_col == red && uncle != nullptr && uncle->_col == black))
{
//第二种情况,此时cur与parent和grandParent是一条直线,parent为红,uncle不存在或者存在且为黑
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
//第三种情况,此时cur与parent和grandParent是一条折线,parent为红,uncle不存在或者存在且为黑
// g
// u p
// c
else if (cur == parent->_left)
{
//先对以p为根节点的树进行右单旋,旋转完毕后将parent指针和cur指针的指向交换,此时节点之间就形成了和第二种情况完全一致的形状,接下来就照着第二种情况的做法去做即可控制平衡。
//先右单旋
RotateR(parent);
swap(parent, cur);//注意一定要交换两个指针的指向,否则本行下面第4行和第5行的代码(即变色的代码)会出问题,会给节点变错颜色。
//然后按照第二种情况的做法去做
RotateL(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
}
}
}
//如果cur的爷爷节点不存在,或者爷爷节点存在但cur和parent节点不同时为红色,此时不需要变色控制平衡,插入函数直接就可以结束了
else//(grandParent == nullptr||(grandParent != nullptr && (cur->_col == black || parent->_col == black)))
{
//return true;
return make_pair(iterator(temp), true);
}
}
//如果cur指针一直向上调整到了根节点,根节点被调整完毕后就会走出循环,出了循环后也要return true,不要忘记了
//return true;
return make_pair(iterator(temp), true);
}
}
private:
Node* _root;
};
修改完RBTree的Insert后,接下来对map的Insert也需要进行稍微的修改,只改返回值的类型即可,如下代码所示,下面的代码就是最终版本的map的插入和删除了。可以发现,map真的就等价于红黑树,只不过map这棵红黑树上挂的节点是一个个pair,除此之外再无区别,比如map的插入就是调用红黑树的插入,map的删除也就是调用红黑树的删除。
最终版本的map的插入和删除
#pragma once
#include"RBTree.h";
template<class Key,class T>//这里的T就是value,写成T是为了和STL标准的一样
class Map
{
public:
//文中说过,map就等价于红黑树,所以红黑树的迭代器就是map的迭代器
typedef RBTreeIterator<pair<Key, T>, pair<Key, T>> iterator;
typedef RBTreeIterator<pair<Key, T>, const pair<Key, T>> const_iterator;
Map()
:_rbt()
{}
pair<iterator ,bool> Insert(const pair<Key, T>& p)
{
return _rbt.Insert(p);
}
void Erase(const Key& k)
{
_rbt.Erase(k);
}
private:
RBTree<Key,T> _rbt;
};
map的最终版本的插入实现完毕后,就可以继续实现map的operator【】了,思路为:在map的operator【】函数中调用红黑树的Insert(或者map的Insert也行),插入一个pair对象A,first成员的值是用户传递的k,second成员的值等于匿名的T对象调用默认构造后的值,如果map(红黑树)中已经存在和要插入的pair对象A的first成员值相等的pair对象B了,那么不再插入A,返回一个pair对象C,first成员是指向pair对象B的迭代器,second成员是false;如果map(红黑树)中不存在和要插入的pair对象A的first成员值相等的pair对象B,则插入A,返回一个pair对象C,first成员是指向pair对象A的迭代器,second成员是true。最后根据前面是哪一种情况,最后在map的operator【】函数中,return一个通过【解引用pair对象C的first成员(即迭代器)】得到的pair对象A或者B的second成员,也就是KV模型中的Value值的引用。
根据上面的思路,代码如下。
#pragma once
#include"RBTree.h";
template<class Key,class T>//这里的T就是value,写成T是为了和STL标准的一样
class Map
{
public:
//文中说过,map就等价于红黑树,所以红黑树的迭代器就是map的迭代器
typedef RBTreeIterator<pair<Key, T>, pair<Key, T>> iterator;
typedef RBTreeIterator<pair<Key, T>, const pair<Key, T>> const_iterator;
Map()
:_rbt()
{}
T& operator[](const Key& k)
{
pair<iterator, bool> temp = _rbt.Insert(make_pair(k, T()));
return (*(temp.first)).second;
}
private:
RBTree<Key,T> _rbt;
};
map的最终测试(涵盖上面所有的接口)
测试如下图,可以发现是符合我们的预期的。
上图代码如下。
#include"Set.h";
#include"Map.h";
void test2()
{
string s[] = { "苹果","香蕉","牛奶","香蕉","橙子","苹果","菠萝"};
Map<string, int>m;
//对map的插入的检测如下
for (string& e : s)
{
m[e]++;//因为map的operator[]是对map的Insert(或者说是对RBTree的Insert)的一层封装,所以如果检测时发现operator[]没有问题,那map的Insert也肯定是没问题的
}
//对map的迭代器部分、map的begin()、end()的检测如下
for (Map<string, int>::iterator it = m.begin(); it != m.end(); it++)
{
cout << it->first << ":" << it->second<<endl;
}
cout << endl;
//对map的删除的检测如下
m.Erase("香蕉");
m.Erase("牛奶");
m.Erase("菠萝");
//支持map的迭代器后就支持范围for了
for (pair<string, int>& e : m)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
void main()
{
test2();
}
map的整体代码
RBTree.h的代码如下。
再次说明,下面RBTree.h的代码是专门提供给Map和Set的底层的,和<<红黑树(RBTree)的模拟实现>>一文中的正常的RBTree.h的代码不完全一样,是经过了稍微的修改的,至于修改了哪些内容,在上文中已经全部说过了。
/*
注意这个版本的RBTree.h是提供给Map和Set的底层的,和正常的RBTree不一样
*/
#pragma once
#include<iostream>
using namespace std;
enum color
{
red,
black
};
template<class T>
class RBTreeNode
{
public:
T _data;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
color _col;
RBTreeNode()
:_data()
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col()
{}
RBTreeNode(const T& x, color col)
:_data(x)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(col)
{}
};
template<class T1,class T2>
struct RBTreeIterator
{
typedef RBTreeNode<T1> Node;
RBTreeIterator()
:_n(nullptr)
{}
RBTreeIterator(Node* n)
:_n(n)
{}
T2& operator*()
{
return (*_n)._data;
}
T2* operator->()
{
return &((*_n)._data);
}
bool operator==(const RBTreeIterator it)const //加const是为了让被const iterator也能调用该接口,注意区分const iterator和const_iterator,前者不是const迭代器
{
return _n == it._n;
}
bool operator!=(const RBTreeIterator it)const //加const是为了让被const iterator也能调用该接口,注意区分const iterator和const_iterator,前者不是const迭代器
{
return !(operator==(it));
}
RBTreeIterator operator++()//前置++
{
if (_n->_right == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_left)
{
_n = curParent;
return (*this);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return (*this);
}
else
{
Node* Rmin = _n->_right;
while (Rmin->_left != nullptr)
{
Rmin = Rmin->_left;
}
_n = Rmin;
return (*this);
}
}
RBTreeIterator operator++(int)//后置++
{
Node* temp = _n;
if (_n->_right == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_left)
{
_n = curParent;
return RBTreeIterator(temp);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return
_n = nullptr;
return RBTreeIterator(temp);
}
else
{
Node* Rmin = _n->_right;
while (Rmin->_left != nullptr)
{
Rmin = Rmin->_left;
}
_n = Rmin;
return RBTreeIterator(temp);
}
}
RBTreeIterator operator--()//前置--
{
if (_n->_left == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_right)
{
_n = curParent;
return (*this);//this指针的类型是RBTreeIterator<T>*, this指针的值是调用operator++接口的迭代器对象的地址
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return (*this);
}
else
{
Node* Lmax = _n->_left;
while (Lmax->_right != nullptr)
{
Lmax = Lmax->_right;
}
_n = Lmax;
return (*this);
}
}
RBTreeIterator operator--(int)//后置--
{
Node* temp = _n;
if (_n->_left == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_right)
{
_n = curParent;
return RBTreeIterator(temp);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return RBTreeIterator(temp);
}
else
{
Node* Lmax = _n->_left;
while (Lmax->_right != nullptr)
{
Lmax = Lmax->_right;
}
_n = Lmax;
return RBTreeIterator(temp);
}
}
Node* _n;
};
template<class Key,class Value>//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
class RBTree
{
typedef RBTreeNode<pair<Key,Value>> Node;
public:
typedef RBTreeIterator<pair<Key,Value>, pair<Key, Value>> iterator;
typedef RBTreeIterator<pair<Key, Value>,const pair<Key, Value>> const_iterator;
RBTree()
:_root(nullptr)
{}
iterator begin()
{
if (_root == nullptr)
return iterator(nullptr);
else
{
Node* cur = _root;
while (cur->_left != nullptr)
cur = cur->_left;
return iterator(cur);
}
}
const_iterator begin()const
{
if (_root == nullptr)
return const_iterator(nullptr);
else
{
Node* cur = _root;
while (cur->_left != nullptr)
cur = cur->_left;
return const_iterator(cur);
}
}
iterator end()
{
return iterator(nullptr);
}
const_iterator end()const
{
return const_iterator(nullptr);
}
bool Erase(const Key& k)//RBTree作为map或者set的底层容器时,T一定是pair类模板实例化出的类
{
if (_root == nullptr)
{
cout << "树中已经不存在节点了,无法继续删除" << endl;
return false;
}
Node* cur = _root;
Node* curParent = cur->_parent;
while (cur != nullptr)
{
//if (x < cur->_data)
if (k < cur->_data.first)
{
curParent = cur;
cur = cur->_left;
}
//else if (x > cur->_data)
else if (k > cur->_data.first)
{
curParent = cur;
cur = cur->_right;
}
//找到了要删除的目标节点,开始删除
else
{
//第一种情况,如果目标节点没有孩子节点
if (cur->_left == nullptr && cur->_right == nullptr)
{
//对应文中的1.1 —— 如果删除的节点是红色叶子节点
if (cur->_col == red)
{
if (cur == curParent->_left)
curParent->_left = nullptr;//删除cur前,记得断开curParent与cur的连接关系
else
curParent->_right = nullptr;//删除cur前,记得断开curParent与cur的连接关系
delete cur;
return true;
}
//对应文中的1.2 —— 如果删除的节点是黑色,且没有孩子节点,则删除后需要调整平衡
else
{
//情形1:要删除的目标节点cur是根节点,所以删除后无需调整平衡
if (cur == _root)
{
delete cur;
_root = nullptr;
return true;
}
// 情形2:被删除节点不是根节点,需要进行调整平衡,注意,在向上调整时,不管cur这个黑色节点的是否存在孩子节点,我们都把cur当作没有孩子节点
int flag = 0;//用于在向上迭代调整的时候,控制不要把cur删除
while (cur != _root)
{
//情形2.2:如果cur是curParent的右孩子
if (cur == curParent->_right)
{
Node* curBrother = curParent->_left;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.2.2:如果cur的兄弟curBrother是红色节点,则说明curBrother一定有左右孩子节点,并且都是黑色
else
{
//当前情况cur是curParent的右孩子,curBrother是curParent的左孩子,所以直接对curParent右单旋
RotateR(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_left;
//走到这里,情况会转换成2.2.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.2.1的代码
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
//情形2.1:如果cur是curParent的左孩子
else
{
Node* curBrother = curParent->_right;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.1.2:如果cur的兄弟是红色节点
else
{
//当前情况cur是curParent的左孩子,所以curBrother一定是curParent的右孩子,所以直接对curParent左单旋
RotateL(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_right;
//走到这里,情况会转换成2.1.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.1.1的代码
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
}
//走到这里,即走出了用于向上调整的循环,说明是向上调整到了根节点,此时直接return true即可
return true;
}
}
//第二种情况,如果目标节点只有左孩子
else if (cur->_left != nullptr && cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
_root->_parent = nullptr;//不要忘了修改新根节点的parent指针的指向
_root->_col = black;//如果要删除的目标节点是整棵树的根节点,并且还只有左孩子,则说明左孩子一定为红,此时一定不要忘了的修改新根节点的颜色,把他从红色变成黑色
delete cur;
return true;
}
else
{
if (cur == curParent->_left)
{
curParent->_left = cur->_left;
cur->_left->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
else
{
curParent->_right = cur->_left;
cur->_left->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
cur->_left->_col = black;
delete cur;
return true;
}
}
//第三种情况,如果目标节点只有右孩子
else if (cur->_left == nullptr && cur->_right != nullptr)
{
if (cur == _root)
{
_root = cur->_right;
_root->_parent = nullptr;//不要忘了修改新根节点的parent指针的指向
_root->_col = black;//如果要删除的目标节点是整棵树的根节点,并且还只有右孩子,则说明右孩子一定为红,此时一定不要忘了的修改新根节点的颜色,把他从红色变成黑色
delete cur;
return true;
}
else
{
if (cur == curParent->_left)
{
curParent->_left = cur->_right;
cur->_right->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
else
{
curParent->_right = cur->_right;
cur->_right->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
cur->_right->_col = black;
delete cur;
return true;
}
}
//第四种情况,如果目标节点既有左孩子,又有右孩子
else if (cur->_left != nullptr && cur->_right != nullptr)
{
Node* Rmin = cur->_right;
Node* RminParent = cur;
while (Rmin->_left != nullptr)
{
RminParent = Rmin;
Rmin = Rmin->_left;
}
cur->_data = Rmin->_data;
//走到这里,替死鬼节点Rmin就被找到了,Rmin只有两种可能,第一:只有右孩子,第二:没有孩子节点
//如果只有右孩子,则套用上面的第三种情况的思路即可。
if (Rmin->_right != nullptr)
{
//注意Rmin不可能是整棵树的根节点,所以不必一板一眼的套用第三种情况的思路
if (Rmin == RminParent->_left)
{
RminParent->_left = Rmin->_right;
Rmin->_right->_parent = RminParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
else
{
RminParent->_right = Rmin->_right;
Rmin->_right->_parent = RminParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
Rmin->_right->_col = black;
delete Rmin;
return true;
}
//替死鬼节点Rmin没有孩子节点,此时直接套用第一种情况的思路即可,但注意,因为第一种情况的代码量太大了,这里我们就不套用思路了,而是直接拷贝第一种情况的代码
else
{
//先把Rmin和RminParent指针的值赋给cur与curParent,这样我们就可以直接拷贝第一种情况的代码并复用了
cur = Rmin;
curParent = RminParent;
//以下所有代码都是拷贝的第一种情况的代码
//对应文中的1.1 —— 如果删除的节点是红色叶子节点
if (cur->_col == red)
{
if (cur == curParent->_left)
curParent->_left = nullptr;//删除cur前,记得断开curParent与cur的连接关系
else
curParent->_right = nullptr;//删除cur前,记得断开curParent与cur的连接关系
delete cur;
return true;
}
//对应文中的1.2 —— 如果删除的节点是黑色,且没有孩子节点,则删除后需要调整平衡
else
{
//情形1:要删除的目标节点cur是根节点,所以删除后无需调整平衡
if (cur == _root)
{
delete cur;
_root = nullptr;
return true;
}
// 情形2:被删除节点不是根节点,需要进行调整平衡,注意,在向上调整时,不管cur这个黑色节点的是否存在孩子节点,我们都把cur当作没有孩子节点
int flag = 0;//用于在向上迭代调整的时候,控制不要把cur删除
while (cur != _root)
{
//情形2.2:如果cur是curParent的右孩子
if (cur == curParent->_right)
{
Node* curBrother = curParent->_left;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.2.2:如果cur的兄弟curBrother是红色节点,则说明curBrother一定有左右孩子节点,并且都是黑色
else
{
//当前情况cur是curParent的右孩子,curBrother是curParent的左孩子,所以直接对curParent右单旋
RotateR(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_left;
//走到这里,情况会转换成2.2.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.2.1的代码
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
//情形2.1:如果cur是curParent的左孩子
else
{
Node* curBrother = curParent->_right;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.1.2:如果cur的兄弟是红色节点
else
{
//当前情况cur是curParent的左孩子,所以curBrother一定是curParent的右孩子,所以直接对curParent左单旋
RotateL(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_right;
//走到这里,情况会转换成2.1.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.1.1的代码
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
}
//走到这里,即走出了用于向上调整的循环,说明是向上调整到了根节点,此时直接return true即可
return true;
}
}
}
}
}
//没有在树中找到目标节点,说明目标节点不存在,直接return false
cout << "你要删除的节点不存在" << endl;
return false;
}
pair<iterator,bool> Insert(const pair<Key, Value>& x)//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
{
if (_root == nullptr)
{
_root = new Node(x, black);
//return true;
return make_pair(iterator(_root), true);
}
else
{
Node* cur = _root;
Node* curParent = nullptr;
while (cur != nullptr)
{
//if (x > cur->_data)
if (x.first > cur->_data.first)
{
curParent = cur;
cur = cur->_right;
}
//else if (x < cur->_data)
else if (x.first < cur->_data.first)
{
curParent = cur;
cur = cur->_left;
}
else
{
//return false;
return make_pair(iterator(cur), false);
}
}
//找到了nullptr空位置,开始插入
//if (x > curParent->_data)
if (x.first > curParent->_data.first)
{
cur = new Node(x, red);
curParent->_right = cur;
cur->_parent = curParent;
}
else
{
cur = new Node(x, red);
curParent->_left = cur;
cur->_parent = curParent;
}
//插入新节点成功,开始控制平衡
Node* temp = cur;//因为下面发生变色调整或者旋转后,cur指针可能会继续往上走,继续进行调整,从而可能让cur指针不再指向新插入的节点,但在调整完毕后return返回值时,是需要返回指向新插入节点的迭代器的,所以这里设置一个temp指针记录新插入节点的地址,防止找不到新插入的节点
Node* parent = nullptr;
Node* grandParent = nullptr;
while (cur->_col == red)//如果调整到根节点来了,就不能再调整了。因为根节点的颜色必须是黑色,所以这里拿cur的颜色是红色作为继续循环的条件
{
parent = cur->_parent;//父亲节点不可能为nullptr,因此下一行代码无需判空
grandParent = parent->_parent;//注意grandParent可能为nullptr
//如果cur的爷爷节点存在,并且cur和parent是连续红色节点,此时才需要变色控制平衡;否则不需要控制平衡,此时插入新节点成功,直接就可以结束Insert函数了
if (grandParent != nullptr && (cur->_col == red && parent->_col == red))
{
//当cur与parent节点都在grandParent的左子树上时走这里
if (parent == grandParent->_left)
{
Node* uncle = grandParent->_right;
//第一种情况,此时无所谓cur与parent和grandParent是否为一条直线,但要求parent为红,uncle存在且为红
// g
// p u
// c
if (parent->_col == red && uncle != nullptr && uncle->_col == red)
{
parent->_col = black;
uncle->_col = black;
if (grandParent != _root)
grandParent->_col = red;
cur = grandParent;
}
else if ((parent->_col == red && uncle == nullptr) || (parent->_col == red && uncle != nullptr && uncle->_col == black))
{
//第二种情况,此时cur与parent和grandParent是一条直线,parent为红,uncle不存在或者存在且为黑
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp),true);
}
//第三种情况,此时cur与parent和grandParent是一条折线,parent为红,uncle不存在或者存在且为黑
// g
// p u
// c
else if (cur == parent->_right)
{
//先对以p为根节点的树进行左单旋,旋转完毕后将parent指针和cur指针的指向交换,此时节点之间就形成了和第二种情况完全一致的形状,接下来就照着第二种情况的做法去做即可控制平衡。
//先左单旋
RotateL(parent);
swap(parent, cur);//注意一定要交换两个指针的指向,否则本行下面第4行和第5行的代码(即变色的代码)会出问题,会给节点变错颜色。
//然后按照第二种情况的做法去做
RotateR(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
}
}
//当cur与parent节点都在grandParent的右子树上时走这里
else if (parent == grandParent->_right)
{
Node* uncle = grandParent->_left;
//第一种情况,此时无所谓cur与parent和grandParent是否为一条直线,但要求parent为红,uncle存在且为红
// g
// u p
// c
if (parent->_col == red && uncle != nullptr && uncle->_col == red)
{
parent->_col = black;
uncle->_col = black;
if (grandParent != _root)
grandParent->_col = red;
cur = grandParent;
}
else if ((parent->_col == red && uncle == nullptr) || (parent->_col == red && uncle != nullptr && uncle->_col == black))
{
//第二种情况,此时cur与parent和grandParent是一条直线,parent为红,uncle不存在或者存在且为黑
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
//第三种情况,此时cur与parent和grandParent是一条折线,parent为红,uncle不存在或者存在且为黑
// g
// u p
// c
else if (cur == parent->_left)
{
//先对以p为根节点的树进行右单旋,旋转完毕后将parent指针和cur指针的指向交换,此时节点之间就形成了和第二种情况完全一致的形状,接下来就照着第二种情况的做法去做即可控制平衡。
//先右单旋
RotateR(parent);
swap(parent, cur);//注意一定要交换两个指针的指向,否则本行下面第4行和第5行的代码(即变色的代码)会出问题,会给节点变错颜色。
//然后按照第二种情况的做法去做
RotateL(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
}
}
}
//如果cur的爷爷节点不存在,或者爷爷节点存在但cur和parent节点不同时为红色,此时不需要变色控制平衡,插入函数直接就可以结束了
else//(grandParent == nullptr||(grandParent != nullptr && (cur->_col == black || parent->_col == black)))
{
//return true;
return make_pair(iterator(temp), true);
}
}
//如果cur指针一直向上调整到了根节点,根节点被调整完毕后就会走出循环,出了循环后也要return true,不要忘记了
//return true;
return make_pair(iterator(temp), true);
}
}
private:
void RotateR(Node* p)
{
if (p != _root)
{
Node* subL = p->_left;
Node* subLR = subL->_right;
Node* p_parent = p->_parent;
subL->_right = p;
subL->_parent = p_parent;
if (p == p_parent->_right)
p_parent->_right = subL;
else
p_parent->_left = subL;
p->_left = subLR;
p->_parent = subL;
if (subLR != nullptr)
subLR->_parent = p;
}
else
{
Node* subL = p->_left;
Node* subLR = subL->_right;
_root = subL;
subL->_right = p;
subL->_parent = nullptr;
p->_left = subLR;
p->_parent = subL;
if (subLR != nullptr)
subLR->_parent = p;
}
}
void RotateL(Node* p)
{
if (_root != p)
{
Node* subR = p->_right;
Node* subRL = subR->_left;
Node* p_parent = p->_parent;
subR->_left = p;
subR->_parent = p_parent;
p->_parent = subR;
p->_right = subRL;
if (p == p_parent->_right)
p_parent->_right = subR;
else
p_parent->_left = subR;
if (subRL != nullptr)
subRL->_parent = p;
}
else
{
Node* subR = p->_right;
Node* subRL = subR->_left;
_root = subR;
subR->_left = p;
subR->_parent = nullptr;
p->_right = subRL;
p->_parent = subR;
if (subRL != nullptr)
subRL->_parent = p;
}
}
public:
bool isbalance()
{
//检验性质2是否被破坏
if (_root != nullptr && _root->_col == red)
{
cout << "根节点不是黑色" << endl;
return false;
}
//检验性质3和4是否被破坏,这俩性质都通过子函数_isbalance检验
else
{
int benchmark = 0;
Node* cur = _root;
while (cur != nullptr)
{
if (cur->_col == black)
{
benchmark++;
}
cur = cur->_left;
}
return _isbalance(_root, 0, benchmark);
}
}
private:
bool _isbalance(Node* root, int blackNum, int benchmark)
{
//检测性质3
if (root == nullptr)
{
if (blackNum != benchmark)
{
cout << "某条路径上的黑色节点的数量和基准值benchmark不相等,性质3被破坏了" << endl;
return false;
}
else
return true;
}
if (root->_col == black)
blackNum++;
//检测性质4
if (root->_col == red && root->_parent->_col == red)//这里root->_parent不可能为nullptr,因为root的颜色是红色,所以root不可能为整棵树的根节点,所以root->_parent不可能为nullptr。
{
cout << "存在连续红色节点,性质4被破坏" << endl;
return false;
}
bool x = _isbalance(root->_left, blackNum, benchmark);
bool y = _isbalance(root->_right, blackNum, benchmark);
return x && y;
}
public:
void Inorder()
{
_Inorder(_root);
}
private:
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_data << ' ';
_Inorder(root->_right);
}
private:
Node* _root;
};
map.h的代码如下。
#pragma once
#include"RBTree.h";
template<class Key,class T>//这里的T就是value,写成T是为了和STL标准的一样
class Map
{
public:
//文中说过,map就等价于红黑树,所以红黑树的迭代器就是map的迭代器
typedef RBTreeIterator<pair<Key, T>, pair<Key, T>> iterator;
typedef RBTreeIterator<pair<Key, T>, const pair<Key, T>> const_iterator;
Map()
:_rbt()
{}
iterator begin()
{
return _rbt.begin();
}
const_iterator begin()const
{
return _rbt.begin();
}
iterator end()
{
return _rbt.end();
}
const_iterator end()const
{
return _rbt.end();
}
pair<iterator ,bool> Insert(const pair<Key, T>& p)
{
return _rbt.Insert(p);
}
void Erase(const Key& k)
{
_rbt.Erase(k);
}
T& operator[](const Key& k)
{
pair<iterator, bool> temp = _rbt.Insert(make_pair(k, T()));
return (*(temp.first)).second;
}
private:
RBTree<Key,T> _rbt;
};
test.cpp的代码如下。
#include"Set.h";
#include"Map.h";
void test1()
{
RBTree<int, int>r;
r.Insert(make_pair(1, 1));
r.Insert(make_pair(4, 4));
r.Insert(make_pair(2, 2));
r.Insert(make_pair(3, 3));
const RBTree<int, int>r1(r);
for (RBTree<int, int>::const_iterator it = r1.begin(); it != r1.end(); it++)
{
cout << (*it).first << ' ';
}
cout << endl;
}
void test2()
{
string s[] = { "苹果","香蕉","牛奶","香蕉","橙子","苹果","菠萝"};
Map<string, int>m;
//对map的插入的检测如下
for (string& e : s)
{
m[e]++;//因为map的operator[]是对map的Insert(或者说是对RBTree的Insert)的一层封装,所以如果检测时发现operator[]没有问题,那map的Insert也肯定是没问题的
}
//对map的迭代器部分、map的begin()、end()的检测如下
for (Map<string, int>::iterator it = m.begin(); it != m.end(); it++)
{
cout << it->first << ":" << it->second<<endl;
}
cout << endl;
//对map的删除的检测如下
m.Erase("香蕉");
m.Erase("牛奶");
m.Erase("菠萝");
//支持map的迭代器后就支持范围for了
for (pair<string, int>& e : m)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
void main()
{
test2();
}
set的模拟实现
对于set的模拟实现,它的很多细节和map是完全相同的,比如set和map一样,他俩都等价于红黑树,只不过树上挂的节点是一个个pair。既然set也等价于红黑树,所以set的迭代器就是RBTree的迭代器,直接拷贝RBTree的迭代器的代码即可,不需要作任何修改,并且因为set等价于红黑树,所以set的begin()等接口就等价于RBTree的begin()等接口,所以实现set的begin()等接口就是对RBTree的begin()等接口套一层封装。
set和map在模拟实现时的区别非常少,只有1:set的Insert需要的参数不是pair(但注意最后通过红黑树的Insert时,插入的节点的有效数据的类型还是pair),2:set不像map,set没有operator[]这个成员函数。
set剩下的细节几乎完全和map一致,这里就不再赘述,而是直接上整体的代码。
set的整体代码
RBTree.h的代码如下。
再次说明,下面RBTree.h的代码是专门提供给Map和Set的底层的,和<<红黑树(RBTree)的模拟实现>>一文中的正常的RBTree.h的代码不完全一样,是经过了稍微的修改的,至于修改了哪些内容,在上文中已经全部说过了。
/*
注意这个版本的RBTree.h是提供给Map和Set的底层的,和正常的RBTree不一样
*/
#pragma once
#include<iostream>
using namespace std;
enum color
{
red,
black
};
template<class T>
class RBTreeNode
{
public:
T _data;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
color _col;
RBTreeNode()
:_data()
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col()
{}
RBTreeNode(const T& x, color col)
:_data(x)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(col)
{}
};
template<class T1,class T2>
struct RBTreeIterator
{
typedef RBTreeNode<T1> Node;
RBTreeIterator()
:_n(nullptr)
{}
RBTreeIterator(Node* n)
:_n(n)
{}
T2& operator*()
{
return (*_n)._data;
}
T2* operator->()
{
return &((*_n)._data);
}
bool operator==(const RBTreeIterator it)const //加const是为了让被const iterator也能调用该接口,注意区分const iterator和const_iterator,前者不是const迭代器
{
return _n == it._n;
}
bool operator!=(const RBTreeIterator it)const //加const是为了让被const iterator也能调用该接口,注意区分const iterator和const_iterator,前者不是const迭代器
{
return !(operator==(it));
}
RBTreeIterator operator++()//前置++
{
if (_n->_right == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_left)
{
_n = curParent;
return (*this);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return (*this);
}
else
{
Node* Rmin = _n->_right;
while (Rmin->_left != nullptr)
{
Rmin = Rmin->_left;
}
_n = Rmin;
return (*this);
}
}
RBTreeIterator operator++(int)//后置++
{
Node* temp = _n;
if (_n->_right == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_left)
{
_n = curParent;
return RBTreeIterator(temp);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return
_n = nullptr;
return RBTreeIterator(temp);
}
else
{
Node* Rmin = _n->_right;
while (Rmin->_left != nullptr)
{
Rmin = Rmin->_left;
}
_n = Rmin;
return RBTreeIterator(temp);
}
}
RBTreeIterator operator--()//前置--
{
if (_n->_left == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_right)
{
_n = curParent;
return (*this);//this指针的类型是RBTreeIterator<T>*, this指针的值是调用operator++接口的迭代器对象的地址
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return (*this);
}
else
{
Node* Lmax = _n->_left;
while (Lmax->_right != nullptr)
{
Lmax = Lmax->_right;
}
_n = Lmax;
return (*this);
}
}
RBTreeIterator operator--(int)//后置--
{
Node* temp = _n;
if (_n->_left == nullptr)
{
Node* cur = _n;
Node* curParent = _n->_parent;
while (curParent != nullptr)//这里本来是cur!=_root的,但_root在RBTree类中,访问不到,所以用了curParent!=nullptr替代
{
if (cur == curParent->_right)
{
_n = curParent;
return RBTreeIterator(temp);
}
else
{
cur = curParent;
curParent = curParent->_parent;
}
}
//如果出了while循环,说明cur走到了根节点,此时说明整棵树就已经遍历完毕了,直接return 一个【和end()函数返回的迭代器指向相同的】迭代器
_n = nullptr;
return RBTreeIterator(temp);
}
else
{
Node* Lmax = _n->_left;
while (Lmax->_right != nullptr)
{
Lmax = Lmax->_right;
}
_n = Lmax;
return RBTreeIterator(temp);
}
}
Node* _n;
};
template<class Key,class Value>//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
class RBTree
{
typedef RBTreeNode<pair<Key,Value>> Node;
public:
typedef RBTreeIterator<pair<Key,Value>, pair<Key, Value>> iterator;
typedef RBTreeIterator<pair<Key, Value>,const pair<Key, Value>> const_iterator;
RBTree()
:_root(nullptr)
{}
iterator begin()
{
if (_root == nullptr)
return iterator(nullptr);
else
{
Node* cur = _root;
while (cur->_left != nullptr)
cur = cur->_left;
return iterator(cur);
}
}
const_iterator begin()const
{
if (_root == nullptr)
return const_iterator(nullptr);
else
{
Node* cur = _root;
while (cur->_left != nullptr)
cur = cur->_left;
return const_iterator(cur);
}
}
iterator end()
{
return iterator(nullptr);
}
const_iterator end()const
{
return const_iterator(nullptr);
}
bool Erase(const Key& k)//RBTree作为map或者set的底层容器时,T一定是pair类模板实例化出的类
{
if (_root == nullptr)
{
cout << "树中已经不存在节点了,无法继续删除" << endl;
return false;
}
Node* cur = _root;
Node* curParent = cur->_parent;
while (cur != nullptr)
{
//if (x < cur->_data)
if (k < cur->_data.first)
{
curParent = cur;
cur = cur->_left;
}
//else if (x > cur->_data)
else if (k > cur->_data.first)
{
curParent = cur;
cur = cur->_right;
}
//找到了要删除的目标节点,开始删除
else
{
//第一种情况,如果目标节点没有孩子节点
if (cur->_left == nullptr && cur->_right == nullptr)
{
//对应文中的1.1 —— 如果删除的节点是红色叶子节点
if (cur->_col == red)
{
if (cur == curParent->_left)
curParent->_left = nullptr;//删除cur前,记得断开curParent与cur的连接关系
else
curParent->_right = nullptr;//删除cur前,记得断开curParent与cur的连接关系
delete cur;
return true;
}
//对应文中的1.2 —— 如果删除的节点是黑色,且没有孩子节点,则删除后需要调整平衡
else
{
//情形1:要删除的目标节点cur是根节点,所以删除后无需调整平衡
if (cur == _root)
{
delete cur;
_root = nullptr;
return true;
}
// 情形2:被删除节点不是根节点,需要进行调整平衡,注意,在向上调整时,不管cur这个黑色节点的是否存在孩子节点,我们都把cur当作没有孩子节点
int flag = 0;//用于在向上迭代调整的时候,控制不要把cur删除
while (cur != _root)
{
//情形2.2:如果cur是curParent的右孩子
if (cur == curParent->_right)
{
Node* curBrother = curParent->_left;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.2.2:如果cur的兄弟curBrother是红色节点,则说明curBrother一定有左右孩子节点,并且都是黑色
else
{
//当前情况cur是curParent的右孩子,curBrother是curParent的左孩子,所以直接对curParent右单旋
RotateR(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_left;
//走到这里,情况会转换成2.2.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.2.1的代码
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
//情形2.1:如果cur是curParent的左孩子
else
{
Node* curBrother = curParent->_right;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.1.2:如果cur的兄弟是红色节点
else
{
//当前情况cur是curParent的左孩子,所以curBrother一定是curParent的右孩子,所以直接对curParent左单旋
RotateL(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_right;
//走到这里,情况会转换成2.1.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.1.1的代码
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
}
//走到这里,即走出了用于向上调整的循环,说明是向上调整到了根节点,此时直接return true即可
return true;
}
}
//第二种情况,如果目标节点只有左孩子
else if (cur->_left != nullptr && cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
_root->_parent = nullptr;//不要忘了修改新根节点的parent指针的指向
_root->_col = black;//如果要删除的目标节点是整棵树的根节点,并且还只有左孩子,则说明左孩子一定为红,此时一定不要忘了的修改新根节点的颜色,把他从红色变成黑色
delete cur;
return true;
}
else
{
if (cur == curParent->_left)
{
curParent->_left = cur->_left;
cur->_left->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
else
{
curParent->_right = cur->_left;
cur->_left->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
cur->_left->_col = black;
delete cur;
return true;
}
}
//第三种情况,如果目标节点只有右孩子
else if (cur->_left == nullptr && cur->_right != nullptr)
{
if (cur == _root)
{
_root = cur->_right;
_root->_parent = nullptr;//不要忘了修改新根节点的parent指针的指向
_root->_col = black;//如果要删除的目标节点是整棵树的根节点,并且还只有右孩子,则说明右孩子一定为红,此时一定不要忘了的修改新根节点的颜色,把他从红色变成黑色
delete cur;
return true;
}
else
{
if (cur == curParent->_left)
{
curParent->_left = cur->_right;
cur->_right->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
else
{
curParent->_right = cur->_right;
cur->_right->_parent = curParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
cur->_right->_col = black;
delete cur;
return true;
}
}
//第四种情况,如果目标节点既有左孩子,又有右孩子
else if (cur->_left != nullptr && cur->_right != nullptr)
{
Node* Rmin = cur->_right;
Node* RminParent = cur;
while (Rmin->_left != nullptr)
{
RminParent = Rmin;
Rmin = Rmin->_left;
}
cur->_data = Rmin->_data;
//走到这里,替死鬼节点Rmin就被找到了,Rmin只有两种可能,第一:只有右孩子,第二:没有孩子节点
//如果只有右孩子,则套用上面的第三种情况的思路即可。
if (Rmin->_right != nullptr)
{
//注意Rmin不可能是整棵树的根节点,所以不必一板一眼的套用第三种情况的思路
if (Rmin == RminParent->_left)
{
RminParent->_left = Rmin->_right;
Rmin->_right->_parent = RminParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
else
{
RminParent->_right = Rmin->_right;
Rmin->_right->_parent = RminParent;//别忘了更新每个节点的_parent指针,这点坑死我了,调试死我了
}
Rmin->_right->_col = black;
delete Rmin;
return true;
}
//替死鬼节点Rmin没有孩子节点,此时直接套用第一种情况的思路即可,但注意,因为第一种情况的代码量太大了,这里我们就不套用思路了,而是直接拷贝第一种情况的代码
else
{
//先把Rmin和RminParent指针的值赋给cur与curParent,这样我们就可以直接拷贝第一种情况的代码并复用了
cur = Rmin;
curParent = RminParent;
//以下所有代码都是拷贝的第一种情况的代码
//对应文中的1.1 —— 如果删除的节点是红色叶子节点
if (cur->_col == red)
{
if (cur == curParent->_left)
curParent->_left = nullptr;//删除cur前,记得断开curParent与cur的连接关系
else
curParent->_right = nullptr;//删除cur前,记得断开curParent与cur的连接关系
delete cur;
return true;
}
//对应文中的1.2 —— 如果删除的节点是黑色,且没有孩子节点,则删除后需要调整平衡
else
{
//情形1:要删除的目标节点cur是根节点,所以删除后无需调整平衡
if (cur == _root)
{
delete cur;
_root = nullptr;
return true;
}
// 情形2:被删除节点不是根节点,需要进行调整平衡,注意,在向上调整时,不管cur这个黑色节点的是否存在孩子节点,我们都把cur当作没有孩子节点
int flag = 0;//用于在向上迭代调整的时候,控制不要把cur删除
while (cur != _root)
{
//情形2.2:如果cur是curParent的右孩子
if (cur == curParent->_right)
{
Node* curBrother = curParent->_left;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.2.2:如果cur的兄弟curBrother是红色节点,则说明curBrother一定有左右孩子节点,并且都是黑色
else
{
//当前情况cur是curParent的右孩子,curBrother是curParent的左孩子,所以直接对curParent右单旋
RotateR(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_left;
//走到这里,情况会转换成2.2.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.2.1的代码
//情形2.2.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.2.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//情形2.2.1.3:说明一下,如果兄弟有两个红色子节点,也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curParent节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewL->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
else if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curBrother);
RotateR(curParent);
curNephewR->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.2.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
return true;
}
//情形2.2.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_right = nullptr;//cur是curParent的右孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur不需要被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把他删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
//情形2.1:如果cur是curParent的左孩子
else
{
Node* curBrother = curParent->_right;//无需判空,因为cur不是根节点,所以curParent一定不为空
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
//情形2.1.2:如果cur的兄弟是红色节点
else
{
//当前情况cur是curParent的左孩子,所以curBrother一定是curParent的右孩子,所以直接对curParent左单旋
RotateL(curParent);
swap(curBrother->_col, curParent->_col);
//旋转完毕后,关系可能会乱,所以更新curBrother与curParent指针的指向,这里我们根据示例图可以知道只有curBrother指针需要更新
curBrother = curParent->_right;
//走到这里,情况会转换成2.1.1:cur和curBrother都是黑色节点,此时直接拷贝上面2.1.1的代码
//情形2.1.1:如果cur的兄弟是黑色节点
if (curBrother->_col == black)
{
//情形2.1.1.2:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色右子节点。
//情形2.1.1.3:如果兄弟有两个红色子节点,我们也走这里,因为这个分支不用双旋,更好
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为黑,右孩子为红。因为文中说过我们要把它当作左孩子为空,右孩子为红,所以才需要走这个if分支
if (curBrother->_right != nullptr && curBrother->_right->_col == red)
{
/*
下面的curNephewR表示cur的侄子节点,有个R说明是curBrother的右孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewR节点的地址,
方便在对curParent节点旋转后还能找到curBrother的右孩子节点,为什么找它呢?需要对curBrother的右孩子节点变色调整。
*/
Node* curNephewR = curBrother->_right;
RotateL(curParent);
curBrother->_col = curParent->_col;
curParent->_col = black;
curNephewR->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.1:如果cur的兄弟有子节点,则只有可能为红色,当前情况就是兄弟只有红色左子节点。
//说明一下,还有一种只存在于向上调整时的特殊情况也要走这里,就是curBrother的左孩子为红,右孩子为黑。因为文中说过我们要把它当作左孩子为红,右孩子为空,所以才需要走这个if分支
else if (curBrother->_left != nullptr && curBrother->_left->_col == red)
{
/*
下面的curNephewL表示cur的侄子节点,有个L说明是curBrother的左孩子;为什么要设置该指针呢?因为这里有一
个巨坑,因为对某个节点旋转,可能会修改该节点的结构,比如导致儿子节点不再是儿子节点,关系会乱,所以如果
在发生旋转后还通过该节点来找它的孩子或者父亲节点,就不一定能找到,所以这里提前记录了curNephewL节点的地址,
方便在对curBrother节点旋转后还能找到curBrother的左孩子节点,为什么找它呢?需要对curBrother的左孩子节点变色调整。
*/
Node* curNephewL = curBrother->_left;
RotateR(curBrother);
RotateL(curParent);
curNephewL->_col = curParent->_col;
curParent->_col = black;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4:如果cur的兄弟没有子节点走这里
//说明一下,还有一种只存在于向上调整时的特殊情况也走这里,就是cur的兄弟存在两个子节点,但子节点必须全是黑色节点,不能有红色存在
else if ((curBrother->_left == nullptr && curBrother->_right == nullptr)
|| ((curBrother->_left != nullptr && curBrother->_left->_col == black) && (curBrother->_right != nullptr && curBrother->_right->_col == black)))
{
//情形2.1.1.4.1:如果父亲节点是红色
if (curParent->_col == red)
{
curParent->_col = black;
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
return true;
}
//情形2.1.1.4.2:如果父亲节点是黑色,则需要向上迭代调整
else
{
curBrother->_col = red;
if (flag == 0)
{
delete cur;
curParent->_left = nullptr;//cur是curParent的左孩子,删除cur后,记得把链接关系断开,写在if语句里就是为了防止向上调整的时候出现cur没有被删除,但连接被断开的情况
}
flag = 1;//用于在向上迭代调整的时候,控制不要把cur删除,这里设置成1后,说明已经删除了目标节点,后序cur指向的节点只是在调整颜色,不需要真的把cur删除
cur = curParent;
curParent = curParent->_parent;//别忘了把curParent也向上调整,这个坑调试死我了,我屮
}
}
}
}
}
}
//走到这里,即走出了用于向上调整的循环,说明是向上调整到了根节点,此时直接return true即可
return true;
}
}
}
}
}
//没有在树中找到目标节点,说明目标节点不存在,直接return false
cout << "你要删除的节点不存在" << endl;
return false;
}
pair<iterator,bool> Insert(const pair<Key, Value>& x)//RBTree作为map或者set的底层容器时,所存储的元素(节点)一定是pair
{
if (_root == nullptr)
{
_root = new Node(x, black);
//return true;
return make_pair(iterator(_root), true);
}
else
{
Node* cur = _root;
Node* curParent = nullptr;
while (cur != nullptr)
{
//if (x > cur->_data)
if (x.first > cur->_data.first)
{
curParent = cur;
cur = cur->_right;
}
//else if (x < cur->_data)
else if (x.first < cur->_data.first)
{
curParent = cur;
cur = cur->_left;
}
else
{
//return false;
return make_pair(iterator(cur), false);
}
}
//找到了nullptr空位置,开始插入
//if (x > curParent->_data)
if (x.first > curParent->_data.first)
{
cur = new Node(x, red);
curParent->_right = cur;
cur->_parent = curParent;
}
else
{
cur = new Node(x, red);
curParent->_left = cur;
cur->_parent = curParent;
}
//插入新节点成功,开始控制平衡
Node* temp = cur;//因为下面发生变色调整或者旋转后,cur指针可能会继续往上走,继续进行调整,从而可能让cur指针不再指向新插入的节点,但在调整完毕后return返回值时,是需要返回指向新插入节点的迭代器的,所以这里设置一个temp指针记录新插入节点的地址,防止找不到新插入的节点
Node* parent = nullptr;
Node* grandParent = nullptr;
while (cur->_col == red)//如果调整到根节点来了,就不能再调整了。因为根节点的颜色必须是黑色,所以这里拿cur的颜色是红色作为继续循环的条件
{
parent = cur->_parent;//父亲节点不可能为nullptr,因此下一行代码无需判空
grandParent = parent->_parent;//注意grandParent可能为nullptr
//如果cur的爷爷节点存在,并且cur和parent是连续红色节点,此时才需要变色控制平衡;否则不需要控制平衡,此时插入新节点成功,直接就可以结束Insert函数了
if (grandParent != nullptr && (cur->_col == red && parent->_col == red))
{
//当cur与parent节点都在grandParent的左子树上时走这里
if (parent == grandParent->_left)
{
Node* uncle = grandParent->_right;
//第一种情况,此时无所谓cur与parent和grandParent是否为一条直线,但要求parent为红,uncle存在且为红
// g
// p u
// c
if (parent->_col == red && uncle != nullptr && uncle->_col == red)
{
parent->_col = black;
uncle->_col = black;
if (grandParent != _root)
grandParent->_col = red;
cur = grandParent;
}
else if ((parent->_col == red && uncle == nullptr) || (parent->_col == red && uncle != nullptr && uncle->_col == black))
{
//第二种情况,此时cur与parent和grandParent是一条直线,parent为红,uncle不存在或者存在且为黑
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp),true);
}
//第三种情况,此时cur与parent和grandParent是一条折线,parent为红,uncle不存在或者存在且为黑
// g
// p u
// c
else if (cur == parent->_right)
{
//先对以p为根节点的树进行左单旋,旋转完毕后将parent指针和cur指针的指向交换,此时节点之间就形成了和第二种情况完全一致的形状,接下来就照着第二种情况的做法去做即可控制平衡。
//先左单旋
RotateL(parent);
swap(parent, cur);//注意一定要交换两个指针的指向,否则本行下面第4行和第5行的代码(即变色的代码)会出问题,会给节点变错颜色。
//然后按照第二种情况的做法去做
RotateR(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
}
}
//当cur与parent节点都在grandParent的右子树上时走这里
else if (parent == grandParent->_right)
{
Node* uncle = grandParent->_left;
//第一种情况,此时无所谓cur与parent和grandParent是否为一条直线,但要求parent为红,uncle存在且为红
// g
// u p
// c
if (parent->_col == red && uncle != nullptr && uncle->_col == red)
{
parent->_col = black;
uncle->_col = black;
if (grandParent != _root)
grandParent->_col = red;
cur = grandParent;
}
else if ((parent->_col == red && uncle == nullptr) || (parent->_col == red && uncle != nullptr && uncle->_col == black))
{
//第二种情况,此时cur与parent和grandParent是一条直线,parent为红,uncle不存在或者存在且为黑
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
//第三种情况,此时cur与parent和grandParent是一条折线,parent为红,uncle不存在或者存在且为黑
// g
// u p
// c
else if (cur == parent->_left)
{
//先对以p为根节点的树进行右单旋,旋转完毕后将parent指针和cur指针的指向交换,此时节点之间就形成了和第二种情况完全一致的形状,接下来就照着第二种情况的做法去做即可控制平衡。
//先右单旋
RotateR(parent);
swap(parent, cur);//注意一定要交换两个指针的指向,否则本行下面第4行和第5行的代码(即变色的代码)会出问题,会给节点变错颜色。
//然后按照第二种情况的做法去做
RotateL(grandParent);
parent->_col = black;
grandParent->_col = red;
//return true;
return make_pair(iterator(temp), true);
}
}
}
}
//如果cur的爷爷节点不存在,或者爷爷节点存在但cur和parent节点不同时为红色,此时不需要变色控制平衡,插入函数直接就可以结束了
else//(grandParent == nullptr||(grandParent != nullptr && (cur->_col == black || parent->_col == black)))
{
//return true;
return make_pair(iterator(temp), true);
}
}
//如果cur指针一直向上调整到了根节点,根节点被调整完毕后就会走出循环,出了循环后也要return true,不要忘记了
//return true;
return make_pair(iterator(temp), true);
}
}
private:
void RotateR(Node* p)
{
if (p != _root)
{
Node* subL = p->_left;
Node* subLR = subL->_right;
Node* p_parent = p->_parent;
subL->_right = p;
subL->_parent = p_parent;
if (p == p_parent->_right)
p_parent->_right = subL;
else
p_parent->_left = subL;
p->_left = subLR;
p->_parent = subL;
if (subLR != nullptr)
subLR->_parent = p;
}
else
{
Node* subL = p->_left;
Node* subLR = subL->_right;
_root = subL;
subL->_right = p;
subL->_parent = nullptr;
p->_left = subLR;
p->_parent = subL;
if (subLR != nullptr)
subLR->_parent = p;
}
}
void RotateL(Node* p)
{
if (_root != p)
{
Node* subR = p->_right;
Node* subRL = subR->_left;
Node* p_parent = p->_parent;
subR->_left = p;
subR->_parent = p_parent;
p->_parent = subR;
p->_right = subRL;
if (p == p_parent->_right)
p_parent->_right = subR;
else
p_parent->_left = subR;
if (subRL != nullptr)
subRL->_parent = p;
}
else
{
Node* subR = p->_right;
Node* subRL = subR->_left;
_root = subR;
subR->_left = p;
subR->_parent = nullptr;
p->_right = subRL;
p->_parent = subR;
if (subRL != nullptr)
subRL->_parent = p;
}
}
public:
bool isbalance()
{
//检验性质2是否被破坏
if (_root != nullptr && _root->_col == red)
{
cout << "根节点不是黑色" << endl;
return false;
}
//检验性质3和4是否被破坏,这俩性质都通过子函数_isbalance检验
else
{
int benchmark = 0;
Node* cur = _root;
while (cur != nullptr)
{
if (cur->_col == black)
{
benchmark++;
}
cur = cur->_left;
}
return _isbalance(_root, 0, benchmark);
}
}
private:
bool _isbalance(Node* root, int blackNum, int benchmark)
{
//检测性质3
if (root == nullptr)
{
if (blackNum != benchmark)
{
cout << "某条路径上的黑色节点的数量和基准值benchmark不相等,性质3被破坏了" << endl;
return false;
}
else
return true;
}
if (root->_col == black)
blackNum++;
//检测性质4
if (root->_col == red && root->_parent->_col == red)//这里root->_parent不可能为nullptr,因为root的颜色是红色,所以root不可能为整棵树的根节点,所以root->_parent不可能为nullptr。
{
cout << "存在连续红色节点,性质4被破坏" << endl;
return false;
}
bool x = _isbalance(root->_left, blackNum, benchmark);
bool y = _isbalance(root->_right, blackNum, benchmark);
return x && y;
}
public:
void Inorder()
{
_Inorder(_root);
}
private:
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_data << ' ';
_Inorder(root->_right);
}
private:
Node* _root;
};
set.h的代码如下。
这里顺便回顾几个知识点,因为map的Insert和Erase接口都不是const成员函数,所以如果有const set<T> s,s对象是无法调用Insert和Erase函数的,s对象只能调用begin()和end()。然后再顺便一说,非const的类对象也是能够调用const成员函数的,但下面因为存在非const版本的begin()和end()接口,构成函数重载,所以这里非const的set对象调用begin()和end()时,只会调用非const版本的begin()和end()。
#include"RBTree.h";
template<class T>
class Set
{
public:
typedef RBTreeIterator<pair<T, T>, pair<T, T>> iterator;
typedef RBTreeIterator<pair<T, T>, const pair<T, T>> const_iterator;
Set()
:_rbt()
{}
pair<iterator, bool> Insert(const T& x)
{
return _rbt.Insert(pair<T, T>(x, x));
}
void Erase(const T& x)
{
_rbt.Erase(x);
}
iterator begin()
{
return _rbt.begin();
}
const_iterator begin()const
{
return _rbt.begin();
}
iterator end()
{
return _rbt.end();
}
const_iterator end()const
{
return _rbt.end();
}
private:
RBTree<T, T>_rbt;
};
test.cpp的代码如下。
#include"Set.h";
#include"Map.h";
void test1()
{
RBTree<int, int>r;
r.Insert(make_pair(1, 1));
r.Insert(make_pair(4, 4));
r.Insert(make_pair(2, 2));
r.Insert(make_pair(3, 3));
const RBTree<int, int>r1(r);
for (RBTree<int, int>::const_iterator it = r1.begin(); it != r1.end(); it++)
{
cout << (*it).first << ' ';
}
cout << endl;
}
void test2()
{
string s[] = { "苹果","香蕉","牛奶","香蕉","橙子","苹果","菠萝"};
Map<string, int>m;
//对map的插入的检测如下
for (string& e : s)
{
m[e]++;//因为map的operator[]是对map的Insert(或者说是对RBTree的Insert)的一层封装,所以如果检测时发现operator[]没有问题,那map的Insert也肯定是没问题的
}
//对map的迭代器部分、map的begin()、end()的检测如下
for (Map<string, int>::iterator it = m.begin(); it != m.end(); it++)
{
cout << it->first << ":" << it->second<<endl;
}
cout << endl;
//对map的删除的检测如下
m.Erase("香蕉");
m.Erase("牛奶");
m.Erase("菠萝");
//支持map的迭代器后就支持范围for了
for (pair<string, int>& e : m)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
void test3()
{
Set<string>s;
//对set的插入的检测如下
s.Insert("香蕉");
s.Insert("牛奶");
s.Insert("菠萝");
s.Insert("苹果");
s.Insert("草莓");
//对set的迭代器类RBTreeIterator的operator系列等接口的检测如下
//对set的begin()等接口的检测如下
for (Set<string>::iterator it = s.begin(); it != s.end(); it++)
{
cout << it->first << ' ';
}
cout << endl;
//对set的删除的检测如下
s.Erase("牛奶");
s.Erase("菠萝");
s.Erase("香蕉");
for (Set<string>::iterator it = s.begin(); it != s.end(); it++)
{
cout << it->first << ' ';
}
cout << endl;
//set的迭代器实现后,范围for也就被支持了
for (pair<string, string>& p : s)
{
cout << p.first << ' ';
}
cout << endl;
}
void main()
{
test3();
}
set的最终测试(涵盖上面所有的接口)
测试如下图,可以发现是符合我们的预期的。
上图代码如下。
#include"Set.h";
#include"Map.h";
void test1()
{
RBTree<int, int>r;
r.Insert(make_pair(1, 1));
r.Insert(make_pair(4, 4));
r.Insert(make_pair(2, 2));
r.Insert(make_pair(3, 3));
const RBTree<int, int>r1(r);
for (RBTree<int, int>::const_iterator it = r1.begin(); it != r1.end(); it++)
{
cout << (*it).first << ' ';
}
cout << endl;
}
void test2()
{
string s[] = { "苹果","香蕉","牛奶","香蕉","橙子","苹果","菠萝"};
Map<string, int>m;
//对map的插入的检测如下
for (string& e : s)
{
m[e]++;//因为map的operator[]是对map的Insert(或者说是对RBTree的Insert)的一层封装,所以如果检测时发现operator[]没有问题,那map的Insert也肯定是没问题的
}
//对map的迭代器部分、map的begin()、end()的检测如下
for (Map<string, int>::iterator it = m.begin(); it != m.end(); it++)
{
cout << it->first << ":" << it->second<<endl;
}
cout << endl;
//对map的删除的检测如下
m.Erase("香蕉");
m.Erase("牛奶");
m.Erase("菠萝");
//支持map的迭代器后就支持范围for了
for (pair<string, int>& e : m)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
void test3()
{
Set<string>s;
//对set的插入的检测如下
s.Insert("香蕉");
s.Insert("牛奶");
s.Insert("菠萝");
s.Insert("苹果");
s.Insert("草莓");
//对set的迭代器类RBTreeIterator的operator系列等接口的检测如下
//对set的begin()等接口的检测如下
for (Set<string>::iterator it = s.begin(); it != s.end(); it++)
{
cout << it->first << ' ';
}
cout << endl;
//对set的删除的检测如下
s.Erase("牛奶");
s.Erase("菠萝");
s.Erase("香蕉");
for (Set<string>::iterator it = s.begin(); it != s.end(); it++)
{
cout << it->first << ' ';
}
cout << endl;
//set的迭代器实现后,范围for也就被支持了
for (pair<string, string>& p : s)
{
cout << p.first << ' ';
}
cout << endl;
}
void main()
{
test3();
}