目录
一、红黑树概念
1.概念
2.性质
二、红黑树定义
1.红黑树节点定义
(1)将新插入节点置为红色
(2)将新插入节点置为黑色
2.红黑树定义
三、红黑树插入
1.插入节点
2.控制颜色
(1)父亲为黑色
(2)父亲是红色
四、红黑树颜色处理
1.cur红,p红,g黑,u存在且为红
(1)抽象图
(2)具象图
2. cur为红,p为红,g为黑,u为黑或u不存在(单旋)
(1)抽象图
(2)具象图
3.cur为红,p为红,g为黑,u不存在或u为黑(双旋)
(1)抽象图
(2)具象图
4.红黑树节点插入代码
五、红黑树查找
六、红黑树检查是否平衡
七、红黑树遍历
八、红黑树验证
一、红黑树概念
1.概念
红黑树也是一种二叉搜索树,在每个结点上增加一个存储位,来表示该节点的颜色,节点要么是红色要么是黑色。
2.性质
红黑树具有以下性质:
(1)每个结点不是红色就是黑色
(2)根节点是黑色的
(3)没有连续的红色节点
(4)每条路径上都包含相同数目的黑色节点
由于(3)和(4)互相控制,因此满足以上性质就能保证:最长路径中节点个数不会超过最短路径节点个数的2倍。
最短路径:全部由黑色节点构成
最长路径:由一黑一红构成,且红色节点数量等于黑色节点数量。
假设黑色节点有N个,最短路径长度为最长路径长度为2*。但是在红黑树中,不一定有最短路径和最长路径。
二、红黑树定义
1.红黑树节点定义
红黑树节点相比于AVL树,多了一个颜色,因此需要一个成员变量来存储节点颜色。AVL树用高度严格控制平衡。红黑树近似平衡,所以不需要平衡因子。
但是红黑树节点的构造函数在初始化节点时,肯定要初始化节点颜色的,那么颜色需要一开始初始化成红色,因为初始化成红色,可能破坏规则(3),影响不大。但是假如将节点初始化成黑色,一定会破坏规则(4)。
(1)将新插入节点置为红色
假如将新插入节点置为红色,会有以下两种情况:
①当父亲是黑色时,没有破坏规则(3),也没有破坏规则(4)
②当父亲是红色时,破坏了规则(3),但是只需要改变一下颜色即可
(2)将新插入节点置为黑色
但是假如将新节点初始化成黑色,不管父亲是黑色还是红色,一定会破坏规则(4),并且影响其他路径,影响范围广。
①当父亲是黑色时,破坏了规则(4)
②当父亲是红色时,也破坏了规则(4)
因此节点要初始化成红色。
-
//红黑树节点颜色
-
enum
Colour
-
{
-
RED,
-
BLACK,
-
};
-
-
//红黑树节点定义
-
template<
class
K,
class
V>
-
struct
RBTreeNode
-
{
-
RBTreeNode<K, V>* _left;
//节点的左孩子
-
RBTreeNode<K, V>* _right;
//节点的右孩子
-
RBTreeNode<K, V>* _parent;
//节点的父亲
-
-
pair<K,V> _kv;
//节点的值
-
Colour _col;
//节点颜色
-
-
RBTreeNode(
const pair<K, V>& kv)
-
:_left(
nullptr)
-
,_right(
nullptr)
-
,_parent(
nullptr)
-
,_kv(kv)
-
,_col(RED)
//节点初始化成红色
-
{}
-
};
2.红黑树定义
-
template<
class
K,
class
V>
-
class
RBTree
-
{
-
typedef RBTreeNode<K, V> Node;
-
-
//构造函数
-
RBTree()
-
:_root(
nullptr)
-
{}
-
-
private:
-
Node* _root;
-
};
三、红黑树插入
1.插入节点
插入节点分为2步:
(1) 先查找,如果树中已存在该节点,则插入失败
(2)树中不存在该节点,插入
-
//插入
-
pair<Node*, bool> Insert(const pair<K, V>& kv)
-
{
-
if (_root ==
nullptr)
-
{
-
_root =
new
Node(kv);
-
_root->_col = BLACK;
-
return
make_pair(_root,
true);
-
}
-
-
//1.先看树中,kv是否存在
-
Node* parent =
nullptr;
-
Node* cur = _root;
-
while (cur)
-
{
-
if (cur->_kv.first < kv.first)
-
{
-
//kv比当前节点值大,向右走
-
parent = cur;
-
cur = cur->_right;
-
}
-
else
if (cur->_kv.first > kv.first)
-
{
-
//kv比当前节点值小,向左走
-
parent = cur;
-
cur = cur->_left;
-
}
-
else
-
{
-
//kv和当前节点值相等,已存在,插入失败
-
return
make_pair(cur,
false);
-
}
-
}
-
-
//2.走到这里,说明kv在树中不存在,需要插入kv,并且cur已经为空,parent已经是叶子节点了
-
Node* newNode =
new
Node(kv);
-
newNode->_col = RED;
-
if (parent->_kv.first < kv.first)
-
{
-
//kv比parent值大,插入到parent的右边
-
parent->_right = newNode;
-
cur->_parent = parent;
-
}
-
else
-
{
-
//kv比parent值小,插入到parent的左边
-
parent->_left = newNode;
-
cur->_parent = parent;
-
}
-
cur = newNode;
-
-
return
make_pair(cur,
true);
-
}
2.控制颜色
所以在插入节点之后,为了满足红黑树的性质,还需要调整节点颜色:
(1)父亲为黑色
如果父亲是黑色,那么不需要调整,4个规则都满足,插入已完成
(2)父亲是红色
如果父亲是红色,违反了规则(3),需要调整颜色
这时就需要对树中的节点进行颜色处理了。
四、红黑树颜色处理
所有插入的新节点颜色都是红色,当父亲是红色时,需要进行颜色处理,分3种情况进行处理。在这3种情况中,cur为新插入节点,p为父亲节点,u为叔叔节点,g为祖父节点。下面的树都可能是一棵完整的树,也有可能是一棵子树。
1.cur红,p红,g黑,u存在且为红
当cur为红,p为红,g为黑,u存在且为红时,为了满足红黑树的性质,处理方法:将p和u变黑,g变红
(1)抽象图
如下a、b、c、d、e都是子树:
(1)假如g是根节点,根据红黑树性质,根节点必须是黑色,那就把g再变黑就好了
(2)假如g不是根节点,g是子树,那么g还有parent节点。
①如果g的parent是黑色,满足红黑树性质,那么停止调整。
②如果g的parent是红色,就破坏了规则(3),那么还需要继续向上调整。
调整方法为:把g当作cur,继续向上调整,直到p不存在,或p存在且为黑停止。
(2)具象图
①g是根节点,直接把p和u变黑,g变红
②g不是根节点,g是子树,把p和u变黑,g变红之后,还要继续向上调整,直到p不存在,或p存在且为黑停止
2. cur为红,p为红,g为黑,u为黑或u不存在(单旋)
这种情况下,g、p、cur形成直线,先看cur为红,p为红,g为黑,u为黑的情况
这是由情况一cur红,p红,g黑,u存在且为红处理以后变换而来,比如以下情况:
在这种情况下,cur原来的颜色一定是黑色,只不过在处理的过程当中,将cur的颜色变成了红色,所以cur不是新增节点,而是新增节点的祖先节点。
(1)抽象图
如下a、b、c、d、e都是子树,由于要旋转,所以要分为两种情况:当p是g的左子树,cur是p的左子树时,g右单旋,p变黑,g变红:
当p是g的右子树,cur是p的右子树时,g左单旋,p变黑,g变红:
(2)具象图
cur是新增节点的祖先节点,那么a、b一定不为空,由于从g到u的路径上有2个黑色节点,那么a和b都存在一个黑色节点,因此,c中也有一个黑色节点,才能满足每条路径上有相同数目的黑色节点。因此d、e要么同时不存在,要么同时为空。
当p是g的左子树,cur是p的左子树时,将节点g右单旋,p变黑,g变红:
当p是g的右子树,cur是p的右子树时,将节点g左单旋,p变黑,g变红:
再看cur为红,p为红,g为黑,u不存在的情况:
u不存在的情况更为简单,假如p是g的左子树,cur是p的左子树,将节点g右单旋,p变黑,g变红即可
假如p是g的右子树,cur是p的右子树,将节点g左单旋,p变黑,g变红即可
3.cur为红,p为红,g为黑,u不存在或u为黑(双旋)
这种情况下g、p、cur形成折线,先看cur为红,p为红,g为黑,u为黑的情况:
(1)抽象图
当p是g的左子树,cur是p的右子树时,处理方法:p左单旋,g右单旋,cur变黑,g变红:
当p是g的右子树,cur是p的左子树时,处理方法:p右单旋,就变成了情况二
(2)具象图
当p是g的左子树,cur是p的右子树时,将p左单旋,g右单旋,cur变黑,g变红
当p是g的右子树,cur是p的左子树,p右单旋,g左单旋,p变黑,g变红
再看cur为红,p为红,g为黑,u不存在的情况,u不存在的情况更为简单:
当p是g的左子树,cur是p的右子树时, 将p左单旋,g右单旋,cur变黑,g变红
当p是g的右子树,cur是p的左子树,p右单旋就变成了情况二:
4.红黑树节点插入代码
-
//插入
-
pair<Node*, bool> Insert(const pair<K, V>& kv)
-
{
-
if (_root ==
nullptr)
-
{
-
_root =
new
Node(kv);
-
_root->_col = BLACK;
-
return
make_pair(_root,
true);
-
}
-
-
//1.先看树中,kv是否存在
-
Node* parent =
nullptr;
-
Node* cur = _root;
-
while (cur)
-
{
-
if (cur->_kv.first < kv.first)
-
{
-
//kv比当前节点值大,向右走
-
parent = cur;
-
cur = cur->_right;
-
}
-
else
if (cur->_kv.first > kv.first)
-
{
-
//kv比当前节点值小,向左走
-
parent = cur;
-
cur = cur->_left;
-
}
-
else
-
{
-
//kv和当前节点值相等,已存在,插入失败
-
return
make_pair(cur,
false);
-
}
-
}
-
-
//2.走到这里,说明kv在树中不存在,需要插入kv,并且cur已经为空,parent已经是叶子节点了
-
Node* newNode =
new
Node(kv);
-
newNode->_col = RED;
-
if (parent->_kv.first < kv.first)
-
{
-
//kv比parent值大,插入到parent的右边
-
parent->_right = newNode;
-
cur->_parent = parent;
-
}
-
else
-
{
-
//kv比parent值小,插入到parent的左边
-
parent->_left = newNode;
-
cur->_parent = parent;
-
}
-
cur = newNode;
-
-
//如果父亲存在,且父亲颜色为红就要处理
-
while (parent && parent->_col == RED)
-
{
-
//关键看叔叔
-
Node* grandfather = parent->_parent;
-
if (parent == grandfather->_left)
//父亲是祖父的左子树
-
{
-
Node* uncle = grandfather->_right;
-
//情况一:叔叔存在且为红
-
if (uncle->_col == RED)
-
{
-
parent->_col = uncle->_col = BLACK;
-
grandfather->_col = RED;
-
-
//继续向上调整
-
cur = grandfather;
-
parent = cur->_parent;
-
}
-
else
//情况二+情况三:叔叔不存在或叔叔存在且为黑
-
{
-
//情况二:单旋
-
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
//父亲是祖父的右子树
-
{
-
Node* uncle = grandfather->_left;
-
//情况一:叔叔存在且为红
-
if (uncle && uncle->_col == RED)
-
{
-
parent->_col = uncle->_col = BLACK;
-
grandfather->_col = RED;
-
-
//继续往上调整
-
cur = grandfather;
-
parent = grandfather->_parent;
-
}
-
else
//情况二+情况三:叔叔不存在或叔叔存在且为黑
-
{
-
//情况二:单旋
-
if (cur == parent->_right)
-
{
-
RotateL(grandfather);
-
parent->_col = BLACK;
-
grandfather->_col = RED;
-
}
-
else
//情况三:双旋
-
{
-
RotateR(parent);
-
RotateL(grandfather);
-
cur->_col = BLACK;
-
grandfather->_col = RED;
-
}
-
break;
//插入结束
-
}
-
}
-
-
}
-
_root->_col = BLACK;
-
-
return
make_pair(newNode,
true);
-
}
五、红黑树查找
查找比较简单,递归向左走或向右走:
-
//查找
-
Node* Find(const K& key)
-
{
-
Node* cur = _root;
-
while (cur)
-
{
-
if (cur->_kv.first < key)
//key比当前节点小,就向右查找
-
{
-
cur = cur->_right;
-
}
-
else
if (cur->_kv.first > key)
//key比当前节点大,就向左查找
-
{
-
cur = cur->_left;
-
}
-
else
//找到了
-
{
-
return cur;
-
}
-
}
-
return
nullptr;
//空树,直接返回
-
}
六、红黑树检查是否平衡
检查是否平衡还是要用到递归子函数
(1)需要判断是否满足红黑树的4条性质
(2)计算最左路径上的黑色节点个数,递归路径时,需要计算该条路径上的黑色节点个数是否和最左路径上的节点个数相等
-
bool _CheckBalance(Node* root,
int blackNum,
int count)
-
{
-
if (root ==
nullptr)
-
{
-
if (count != blackNum)
-
{
-
cout <<
"黑色节点数量不相等" << endl;
-
return
false;
-
}
-
return
true;
-
}
-
-
if (root->_col == RED && root->_parent->_col == RED)
-
{
-
cout <<
"存在连续红色节点" << endl;
-
return
false;
-
}
-
-
if (root->_col == BLACK)
-
{
-
count++;
-
}
-
-
return _CheckBalance(root->_left, blackNum, count)
-
&& _CheckBalance(root->_right, blackNum, count);
-
}
-
-
//检查是否平衡
-
bool CheckBlance()
-
{
-
if (_root ==
nullptr)
-
{
-
return
true;
-
}
-
-
if (_root->_col == RED)
-
{
-
cout <<
"根节点为红色" << endl;
-
return
false;
-
}
-
-
//找最左路径做黑色节点数量参考值
-
int blackNum =
0;
-
Node* left = _root;
-
while (left)
-
{
-
if (left->_col == BLACK)
-
{
-
blackNum++;
-
}
-
left = left->_left;
-
}
-
-
int count =
0;
-
return _CheckBlance(_root, blackNum, count);
-
}
对于以下红黑树,递归过程如下:
七、红黑树遍历
遍历也很简单,中序递归遍历左子树和右子树:
-
//遍历
-
void _InOrder(Node* root)
-
{
-
if (root ==
nullptr)
-
{
-
return;
-
}
-
-
_InOrder(root->_left);
-
cout << root->_kv.first <<
":" << root->_kv.second << endl;
-
_InOrder(root->_right);
-
}
-
-
void InOrder()
-
{
-
_InOrder(_root);
-
cout << endl;
-
}
八、红黑树验证
-
#include "RedBlackTree.h"
-
-
void TestRBTree()
-
{
-
//,1,3,5,15,7,16,14
-
int a[] = {
4,
2,
6,
1,
3,
5,
15,
7,
16 };
-
RBTree<
int,
int> t;
-
for (
auto e : a)
-
{
-
t.
Insert(
make_pair(e, e));
-
}
-
-
t.
InOrder();
-
//cout << t.CheckBalance() << endl;
-
}
-
-
int main()
-
{
-
TestRBTree();
-
return
0;
-
}
插入成功: