目录
一.概念与性质
二.基本操作
1.建树
2.插入
情况一
情况二
3.查找
4.验证
三.红黑树与AVL树的比较
一.概念与性质
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质:
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
注意:
1.我们平时所说的叶子节点指的是没有子节点的节点,但在红黑树中,叶子节点指的是空节点,即图中NIL节点。这样有助于红黑树的效率与维护,但在简单实现中,其体现感不强。
2.红黑树中不能有连续的红色节点,但可以有连续的黑色节点。
3.红黑树最坏情况下最长路径是最短路径的两倍。由性质3、4决定,最长路径为黑-红-黑交替,最短路径为全黑。
二.基本操作
1.建树
树的节点跟树的建立跟二叉搜索树与AVL树相近, 但需要添加颜色标记,采用枚举体。
注意:新插入的节点应该是红色的,因为如果插入黑色节点,一定会违反性质4,但如果插入红色节点,只可能会违反性质3(红色节点下连接了一个新的红色节点)。因此,应选择默认插入红色节点,减少修改次数。
enum Colour
{
RED, //0
BLACK //1
};
//RBTree树节点
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)
{ }
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
2.插入
红黑树的插入方式与AVL树相似,即按照大小走到底部,找到插入位置,插入后自下而上维护颜色性质即可。
关键在于如何在插入后进行调整:
当新插入的节点的父节点是黑色的,没有违反红黑树任意一条性质,不需要改动;当新插入的节点的父节点是红色的,违反了性质三,即不能有连续的红节点,此时需分情况讨论。
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
注意:以下图解均为叔叔在祖父的右边,代码中给出了全部情况
情况一
cur为红,p为红,g为黑,u存在且为红
对此,可以将抽象子树a, b, c, d的高度按h = 0,h = 1举例,当h > 2后的种类很多,不再展示,0和1对于规律的推出已经足够。
h = 0时
h = 1时
由此,我们得出规律:当cur为红,p为红,g为黑,u存在且为红时:将p,u改为黑,g改为红。如果g的父节点存在且为红,则继续向上调整,否则停止。若根节点变红了,则要把其调整回黑色。
代码:
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
{
// 相等不插入
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;
//如果叔叔在右边
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//叔叔存在且为红, 往上变色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上
cur = grandfather;
parent = cur->_parent;
}
}
else//叔叔在左边
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
}
}
// 如果根节点变红了 设回黑
_root->_col = BLACK;
return true;
}
情况二
cur为红,p为红,g为黑,u不存在/u为黑
依旧是选取h=0 和 h = 1时举例:
h = 0,u不存在时
h = 1,u存在时,两种情况
因为在旋转时不对颜色进行处理,所以u不存在时和u为黑归为一类,所以当cur为红,p为红,g为黑,u不存在/u为黑且u在其祖父节点的右边时:若cur在p的左边,则进行右单旋将p变为黑,g变为红;若cur在p的右边,则先进行左单选,再进行右单旋,将cur变为黑,g变为红。
注意:只要进行了上述两种带旋转的操作,涉及到的节点的最上端祖先一定变为了黑色,无需再向上调整。
插入完整代码:
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
{
// 相等不插入
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;
//如果叔叔在右边
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//叔叔存在且为红, 往上变色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上
cur = grandfather;
parent = cur->_parent;
}
// 叔叔不存在或者存在且为黑
else
{
if(cur == parent->_left)
{
// g
// p u
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
//cur去了g的位置 g去了u的位置
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 = cur->_parent;
}
else // 叔叔不存在,或者存在且为黑
{
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
// 如果根节点变红了 设回黑
_root->_col = BLACK;
return true;
}
//右旋
void RotateR(Node* parent)
{
// 要更改六条边
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 先处理parent和SubLR
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
//再处理parent和 subl
//因为 后面需要把 parent的parent和subl链接
//需要先保存parent的parent
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
//处理 subl的 父节点
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
//左旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
}
3.查找
与二叉搜索树,AVL树一致,从上往下按大小走即可。
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
{
cur = cur->_right;
}
else if (cur->_kv.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
4.验证
红黑树的检验主要在两个方面:
1.其中序遍历是否为有序(二叉搜索树的性质)
2.是否满足红黑树的性质
关于性质4,我们可以先预处理一条路径的黑色节点数,再中序遍历时判断是否相等即可。
public:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
if (_root->_col == RED)
{
return false;
}
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return _Check(_root, 0, refNum);
}
int Height()
{
return _Height(_root);
}
int Size()
{
return _Size(_root);
}
private:
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
return max(_Height(root->_left), _Height(root->_right)) + 1;
}
bool _Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
//cout << blackNum << endl;
if (refNum != blackNum)
{
cout << "存在黑色节点的数量不相等的路径" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
blackNum++;
}
return _Check(root->_left, blackNum, refNum)
&& _Check(root->_right, blackNum, refNum);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
三.红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2 N),红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红 黑树更多。