红黑树的概念
红黑树,是一种二叉搜索树,与上节实现的AVL树使用_bf平衡因子控制绝对平衡不同,其在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
通过控制结点颜色控制最长路径小于最短路径两倍,达到接近平衡的二叉搜索树
红黑树的性质
1.每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指空结点)
性质解析:
为何通过这几个性质就可以控制路径长度?
因为红色结点的孩子必然是黑色的,所以不会出现两个连续的红色结点。最长路径就是一黑一红一黑一红进行交替,并且根节点必须是黑色的,所以红色节点最多跟黑色结点数量相同。而每条路径的黑色结点是相同的,所以全黑路径为最短,并且其为最长路径的1/2
红黑树的模拟实现
红黑树和结点的定义
enum Color {
RED, BLACK }; //枚举类型定义红和黑
template<class T>
struct RBTreeNode
{
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED) //结点初始化为红色
, _data(data)
{
}
RBTreeNode<T>* _left; //结点左孩子
RBTreeNode<T>* _right; //结点右孩子
RBTreeNode<T>* _parent;//结点的家长
Color _col; //结点的颜色
T _data; //存储的值(map存储pair, set存储key)
};
struct RBTree
{
typedef RBTreeNode<T> Node;
RBTree()
:_root(nullptr)
{
}
pair<iterator, bool> insert(const T& data);
private:
//旋转操作
void Lrotate(Node* cur);
void Rrotate(Node* cur);
void LRrotate(Node* cur);
void RLrotate(Node* cur);
Node* _root;
};
红黑树插入接口实现
为什么二叉树结点初始化为红?
因为新插入的结点若为黑,那么此路径黑节点数量增加一。根据性质可知所有路径黑结点数量相同,那么所有路径的都需要增加黑节点,那么这棵树要大改动。若为红结点,若其父母为红才需调整,若为黑则无需调整。显然更简单实现
接下来讨论一些简单的情况
情况一:结点的父亲为黑,插入后无需改动
情况二:当新插入一个结点,其父亲为红,并且其uncle为红
将grandfather的颜色变为红,将father和uncle的颜色变为黑。
对于这颗子树来说,两条路径失去了根这个黑节点,但是两条路径都多了一个黑结点
对于整颗树来说,上方结点并未变化,路径来到grandfather结点处无论走左还是右,经历的黑结点都是一样的,所以所有的路径满足黑节点数量相同
但是由于grandfather被调整为红色,若其双亲也为红色则还需要继续调整
停止条件:1.到达根 2.grandfater为黑
1.判断条件:parent不存在 注意:需要将根结点制黑
2.判断条件: grandfather为黑 已经满足红黑树条件,无需继续调整
情况三:在插入结点或向上调整过程中,其父亲为红,其uncle为黑或者不存在
1.左树过长,并且cur为parent的左节点,uncle为黑
操作:进行右旋(以parent为根)操作,然后break退出循环
注意:再右旋操作后还进行了部分颜色的调整,如parent由红变黑,grandfather由黑变红
这是因为经过旋转这棵树已经满足平衡条件,此时根可以为黑可以满足所有路径黑结点相同的条件。
由图可以很清晰的看到,因为parent和cur为红所以grandfather的右路径和parent的右路径和cur的右路径和做路径包含的黑节点相同,所以将parent标记黑,grandfather和cur标记为红剩下的路径包含的黑节点一定是相同的。并且和最初状态相比黑结点数量并没有增加
2.左树过长,并且cur为parent的左节点,uncle不存在
操作:进行右旋(以parent为根)操作,然后break退出循环
和上面一种情况很相似,并且比较简单就不赘述了
3.左树过长,并且cur为parent的右节点,uncle为黑
操作:先进行左旋操作,再进行右旋操作
我们需要通过旋转将结点尽量转移到一边这样旋转才会让树保持平衡,因为左旋和右旋对结点的颜色调控刚好和我们需要的相同,所以无需再进行调色
4.左树过长,并且cur为parent的右节点,uncle不存在
操作:和情况三相同
以下还有四种情况,为右树过长时的调控,和左树过长刚好相对,可以自行画图整理
插入代码
bool insert(const T& data)
{
//若跟为空则创造根
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
else
{
KeyOfT kot;
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//若key大与结点的key则去右树
if (kot(data) > kot(cur->_data))
{
parent = cur;
cur = cur->_right;
}
//key小于结点的key去左树
else if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_left;
}
//若等于,说明其中已经右key结点了,插入失败
else
{
return false;
}
}
//找到新插入结点的父亲了,构造新结点,将其和父亲相连
cur = new Node(data);
cur->_parent = parent;
if (kot(cur->_data) > kot(parent->_data))
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
//若父亲存在并且为红,则进行调整。若为黑不进入循环直接退出
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
Node* uncle;
//判断parent和grandfather的位置关系
if (parent == grandfather->_left)
{
//parent是grandfather的右,则uncle就是grandfather的左
uncle = grandfather->_right;
//情况二。,不管cur在parent的哪个方向都没关系,变色向上调整
//若uncle为红则进行变色处理,并且继续向上调整
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
//若uncle为黑,则进行判断parent和cur关系
else if (uncle && uncle->_col == BLACK)
{
//若是cur是parent的左,则为情况三种的状态1
if (parent->_left == cur)
{
Rrotate(parent);
break;
}
else
{
//cur 是 parent的右节点,则为情况三的状态3
LRrotate(cur);
break;
}
}
else
{
//uncle 不存在
//情况三的状态2
if (parent->_left == cur)
{
Rrotate(parent);
break;
}
else
{
//cur 是 parent的右节点
//情况三的状态4
LRrotate(cur);
break;
}
}
}
//parent在左的情况,就是和在右反过来而已,就不进行细节讲解了
else
{
uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
else if (uncle && uncle->_col == BLACK)
{
if (parent->_right == cur)
{
Lrotate(parent);
break;
}
else
{
//cur 是 parent的左节点
RLrotate(cur);
break;
}
}
else
{
//uncle 不存在
if (parent->_right == cur)
{
Lrotate(parent);
break;
}
else
{
//cur 是 parent的左节点
RLrotate(cur);
break;
}
}
}
}
}
//防止不断出现清康二,向上调整将根变红,最后标记以下
_root->_col = BLACK;
return true;
}
旋转代码
void Lrotate(Node* cur)
{
Node* parent = cur->_parent;
Node* parpar = parent->_parent;
Node* subL = cur->_left;
parent->_right = subL;
if (subL)
{
subL->_parent = parent;
}
cur->_parent = parpar;
if (parpar)
{
if (parpar->_left == parent)
{
parpar->_left = cur;
}
else
{
parpar->_right = cur;
}
}
else
{
_root = cur;
cur->_parent = nullptr;
}
cur->_left = parent;
parent->_parent = cur;
cur->_col = BLACK;
parent->_col = RED;
}
void Rrotate(Node* cur)
{
Node* parent = cur->_parent;
Node* parpar = parent->_parent;
Node* subR = cur->_right;
parent->_left = subR;
if (subR)
{
subR->_parent = parent;
}
cur->_parent = parpar;
if (parpar)
{
if (parpar->_left == parent)
{
parpar->_left = cur;
}
else
{
parpar->_right = cur;
}
}
else
{
_root = cur;
cur->_parent = nullptr;
}
cur->_right = parent;
parent->_parent = cur;
cur->_col = BLACK;
parent->_col = RED;
}
void LRrotate(Node* cur)
{
Lrotate(cur);
Rrotate(cur);
}
void RLrotate(Node* cur)
{
Rrotate(cur);
Lrotate(cur);
}
和AVL树的非常相近,只是将平衡因子的调控换成了颜色调控,整体框架并未改变
红黑树测试
bool _IsRBTree(Node* root, int benchmark, int cur_black_num)
{
//当根结点递归至空时,检验其这条路径上的黑色结点和最左路径的结点数量是否相同
if (root == nullptr)
{
if (benchmark == cur_black_num)
{
return true;
}
else
{
//不同报错
cout << "不同路径黑色节点数目可能不同" << endl;
return false;
}
}
//若这个结点为黑,黑节点数量+1
if (root->_col == BLACK)
{
cur_black_num++;
}
//若结点为红则检验其parent是否为红
if (root->_col == RED)
{
Node* parent = root->_parent;
//检验parent颜色并确保parent存在
if (parent && parent->_col == RED)
{
cout << "连续两个节点为红" << endl;
return false;
}
}
//递归检验左右树的性质
return _IsRBTree(root->_left, benchmark, cur_black_num)
&& _IsRBTree(root->_right, benchmark, cur_black_num);
}
bool IsRBTree()
{
//检验根为红色
if (_root->_col == RED)
{
cout << "根节点为红" << endl;
}
//设置标准,检验最左路径上的黑节点数量
int benchmark = 0;
int cur_black_num = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
benchmark++;
}
cur = cur->_left;
}
return _IsRBTree(_root, benchmark, cur_black_num);
}
int tree_deep(Node* root)
{
if (root == nullptr)
{
return 0;
}
int deep = tree_deep(root->_left) + 1;
if (deep < tree_deep(root->_right) + 1)
{
deep = tree_deep(root->_right) + 1;
}
return deep;
}
红黑树有几个特性
1.根为黑 2.最长路径小于最短路径的两倍 3.不能出现相连红节点 4.每条路径黑节点数量相同
红黑树的性能
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( log(N)),红黑树不追求绝对平衡,其
只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构
中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。