我们在上一篇博客认识到了平衡二叉树(AVLTree),了解到平衡二叉树的性质,其实平衡二叉树最大的作用就是查找,AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。AVL树的效率就是高在这个地方。如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理, 现在呢,我们来思考一下虽然AVL树的查找效率高,但是呢构建一颗AVL树的成本是多少?? 因为当它的高度差大于1的时候,就要触发旋转算法来调节平衡二叉树,所以当你数据足够大的时候,那么你创建一颗平衡二叉树的成本其实不小. 这个时候就有人开始思考,并且提出了红黑树的理论,那么红黑树到底比AVL树好在哪里?首先我们应该知道由于AVL树的性质,当它的任意两个分支之间的高度差大于一的时候就会触发旋转算法,所以构建一棵树得触发很多次旋转算法,这里的AVL树的查找时间复杂度为O(logN),但是红黑树它的性质让它的查找的时间复杂度为O(nlogN).可以看出AVL树时间复杂度优于红黑树,BUT!! 在计算机中它的计算速度惊人每秒的运算量都是亿级的,所以nlogN和logN在计算机面前没有什么区别,红黑树触发旋转算法的概率要远远小于AVL树,这时构建一个红黑树的成本也就远远小于AVL树,所以生活中经常使用的都是红黑树.(AVL树只是红黑树的一个前身,红黑树就是AVL树的加强版)首先我们来认识红黑树的性质,红黑树是一颗二叉搜索树,它在每个节点上增加了一个存储为来表示节点的颜色,可以RED或BLACK,通过对任何一条从根到叶子简单路径上的颜色约束,红黑树保证最长路径不超过最短路径的两倍,因而近似平衡.。
红黑树的具体性质
1.每个节点的颜色不是红色就是黑色.
2.红黑树的根节点必须是黑色的.
3.不能出现连续的红节点
4.对每一个节点,从该节点到齐所有后代节点的简单路径上,均包含相同数目的黑色节点.
其实当我们的上面这些条件满足后,这棵树就是已经是一个最长路径不超过最短路径的两倍了. 具体为什么??
很明显,只要满足上面几条性质,最长路径一定超过最短路径的两倍。
红黑树的插入
首先红黑树的插入其实不是那么容易实现的,以前搜索树的插入我们很容易理解现在我们首先思考一个问题,你插入节点的默认颜色是RED或BLACK? 这里我们需要根据性质来思考,首先如果插入黑节点,这个可以直接插入无论它的父亲是什么颜色,但是红黑树的性质是每条路径的黑色节点数目相同这个时候你再想想那其他路径的黑色节点数目一定比你现在少一个节点,所以调整起来是非常繁琐的. 插入红节点不需要调整其他路径,如果它的父亲为黑,那么直接插入,如果他的父亲为红那么在该路径上面开始分情况调整. 所以插入节点默认颜色一定要为红.如果为黑调节成本太大了.接下来开始插入节点如果插入节点的父亲为黑那么直接插入后返回不需要做任何调整. 但是如果插入节点的父亲为红,那么就需要调整了.具体的调整过程可以分为三个情况:
1. 新插入节点cur的父亲为红,并且祖父节点(gf)为黑,叔叔节点(uncle)存在且为红
这种情况是最简单的情况,出现了两个连续的红节点需要调整,我们只需要将parent和uncle变为黑色,再将gf变为红色即可,然后向上更新进行调色,直到该树中没有连续的红节点即可;
2.新插入的节点的parent为红色,uncle不存在或者uncle存在且为黑
首先来说uncle不存在的情况,首先,parent必须变黑(必须二字是肯定的,如果你不把parent变黑,那么存在连续的红节点,难道想把cur变黑吗?这和直接插入黑节点这种麻烦的做法有什么区别?),其次parent变黑了之后,这条路上就多了一个黑节点,我们接着以gf为基准进行一次右旋,然后再把gf变红即可
接下来我们讨论一下最难的情况:uncle存在且为黑
这种情况肯定是向上进行调整的过程中遇到的情况,也就是说cur节点肯定不是新插入的节点,单纯的插入一个节点是不可能出现这种情况,试想一下,去掉你新插入的节点,这棵树(就这颗子树而言)很明显就不是红黑树了,那么出现这种情况的原因只可能是因为向上调整而造成的变色导致的;举个简单的例子一看便知:
cur为新增节点,新增的cur导致树中出现了连续的红节点,不再满足红黑树的性质,此时uncle存在且为红,满足第一种情况,我们按照第一种情况的调整方法来进行调整;
现在就是一个典型的uncle存在且为黑的情况了,那具体应该怎样调节呢?
还是一样,parent必须变黑,然后以gf为基准进行一次右单旋,再将gf变红即可,如图:
好了,就是这样了,现在说的都是单旋的情况,接下来看一个双旋的情况;
咦,这不是和我们第一种情况一样吗?是一样的,为了触发双旋来看下面,首先上图中的parent和uncle变黑,gf变红,接着向上调整,让cur=gf,就触发了双旋,旋转及变色过程如下:
这就是我们红黑树的插入实现了,上述我们所举的所有粒子都是parent是gf左子树,uncle是gf右子树的情况,当parent为gf右子树,uncle为gf左子树的时候调整以及变色的思想都是差不多的,这里我不再举例说明,直接看完整版代码:
#pragma once
#include
using namespace std;
enum colour{
BLACK,
RED,
};
template
struct RBTreeNode{
Key _key;
Value _val;
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
colour _colour;
RBTreeNode(const Key& key,const Value& val)
:_key(key)
,_val(val)
,_left(NULL)
,_right(NULL)
,_parent(NULL)
,_colour(RED)
{}
};
template
class RBTree{
typedef RBTreeNode Node;
public:
RBTree()
:_root(NULL)
{}
void InOrder()
{
_InOrder(_root);
}
void _InOrder(Node* root)
{
if(root==NULL)
{
return;
}
_InOrder(root->_left);
cout<_key<<": ";
cout<_colour<_right);
}
//左旋,和AVL一样
void RotateL(Node* parent)
{
Node* subR=parent->_right;
Node* ppNode=parent->_parent;
Node* subRL=subR->_left;
parent->_right=subRL;
if(subRL)
{
subRL->_parent=parent;
}
subR->_left=parent;
parent->_parent=subR;
if(_root==parent)
{
_root=subR;
subR->_parent=NULL;
}
else
{
if(parent==ppNode->_left)
{
ppNode->_left=subR;
}
else
{
ppNode->_right=subR;
}
subR->_parent=ppNode;
}
}
//右旋,和AVL一样
void RotateR(Node* parent)
{
Node* ppNode=parent->_parent;
Node* subL=parent->_left;
Node* subLR=subL->_right;
parent->_left=subLR;
if(subLR)
{
subLR->_parent=parent;
}
subL->_right=parent;
parent->_parent=subL;
if(_root==parent)
{
_root=subL;
subL->_parent=NULL;
}
else
{
if(parent==ppNode->_left)
{
ppNode->_left=subL;
}
else
{
ppNode->_right=subL;
}
subL->_parent=ppNode;
}
}
bool Insert(const K& key,const V& value)
{
if(_root==NULL)//如果根节点为空,则创建一个新节点,并将颜色置为黑
{
_root=new Node(key,value);
_root->_colour=BLACK;
return true;
}
Node* parent=NULL;
Node* cur=_root;
//先找到插入位置
while(cur)
{
if(cur->_key>key)
{
parent=cur;
cur=cur->_left;
}
else if(cur->_key_right;
}
else
{
//插入失败
return false;
}
}
//将新增节点置为红色
cur=new Node(key,value);
cur->_colour=RED;
if(parent->_key>key)
{
parent->_left=cur;
}
else
{
parent->_right=cur;
}
cur->_parent=parent;
//父亲为黑插入节点还是红黑树
while(parent&&parent->_colour==RED)//父亲为红
{
//关键看uncle
Node* gf=parent->_parent;
if(gf->_left==parent)//parent为gf的左子树,则uncle为gf的右子树
{
Node* uncle=gf->_right;
//uncle存在且为红,将uncle和parent的颜色都置为黑,将gf的颜色置为红,然后继续向上更新
if(uncle&&uncle->_colour==RED)
{
uncle->_colour=parent->_colour=BLACK;
gf->_colour=RED;
cur=gf;
parent=cur->_parent;
}
else//uncle不存在,或者uncle存在且为黑
{
if(parent->_right==cur)//parent为gf的左子树,cur为parent的右子树时,需要进行双旋
{
RotateL(parent);
swap(cur,parent);
}
RotateR(gf);//右单旋
parent->_colour=BLACK;
gf->_colour=RED;
}
}
else
{
Node* uncle=gf->_left;
if(uncle&&uncle->_colour==RED)
{
parent->_colour=uncle->_colour=BLACK;
gf->_colour=RED;
cur=gf;
parent=cur->_parent;
}
else
{
if(parent->_left==cur)
{
RotateR(parent);
swap(cur,parent);
}
RotateL(gf);
parent->_colour=BLACK;
gf->_colour=RED;
}
}
}
_root->_colour=BLACK;
return true;
}
//此部分为判断一棵树是否为红黑树
bool IsBanlance()
{
if(_root&&_root->_colour==RED)
{
return false;
}
//求出每条路径的黑节点数目
Node* cur=_root;
size_t N=0;
size_t blackNode=0;
while(cur)
{
if(cur->_colour==BLACK)
{
N++;
}
cur=cur->_left;
}
return _IsBanlance(_root,blackNode,N);
}
bool _IsBanlance(Node* root,size_t blackNum,const size_t N)
{
if(root==NULL)
{
if(N!=blackNum)
{
cout<<"黑色节点数不相等"<_colour==BLACK)
{
blackNum++;
}
if(root->_colour==RED&&root->_parent->_colour==RED)
{
cout<<"存在连续的红节点"<_key<_left,blackNum,N)&&_IsBanlance(root->_right,blackNum,N);
}
private:
Node* _root;
};
void TestRBTree()
{
RBTree t;
int arr[]={4,2,6,1,3,5,15,7,16,14};
for(int i=0; iIsbanlance? "<
运行结果:
上述代码中包含了判断一棵树是否为红黑树的一个函数IsBalance,假设现在有一个面试题,让我们判断一棵树是不是红黑树,我们该怎么做,在这里,我们应该抓住红黑树每条路径的黑节点数目相并且不存在连续的红节点的特性,基于这两个特性,求出这个问题分为两步:
1.先随便遍历一条路径(比如从根节点开始一直向左走或者向右),求出这条路上黑节点的数目BlackNum
2.写一个递归函数,将求出的黑色节点数目BlackNum传参穿进去,再定义一个变量n记录当前黑节点的数目,从根节点开始,对左右子树分别进行递归操作(遇到一个黑色节点n++),同时遇到一个红色节点,就判断一次它的父节点是不是红色,每次到叶子节点的时候判断这个n是否等于BlackNum,只要有一条路径上的n不等于BlackNum,那这颗树就不是红黑树。