摘要:本文主要介绍红黑树的概念与基本实现。首先对于红黑树的概念和性质进行说明,再根据红黑树的特性设定相应的实现方案。再实现过程中,主要介绍了红黑树的插入步骤,首先补充了关于树的旋转知识,介绍了如何控制树的高度与平衡,再通过搜索的性质与红黑树的性质入手,完成红黑树的插入,实现后对其进行验证。最后将其与AVL树进行了性能比较,展示红黑树的优点。
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树遵循以下性质:
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的,即没有连续的红节点
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
根据以上性质,可以保证最长路径不超过最短路径的两倍,原因为:
- 由性质4,当根到任一个叶节点的简单路径最短时,这条路径必须全是由黑结点构成
- 由性质3,当某条路径最长时,这条路径必须由黑红相间的路径构成,此时红节点路径和黑节点相同
示例用图如下:
红黑树的形式采取与AVL树相似的方案三叉链,由于在红黑树在插入节点时,需要更新祖先的节点颜色,因此设置了_parent
指针方便更新。对于节点的结构,增加了枚举类型Colour
来记录颜色。关于节点的构造函数将节点的初始颜色设置为红色,便于后续插入过程。否则违反了性质4,增加插入难度。
对于树的结构,设置根节点即可,通过根节点来访问整课树形结构,具体代码如下:
enum Colour{
RED,
BLACK
};
template<class K, class K>
class RBTreeNode {
public:
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _KeyValue;
Colour _colour;
RBTreeNode(const pair<K, V>& KeyValue)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _colour(RED)
, _KeyValue(KeyValue)
{}
};
template<class K,class V>
class RBTree {
typedef RBTreeNode<K, V> RBTreeNode;
private:
RBTreeNode* _root;
public:
RBTree()
:_root(nullptr);
{}
};
红黑树的插入是在搜索二叉树的条件下进行改进,先进行搜索,再进行颜色与平衡控制,从而达到降低高度的目的。因此主要基本思想可以分为:
几何特征:当新节点插入较高左子树的左侧时,将其需要将其像右旋转,图示如下:
旋转意义:将高度较高的子树的最大节点上提为根,将原来的根下移为子树的根,使得高度平衡。如此原来高度为H+2的子树降低为H+1,原来H的子树提高为H+1。
实现过程:
从总体上观察,是将cur
的右子树作为了parent
的左子树,再将cur
的右子树指向parent
void RotateR(AVLTreeNode* parent) {
AVLTreeNode* cur = parent->_left;
AVLTreeNode* curR = cur->_right;
parent->_left = curR;
curR->_parent = parent;
cur->_right = parent;
parent->_parent = cur;
}
对于整棵树的整体已经变化完毕了,但是还需要对树外的指针进行操作,即对于parent
的parent
也要进行连接。如果parent
是根节点就不需要连接,将cur
作为根,否则就需要找出链接的子树,并完成相互链接。
void RotateR(AVLTreeNode* parent) {
// cur一定存在 无需判断空
AVLTreeNode* cur = parent->_left;
AVLTreeNode* curR = cur->_right;
// 对于parent的parent也要进行连接
AVLTreeNode* parentParent = parent->_parent;
parent->_left = curR;
curR->_parent = parent;
cur->_right = parent;
parent->_parent = cur;
// 如果parent是根节点就不需要连接,将cur作为根
if (parent == _root) {
_root = cur;
_root->_parent == nullptr;
}
// 将cur作为根,否则就需要找出链接的子树,并完成相互链接
else{
if (parentParent->_left == parent) {
parentParent->_left = cur;
cur->_parent = parentParent;
}
else {
parentParent->_right = cur;
cur->_parent = parentParent;
}
}
}
由于存在空指针的情况,因此需要对节点的子树进行判断
void RotateR(AVLTreeNode* parent) {
AVLTreeNode* cur = parent->_left;
AVLTreeNode* curR = cur->_right;
// 对于parent的parent也要进行连接
AVLTreeNode* parentParent = parent->_parent;
parent->_left = curR;
// curR可能为空,因此需要对其进行空指针的判断
if (curR != nullptr) {
curR->_parent = parent;
}
cur->_right = parent;
parent->_parent = cur;
// 如果parent是根节点就不需要连接,将cur作为根
if (parent == _root) {
_root = cur;
_root->_parent == nullptr;
}
// 将cur作为根,否则就需要找出链接的子树,并完成相互链接
else{
if (parentParent->_left == parent) {
parentParent->_left = cur;
cur->_parent = parentParent;
}
else {
parentParent->_right = cur;
cur->_parent = parentParent;
}
}
}
最后对平衡因子进行相应的修改
cur->_balanceFactor = parent->_balanceFactor = 0;
完整代码
void RotateR(AVLTreeNode* parent) {
AVLTreeNode* cur = parent->_left;
AVLTreeNode* curR = cur->_right;
// 对于parent的parent也要进行连接
AVLTreeNode* parentParent = parent->_parent;
parent->_left = curR;
// curR可能为空,因此需要对其进行空指针的判断
if (curR != nullptr) {
curR->_parent = parent;
}
cur->_right = parent;
parent->_parent = cur;
// 如果parent是根节点就不需要连接,将cur作为根
if (parent == _root) {
_root = cur;
_root->_parent == nullptr;
}
// 将cur作为根,否则就需要找出链接的子树,并完成相互链接
else{
if (parentParent->_left == parent) {
parentParent->_left = cur;
cur->_parent = parentParent;
}
else {
parentParent->_right = cur;
cur->_parent = parentParent;
}
}
cur->_balanceFactor = parent->_balanceFactor = 0;
}
几何特征:当新节点插入较高右子树的右侧时,将其需要将其像左旋转,图示如下:
旋转意义:将高度较高的子树的最大节点上提为根,将原来的根下移为子树的根,使得高度平衡。如此原来高度为H+2的子树降低为H+1,原来H的子树提高为H+1。
实现过程:与右单旋类似,只是方向不同,在此不做过多赘述
void RotateL(AVLTreeNode* parent) {
AVLTreeNode* cur = parent->_right;
AVLTreeNode* curL = cur->_left;
AVLTreeNode* parentParent = parent->_parent;
parent->_right = curL;
if (curL != nullptr) {
curR->_parent = parent;
}
parent->_parent = cur;
cur->_left = parent;
if (parent == _root) {
_root = cur;
_root->_parent = nullptr;
}
else {
if (parentParent->_left == parent) {
parentParent->_left = cur;
cur->_parent = parentParent;
}
else {
parentParent->_right = cur;
cur->_parent = parentParent;
}
}
}
该步的方法与搜索二叉树的插入过程相似,首先找出键值对的键找出插入位置,其中记录parent
和cur
指针,便于在找到后简单的找出与父节点连接的方向以及完成插入节点的parent
指针的连接。
bool Insert(const pair<K, V>& keyValue) {
if (_root == nullptr) {
_root = new RBTreeNode(keyValue);
_root->_colour = BLACK;
return true;
}
RBTreeNode* parent = nullptr;
RBTreeNode* cur = _root;
while (cur) {
if (cur->_KeyValue.first < keyValue.first) {
parent = cur;
cur = cur->_right;
}
else if (cur->_KeyValue.first > keyValue.first) {
parent = cur;
cur = cur->_left;
}
else {
return false;
}
}
cur = new RBTreeNode(keyValue);
cur->_colour = RED;
if (parent->_KeyValue.first < keyValue.first) {
parent->_right = cur;
cur->_parent = parent;
}
else {
parent->_left = cur;
cur->_parent = parent;
}
// 控制平衡
}
对于插入的节点,默认设置为红色。如果设置为黑色可能会对黑色节点的数目造成影响,由于要符合性质4,在修改颜色时会造成麻烦。 默认为红色后,对于颜色与平衡的控制可以分为两大类
直接出入即可,说插入节点为红色节点,不存在破坏红黑树结构的可能,但需要时刻保持根节点为黑色
// 控制平衡
_root->_colour = BLACK;
几何特征:在未插入节点时,父亲为红色,且叔叔也为红色,当插入节点后会出现两种连续的红色,不符合红黑树插入的性质
几何图示:
颜色修改:
- 将父节点与叔叔节点修改为黑色,并将爷爷节点修改为红色
- 由于爷爷进行了颜色修改,可能出现两个连续的红节点,因此是一个父亲节点迭代的过程
- 对于根节点的黑色保持
修改意义:保持黑色节点在不同路径下的数量一致
平衡修改:树认为平衡状态不需要修改
实现过程:
- 判定父亲节点与叔叔节点的颜色与方向
- 进行颜色修改
- 迭代祖先颜色修改过程
- 对于根节点的颜色保持
具体代码:
while (parent && parent->_colour == RED){
RBTreeNode* grathfather = parent->_parent;
if (grathfather->_left == parent) {
RBTreeNode* uncle = grathfather->_right;
// 情况一:父亲节点不为红色且叔叔节点存在且为红
if (uncle && uncle.colour == RED) {
parent->_colour = uncle->_colour = BLACK;
grathfather->_colour = RED;
// 迭代
cur = grathfather;
parent = cur->_parent;
}
else {
}
}
else {
RBTreeNode* uncle = grathfather->_left;
// 情况一:父亲节点不为红色且叔叔节点存在且为红
if (uncle && uncle.colour == RED) {
parent->_colour = uncle->_colour = BLACK;
grathfather->_colour = RED;
// 迭代
cur = grathfather;
parent = cur->_parent;
}
else {
}
}
}
几何特征:在未插入节点时,父亲为红色,且叔叔为黑色,当插入节点后会出现两种连续的红色,不符合红黑树插入的性质。而且当叔叔为黑时或不存在时,当新节点插入,将会使得性质4被破坏,此时的树也并非符合条件的接近平衡。为此需要进行旋转操作。
几何图示:
平衡修改:
当新插入节点与树较高位同侧,采用单旋即可
当新插入节点与树较高位异侧,采用双旋即可
颜色修改:在旋转后,保持黑色节点的数量一致,其中新子树的根为黑色,子节点均为红色
实现过程:
- 判定父亲节点与叔叔节点的颜色与方向,确定插入的方向与高度更高的方向的关系,完成平衡修改方案判断
- 颜色修改
- 结束迭代
具体代码:
while (parent && parent->_colour == RED){
RBTreeNode* grathfather = parent->_parent;
if (grathfather->_left == parent) {
RBTreeNode* uncle = grathfather->_right;
//父亲节点不为红色且叔叔节点存在且为红
if (uncle && uncle.colour == RED) {
parent->_colour = uncle->_colour = BLACK;
grathfather->_colour = RED;
// 迭代
cur = grathfather;
parent = cur->_parent;
}
//父亲节点不为红色且叔叔节点不存在或存在且为黑
else {
// 左左:右单旋
// grathfather
// parent
// cur
if (cur == parent->_left) {
RotateR(grathfather);
parent->_colour = BLACK;
grathfather->_colour = RED;
}
// 左右:左单旋 -- 右单旋
// grathfather
// parent
// cur
else {
RotateL(parent);
RotateR(grathfather);
cur->_colour = BLACK;
grathfather->_colour = RED;
}
break;
}
}
else {
RBTreeNode* uncle = grathfather->_left;
// 情况一:父亲节点不为红色且叔叔节点存在且为红
if (uncle && uncle.colour == RED) {
parent->_colour = uncle->_colour = BLACK;
grathfather->_colour = RED;
// 迭代
cur = grathfather;
parent = cur->_parent;
}
else {
// 右右:左单旋
// grathfather
// parent
// cur
if (cur == parent->_right) {
RotateL(grathfather);
parent->_colour = BLACK;
grathfather->_colour = RED;
}
else {
// 右左:右单旋 -- 左单旋
// grathfather
// parent
// cur
RotateR(parent);
RotateL(grathfather);
cur->_colour = BLACK;
grathfather->_colour = RED;
}
break;
}
}
}
红黑树的检测分为两步:
通过中序遍历来完成对二叉搜索树的检查
public:
void InOrder(){
_InOrder(_root);
}
private:
void _InOrder(RBTreeNode* root){
if (root == NULL)
return;
_InOrder(root->_left);
cout << root->_KeyValue.first << ":" << root->_KeyValue.second << endl;
_InOrder(root->_right);
}
主要步骤为:
验证根节点的颜色为黑色
if (_root && _root->_colour == RED){
cout << "根节点不是黑色" << endl;
return false;
}
验证没有连续的红节点
if (root->_colour == RED && root->_parent->_colour == RED){
cout << "出现连续红色节点" << endl;
return false;
}
验证简单路径下均包含相同数目的黑色结点 :以最左路径作为参考值,当每条路径的黑色节点不等于该基准值时,就违反了红黑树的性质
bool IsBalance(){
// 最左路径黑色节点数量做基准值
int banchmark = 0;
RBTreeNode* left = _root;
while (left){
if (left->_colour == BLACK)
++banchmark;
left = left->_left;
}
int blackNum = 0;
return _IsBalance(_root, banchmark, blackNum);
}
bool _IsBalance(RBTreeNode* root, int banchmark, int blackNum){
if (root == nullptr){
if (banchmark != blackNum){
cout << "存在路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_colour == BLACK){
++blackNum;
}
return _IsBalance(root->_left, banchmark, blackNum)
&& _IsBalance(root->_right, banchmark, blackNum);
}
bool IsBalance(){
if (_root && _root->_colour == RED){
cout << "根节点不是黑色" << endl;
return false;
}
// 最左路径黑色节点数量做基准值
int banchmark = 0;
RBTreeNode* left = _root;
while (left){
if (left->_colour == BLACK)
++banchmark;
left = left->_left;
}
int blackNum = 0;
return _IsBalance(_root, banchmark, blackNum);
}
bool _IsBalance(RBTreeNode* root, int banchmark, int blackNum){
if (root == nullptr){
if (banchmark != blackNum){
cout << "存在路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_colour == RED && root->_parent->_colour == RED){
cout << "出现连续红色节点" << endl;
return false;
}
if (root->_colour == BLACK){
++blackNum;
}
return _IsBalance(root->_left, banchmark, blackNum)
&& _IsBalance(root->_right, banchmark, blackNum);
}
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
对于AVL树来说,如果高度为h,则节点数量为N = 2^h - 1
,高度接近于logN。
对于红黑树来说,虽然左右没有那么均衡,但是整体高度也是可以推断的较为平衡的。假设一条路径下黑色节点数量为X,根据红黑树的性质,假设高度为h,满足于 X ≤ h ≤ 2X
,节点数量满足于2^X - 1 ≤ N ≤ 2^2X - 1
,分别对应全黑二叉树与一黑一红二叉树。对应的整体高度为2*logN
。但是降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优。
#pragma once
#include
using namespace std;
enum Colour{
RED,
BLACK
};
template<class K, class V>
class RBTreeNode {
public:
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _KeyValue;
Colour _colour;
RBTreeNode(const pair<K, V>& KeyValue)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _colour(RED)
, _KeyValue(KeyValue)
{}
};
template<class K,class V>
class RBTree {
typedef RBTreeNode<K, V> RBTreeNode;
private:
RBTreeNode* _root;
public:
RBTree()
:_root(nullptr)
{}
bool Insert(const pair<K, V>& keyValue) {
// 空树
if (_root == nullptr) {
_root = new RBTreeNode(keyValue);
_root->_colour = BLACK;
return true;
}
// 按搜索树规则查找
RBTreeNode* parent = nullptr;
RBTreeNode* cur = _root;
while (cur) {
if (cur->_KeyValue.first < keyValue.first) {
parent = cur;
cur = cur->_right;
}
else if (cur->_KeyValue.first > keyValue.first) {
parent = cur;
cur = cur->_left;
}
else {
return false;
}
}
// 插入
cur = new RBTreeNode(keyValue);
cur->_colour = RED;
if (parent->_KeyValue.first < keyValue.first) {
parent->_right = cur;
cur->_parent = parent;
}
else {
parent->_left = cur;
cur->_parent = parent;
}
// 控制平衡
while (parent && parent->_colour == RED){
RBTreeNode* grathfather = parent->_parent;
if (grathfather->_left == parent) {
RBTreeNode* uncle = grathfather->_right;
//父亲节点不为红色且叔叔节点存在且为红
if (uncle && uncle->_colour == RED) {
parent->_colour = uncle->_colour = BLACK;
grathfather->_colour = RED;
// 迭代
cur = grathfather;
parent = cur->_parent;
}
//父亲节点不为红色且叔叔节点不存在或存在且为黑
else {
// 左左:右单旋
// grathfather
// parent
// cur
if (cur == parent->_left) {
RotateR(grathfather);
parent->_colour = BLACK;
grathfather->_colour = RED;
}
// 左右:左单旋 -- 右单旋
// grathfather
// parent
// cur
else {
RotateL(parent);
RotateR(grathfather);
cur->_colour = BLACK;
grathfather->_colour = RED;
}
break;
}
}
else {
RBTreeNode* uncle = grathfather->_left;
// 情况一:父亲节点不为红色且叔叔节点存在且为红
if (uncle && uncle->_colour == RED) {
parent->_colour = uncle->_colour = BLACK;
grathfather->_colour = RED;
// 迭代
cur = grathfather;
parent = cur->_parent;
}
else {
// 右右:左单旋
// grathfather
// parent
// cur
if (cur == parent->_right) {
RotateL(grathfather);
parent->_colour = BLACK;
grathfather->_colour = RED;
}
else {
// 右左:右单旋 -- 左单旋
// grathfather
// parent
// cur
RotateR(parent);
RotateL(grathfather);
cur->_colour = BLACK;
grathfather->_colour = RED;
}
break;
}
}
}
_root->_colour = BLACK;
}
void InOrder(){
_InOrder(_root);
}
bool IsBalance(){
if (_root && _root->_colour == RED){
cout << "根节点不是黑色" << endl;
return false;
}
// 最左路径黑色节点数量做基准值
int banchmark = 0;
RBTreeNode* left = _root;
while (left){
if (left->_colour == BLACK)
++banchmark;
left = left->_left;
}
int blackNum = 0;
return _IsBalance(_root, banchmark, blackNum);
}
private:
void RotateR(RBTreeNode* parent) {
RBTreeNode* cur = parent->_left;
RBTreeNode* curR = cur->_right;
// 对于parent的parent也要进行连接
RBTreeNode* parentParent = parent->_parent;
parent->_left = curR;
// curR可能为空,因此需要对其进行空指针的判断
if (curR != nullptr) {
curR->_parent = parent;
}
cur->_right = parent;
parent->_parent = cur;
// 如果parent是根节点就不需要连接,将cur作为根
if (parent == _root) {
_root = cur;
_root->_parent == nullptr;
}
// 将cur作为根,否则就需要找出链接的子树,并完成相互链接
else {
if (parentParent->_left == parent) {
parentParent->_left = cur;
cur->_parent = parentParent;
}
else {
parentParent->_right = cur;
cur->_parent = parentParent;
}
}
}
void RotateL(RBTreeNode* parent) {
RBTreeNode* cur = parent->_right;
RBTreeNode* curL = cur->_left;
RBTreeNode* parentParent = parent->_parent;
parent->_right = curL;
if (curL != nullptr) {
curL->_parent = parent;
}
parent->_parent = cur;
cur->_left = parent;
if (parent == _root) {
_root = cur;
_root->_parent = nullptr;
}
else {
if (parentParent->_left == parent) {
parentParent->_left = cur;
cur->_parent = parentParent;
}
else {
parentParent->_right = cur;
cur->_parent = parentParent;
}
}
}
void _InOrder(RBTreeNode* root){
if (root == NULL)
return;
_InOrder(root->_left);
cout << root->_KeyValue.first << ":" << root->_KeyValue.second << endl;
_InOrder(root->_right);
}
bool _IsBalance(RBTreeNode* root, int banchmark, int blackNum){
if (root == nullptr){
if (banchmark != blackNum){
cout << "存在路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_colour == RED && root->_parent->_colour == RED){
cout << "出现连续红色节点" << endl;
return false;
}
if (root->_colour == BLACK){
++blackNum;
}
return _IsBalance(root->_left, banchmark, blackNum)
&& _IsBalance(root->_right, banchmark, blackNum);
}
};
补充:
- 代码将会放到:C++/C/数据结构代码链接 ,欢迎查看!
- 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!