前面我们学习了AVL树,而AVL树对于树高度的检查非常严格,这样的好处是我们树的查找效率非常高,但是这样严格的检查,会导致我们树的插入,删除这些操作效率会低一些(因为AVL树的旋转非常的频繁);所以因为这个原因,出现了一种对数高度检查相对于AVL树要开放一些的树——红黑树;红黑树的效率分配较为均匀,使得插入,删除,查找都具有不错的效率;这也是为什么我们stl中map和set的底层使用的是红黑树的原因;
红黑树也好二叉树也罢它们本质都是搜索树,只是通过不同的方式和标准来平衡我们的二叉树,使得我们的搜索二叉树不会出现歪脖子树的情况;
效率均衡的原因:标准与规则
红黑树建立标准
红黑树的平衡标准是树的两边相差的高度要小于两倍的关系;就是树一边的节点数最多等于另一边节点的两倍;
平衡规则
1.红黑树的根节点为黑色;
2.红黑树不能有两个连续的红色节点;
3.红黑叔每个节点到叶子节点中的黑色节点的个数相同;
4.每个节点只有红黑两种颜色;
5.树末尾节点连接的空节点看作黑色节点;
接下来我们一边实现我们的红黑树,一边理解红黑树做了什么平衡了我们的二叉树;
红黑树节点
enum Color//枚举类型
{
Red,
Black
};
template <class K,class V>
struct RBTreeNode
{
typedef RBTreeNode<K, V> node;
node* _left;
node* _right;
pair<K, V> _data;
node* _parent;
Color _cor; //节点颜色
RBTreeNode<K,V>(const pair<K,V>& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _cor(Red)
,_data(data)
{}
};
红黑树的节点包含的还是三叉指针,还有数据;和AVL树不同的是红黑树的节点中存储了颜色(满足条件4),这一成员;(节点在初始化的时候都是红色)
我们继续按照数据结构的通用性质,先描述再组织;我们描述了节点之后,我们要开始把节点组织起来了;
组织节点
我们建立一个红黑树的类
template<class K,class V>
class RBTree
{
public:
RBTree()
:_root(nullptr)
{}
typedef RBTree<K, V> tree;
typedef RBTreeNode<K, V> node;
bool insert(const pair<K,V>& data);//通过插入一个个节点来形成红黑树
private:
node* _root;
};
将节点放入这棵类中,这个类就是我们写的红黑树;
插入节点
有了放置节点的类了之后,我们肯定需要开始将节点放入我们的类中;我们在这个类中,写了一个insert接口,通过调用这个接口,我们就可以形成节点并插入类中;我们下面开始实现我们的insert接口;
首先,我们从树的根节点开始寻找,我们将要插入数据的位置;这一步非常简单,和搜索二叉树没有任何不同,找位置,然后插入,如果树中有相同数据则无法插入;
bool insert(const pair<K,V>& data)
{
if (_root == nullptr)//代表树为空树,这个新插入数据直接作为根
{
_root = new node(data);
_root->_cor = Black;
return true;
}
node* cur = _root;
node* parent = cur;
while (cur)
{
if (data.first < cur->_data.first)
{
parent = cur;
cur = cur->_left;
}
else if(data.first > cur->_data.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new node(data);
if (parent->_data.first > cur->_data.first)
{
parent->_left = cur;
}
else if (parent->_data.first < cur->_data.first)
{
parent->_right = cur;
}
cur->_parent = parent;
//红黑树操作
。。。。。。
}
找到位置之后,通过数据insert接收的参数创建节点(初始的创建节点都为红色),然后再插入相应位置;值得注意的是:如果我们插入的是空树那么这个第一个插入的节点为根所以它的颜色需要被更改成黑色;
接下来就要进入红黑树的精华部分了;
红黑树平衡操作
我们将成立的条件复制到下面来,以便于我们观看:
1.红黑树的根节点为黑色;
2.红黑树不能有两个连续的红色节点;
3.红黑叔每个节点到叶子节点中的黑色节点的个数相同;
4.每个节点只有红黑两种颜色;
5.树末尾节点连接的空节点看作黑色节点;
前面我们在创建节点并插入的时候,就已经可以满足1,4两个条件了;接下来我们需要判断我们新插入的节点是违反了上面的条件2,3;
首先我们需要判断我们插入的节点的父亲节点颜色;黑色 判断我们当前节点插入正确,不需要再进行调整了;红色 则违法了条件2,我们需要对我们的树进行调整;
我们现在列举出调整的几种情况:
情况1
我们的uncle节点(父亲节点的父亲节点的另一个子节点)为红色且我们的父亲节点也为红色的时候;
我们变换完颜色后还需要向上调整;因为我们的ppnode颜色变为了红色,我们还需要检查ppnode的父亲节点颜色是否违反了两个连续节点不能为红色的规则;
那么这样我们的ppnode不一定是根需要向上调整,是不是也说明我们的cur也不一定是新插入的节点呢,它会不会也是由ppnode变为红色形成的新的cur节点呢?当然这是肯定的啦!所以我们如果要全面的用抽象图概括这种情况;应该使用下面的这张图;
情况2(单旋)
当我们的uncle节点为黑色或者uncle节点不存在的时候;
uncle不存在时:
这种情况一定是在末尾的时候出现的,因为,只有在末尾插入的时候uncle才可能不存在,如果是递归回到上层时发现的uncle节点为空,那么一定会违反每个节点到叶子节点的黑色节点个数相同的规则;所以这种情况一定是在末尾插入cur时形成的;
uncle存在为黑的时候:
此时的操作还有理由和上面unlce为空时相同;只不过和uncle不存在不同的是,我们此时的情况一定是由情况1向上遍历的时候出现的;
情况3(双旋)
uncle不存在:
这里也一定是从末尾插入,理由同情况二的uncle不存在;
uncle存在且为黑:
由情况1向上递归形成;
所以需要进行的平衡操作就是这三种情况;
我们代码该如何编写呢,因为我们上面的三种情况其实还是要分左右枝的插入的,也就是说这三种情况又可以分为parent再ppnode的左边或者parent在ppnode的右边两种大情况;由此我们可以写出这样的代码:
bool insert(const pair<K,V>& data)
{
。。。。。。
//下面开始构造红黑树
while (parent && parent->_cor == Red)
{
node* ppnode = parent->_parent;
if (ppnode->_left == parent)//当我们的插入节点在左树的时候
{
node* uncle = ppnode->_right;
//情况1:uncle节点和父亲节点都为红
//把父亲节点和uncle节点都变黑,ppnode变红
if (uncle && uncle->_cor == Red)
{
uncle->_cor = Black;
parent->_cor = Black;
ppnode->_cor = Red;
//颜色变换后向上递归查看祖父颜色是否正确
cur = ppnode;
parent = ppnode->_parent;
}
else//当uncle节点不存在或者uncle节点为黑色的时候(情况2,3)
//我们无法再通过更换颜色来满足红黑树需求
//此时需要旋转将高度
{
if (cur == parent->_left)//右单旋(cur在ppnode的左子树的左子树上)情况2
{
RotateR(ppnode);
//右旋后更改节点颜色
parent->_cor = Black;
ppnode->_cor = Red;
break;
}
if (cur = parent->_right)//左右双旋(cur在ppnode的左子树的右子树上)情况3
{
RotateL(parent);
RotateR(ppnode);
cur->_cor = Black;
ppnode->_cor = Red;
break;
}
}
}
else//插入节点在右树
{
node* uncle = ppnode->_left;
//情况1:uncle节点和父亲节点都为红
//把父亲节点和uncle节点都变黑,ppnode变红
if (uncle && uncle->_cor == Red)
{
uncle->_cor = Black;
parent->_cor = Black;
ppnode->_cor = Red;
//颜色变换后向上递归查看祖父颜色是否正确
cur = ppnode;
parent = ppnode->_parent;
}
else//当uncle节点不存在或者uncle节点为黑色的时候(情况2,3)
//我们无法再通过更换颜色来满足红黑树需求
//此时需要旋转将高度
{
if (cur == parent->_right)//左单旋(cur在ppnode的右子树的右子树上)情况2
{
RotateL(ppnode);
//右旋后更改节点颜色
parent->_cor = Black;
ppnode->_cor = Red;
break;
}
if (cur = parent->_left)//右左双旋(cur在ppnode的右子树的左子树上)情况3
{
RotateR(parent);
RotateL(ppnode);
cur->_cor = Black;
ppnode->_cor = Red;
break;
}
}
}
}
_root->_cor = Black;
return true;
}
旋转的代码我们之前再AVL树实现的时候就有编写了;
void RotateR(node* parent)//右旋
{
node* subl = parent->_left;
node* sublr = subl->_right;
parent->_left = sublr;
if (sublr)
sublr->_parent = parent;
node* ppnode = parent->_parent;
subl->_right = parent;
parent->_parent = subl;
if (ppnode == nullptr)
{
_root = subl;
_root->_parent = nullptr;
}
else
{
subl->_parent = ppnode;
if (ppnode->_left == parent)
{
ppnode->_left = subl;
}
else
{
ppnode->_right = subl;
}
}
return;
}
void RotateL(node* parent)//左旋
{
node* subr = parent->_right;
node* subrl = subr->_left;
parent->_right = subrl;
if (subrl)
subrl->_parent = parent;
node* ppnode = parent->_parent;
subr->_left = parent;
parent->_parent = subr;
if (ppnode == nullptr)
{
_root = subr;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subr;
}
else
{
ppnode->_right = subr;
}
subr->_parent = ppnode;
}
}
就这样我们的红黑树的平衡操作也完成了;
通过上面的方式我们就建立好了一颗红黑树;
验证红黑树
我们下面通过这样的测试代码测试我们的树是否是正确的红黑树
下面的代码是通过递归的方式检查每个节点和上一节点的颜色是否都为红色,并且在递归到每个节点的时候又在递归中建立递归来检查我们这个节点到树末尾的每条路径的黑色节点是否相同;由此我们的检查获得结果;
bool isrbtree()
{
if (_root == nullptr)
{
return true;
}
if (_root->_cor == Red)
{
cout << "树的根为红色" << endl;
return false;
}
return _isrbtree(_root);//判断情况3,4
}
bool _isrbtree(node* root)//用来测试每个点是否都满足红黑树条件
{
if (root == nullptr)
return true;
if (root->_parent
&& root->_parent->_cor == Red
&& root->_cor == Red)
{
cout << "有连续的红色节点" << endl;
return false;
}
int blackcount = 0;
node* cur = root;
while (cur)
{
if (cur->_cor == Black)
blackcount++;
cur = cur->_left;
}
int k = 0;
if (!(equalblack(root, blackcount, k)))
{
cout << "路径上的黑色节点数不相同" << endl;
return false;
}
return _isrbtree(root->_left)
&& _isrbtree(root->_right);
}
bool equalblack(node* root, int blackcount, int k)//用来测试条件4是否每条路径上的黑色节点数都相同
{
if (root == nullptr)
{
if (k != blackcount)
return false;
return true;
}
if (root->_cor == Black)
k++;
return equalblack(root->_left, blackcount, k)
&& equalblack(root->_right, blackcount, k);
}
我们通过100个随机数插入我们红黑树中看是否正确形成红黑树;
void test1()
{
RBTree<int, int>t;
srand(time(0));
for (int i = 0; i < 10000; i++)
{
int x = rand()%100;
t.insert(make_pair(x, x));
}
t.print();
if (t.isrbtree())
{
cout << "红黑树建立成功"<<endl;
}
else
{
cout << "红黑树建立失败" << endl;
}
}
以上就是红黑树的全部内容了;
如果想看完整的代码可以到这个链接查看我的gitee代码: