红黑树的概念
红黑树本质是一种二叉搜索树,在二叉搜索树基础上每个结点多了一个颜色(可以是RED或者BLACK),通过对结点的控制,红黑树会确保没有一条路径会比其他路径长出两倍.因此是接近平衡的
红黑树的性质
1.红黑树的结点不是黑色就是红色
2.红黑树的根结点是黑色的
3.如果一个结点是红色,他的两个孩子结点是黑色
4.每个叶子结点都是黑色的(指空指针)
4.1 没有连续的红色结点
4.2 红色结点的父亲都是黑色
4.3 黑色结点可以连续存在
5. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
大致结构如下图所示
红黑树的结点
enum Colour//结点的颜色
{
RED,
BLACK
};
template<class k,class v>
struct RedBlackTreeNode//红黑树的结点
{
RedBlackTreeNode* _left; //结点的左孩子
RedBlackTreeNode* _right; //结点的右孩子
RedBlackTreeNode* _parent; //结点的父亲
pair<k, v> _kv; //结点的值
Colour _col; //结点的颜色
RedBlackTreeNode(const pair<k,v>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
{}
};
红黑树的插入
红黑树的插入分为三种可能会出现的情况
分别分析这三种情况会不会破坏红黑树的规则
因为新插入的结点是红色的 如果父亲结点是黑色就不破坏红黑树的结点
如果父亲结点是红色就违反了红黑树不能有连续红色结点的规则
g代表祖父结点 u代表叔叔结点 p代表父亲结点 c代表当前结点
情况一 g为黑色 u为红色 p为红色 c为红色
这种情况最好解决 只需要变色处理就可以 他的关键在于u结点存在并且为红色
只需要把p和u拉黑锅 p变红就可以了
注意的是这有可能是一颗整树root结点就是g结点
也有可能是一颗子树 g变红主要是应对子树的时候需要继续向上处理
整棵树
子树
情况二 g为黑色 u为黑色 p为红色 c为红色
这种情况需要旋转加变色 根据上图
需要右单旋+变色
![右单旋](https://i-blog.csdnimg.cn/blog_migrate/23559784347a578974c6ab7523f84f30.png)
根据红黑树的定义 只需要把p变为黑色 g变为红色即可
![](https://i-blog.csdnimg.cn/blog_migrate/49e56661b9195d81d5137931547fd9e0.png)
情况三g为黑色 u不存在 p为红色 c为红色
根据上图所示 右单旋 +变色
旋转之后发现和情况二一样,只需要把p变为黑色 g变为红色即可
代码实现如下
typedef RedBlackTreeNode<k,v> Node;
bool Insert(const pair<k,v>& kv)
{
if (_root == nullptr)//如果是空树直接插入返回即可
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
//说明不为空 继续向下走查找插入的位置
Node* parent = nullptr;
Node* cur = _root;
while (cur)//如果为空就找到了插入的位置
{
if (cur->_kv.first < kv.first)//如果插入的值比我的值大就去我的右
{
parent = cur;
cur = cur->_right;
}
else if(cur->_kv.first > kv.first)//如果插入的值比我的值小就去我的左
{
parent = cur;
cur = cur->_left;
}
else//说明插入的值和我的值相等 不允许插入 返回false
{
return false;
}
}
//找到插入结点开始插入
cur = new Node(kv);
cur->_col = RED;//新结点的颜色为红色
if (parent->_kv.first < kv.first)//连接父亲结点
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//判断平衡因子(颜色)
while (parent && parent->_col == RED)//两个连续红色才需要变色
{
Node* grandfather = parent->_parent;
assert(grandfather);
if (grandfather->_left == parent)//去找叔叔结点的位置
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)//uncle存在并且为红色
{
parent->_col = BLACK;
uncle->_col = BLACK;
//继续向上处理
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//说明uncle不存在或者uncle为黑色 需要旋转or旋转加变色
{
if (cur == parent->_left)//需要单旋 加变色
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//需要双旋
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//break;
}
}
else//grandfather->_right=parent
{
Node* uncle = grandfather->_left;
if (uncle &&uncle->_col == RED)
{
uncle->_col = BLACK;
parent->_col = BLACK;
//需要向上处理
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//说明uncle不存在或者uncle为黑色 需要旋转or旋转加变色
{
if(parent->_right == cur)//需要单旋 加变色
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//需要双旋
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//break;
}
}
}
//最后要把根结点变为黑色
//因为插入阶段会把g变为红色 g有可能是root结点 所有手动控制把root结点变为黑色
_root->_col = BLACK;
return true;
}
红黑树的验证
红黑树的验证分为两步
1.检测其是否满足搜索二叉树(中序打印有序即可)
2.检测其是否满足红黑树性质即可
检测其是否满足搜索二叉搜索树简单 中序遍历即可
检测其是否满足红黑树性质稍微复杂一点
红黑树的性质 需要判断的几点
1.root结点必须为黑色
2.不能有两个连续的红色结点
3.每条路径的黑色结点相等
bool IsBalance()//检查树是否平衡
{
if (_root == nullptr)//红黑树允许空树的存在
{
return true;
}
if (_root->_col == RED)//如果根结点的颜色为红色 不符合红黑树的性质
{
return false;
}
Node* cur = _root;
int benchmark = 0;//定义benchmark 为基准值
while (cur)//去遍历最简单的路径 最左路径或者最右路径
{
if (cur->_col == BLACK)
{
++benchmark;
}
cur = cur->_left;
}
return prevcheck(_root, 0, benchmark);
}
bool prevcheck(Node* root,int BlackNum,int benchmark)//检查平衡
{
if (root == nullptr)//说明走到了末尾
{
if (benchmark != BlackNum)//去判断两个路径里的黑色结点是否相同
{
cout << "黑色结点不相等" << endl;
return false;
}
else
{
return true;
}
}
if (root->_col == BLACK)
{
++BlackNum;
}
//去判断root和root的儿子结点不好判断
//儿子可能不存在 可能在左 可能在右 情况比较复杂
//所以我们直接判断root和root的父亲 root的父亲一定存在
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "存在连续红色结点" << endl;
return false;
}
return prevcheck(root->_left,BlackNum,benchmark)//去检查所有路径的黑色结点个数
&& prevcheck(root->_right,BlackNum,benchmark);
}
红黑树和AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( ),红黑树不追求绝对平衡,其 只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构 中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。