红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1978年发明,在当时被称为平衡二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。
对于二叉树,如果它是一棵接近平衡的二叉树,它的操作效率(查询,插入,删除)较高,其时间复杂度是O(logN)。但是可能会出现一种极端的情况,那就是插入的数据是有序的(递增或者递减),那么所有的节点都会在根节点的右侧或左侧,此时,二叉搜索树就变为了一个链表,它的操作效率就降低了,时间复杂度为O(N),所以可以认为二叉搜索树的时间复杂度介于O(logN)和O(N)之间,视情况而定。
那么为了应对这种极端情况,红黑树就出现了,它是具备了某些特性的二叉搜索树,能解决非平衡树问题,红黑树是一种接近平衡的二叉树(说它是接近平衡因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的特殊性质来维持一种接近平衡的结构,进而提升整体的性能,并没有严格的卡定某个平衡因子来维持绝对平衡)。
因此,红黑树相对于二叉树而言,其接近平衡的性质保证了它的时间复杂度,同时,它的控制条件只把它调整为接近平衡,因为对其的平衡条件相比于AVL树较为宽松,其旋转次数必然没有AVL树那么多,在一定情况下,发生的旋转次数必然比AVL树更少,其效率比AVL略微优秀。
首先,红黑树是一个二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是RED,也可以是BLACK;通过任意一条从根到叶子简单路径上颜色的约束,当从根节点到叶子节点的路径上黑色节点相同时,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡。为了达到这个目的,它需要同时满足以下特性:
- 节点是红色或黑色
- 根是黑色
- 叶子节点都是黑色,这里的叶子节点指的是最底层的空节点(外部节点),下图中的那些null节点才是叶子节点,null节点的父节点在红黑树里不将其看作叶子节点。
- 红色节点的子节点都是黑色
1.红色节点的父节点都是黑色
2.从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点- 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点
那么它是如何根据特性保证,最长路径不超过最短路径的两倍的呢?
首先,由定义我们可以得知:
这个树的最短路径上的节点一定全是黑色的,假设其节点数为n。
证明如下:其实也没啥证明的,五个特性对黑色节点没有硬性要求,那么我们的最短路径就是纯黑色节点组成的。
这个树的最长路径一定是n个黑色和n个红色节点组成的。
证明如下:
1.从第五特性得知,红黑树的所有路径都包含相同数目的黑色节点,那么它就有n个黑色节点
2.如果他要是最长的路径,那么其它节点就为红色,但红色节点又不能连续存在,最多是一个红色节点和一个黑色节点交替存在。
3.同时我们要求最长路径,那么就是红色节点和黑色节点交替存在的情况,其红色节点数目和黑色节点数目便成了1:1的关系,因此我们便有n个红色节点。
示例如下:
根据其红黑树的特性我们可知,每个节点我们都要有一个数据来表示颜色,因为这里只有红黑两种颜色,我建议用一个枚举类型来定义。
enum Color
{
Red,
Black
};
//直接实现kv模型的红黑树
template
class RBTreeNode
{
public:
RBTreeNode(const pair& data)
:_co(Red)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_data(data)
{}
Color _co;
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
pair _data;
};
我们都知道,旋转是一个二叉树的核心操作,这里我们直接提前写一下。
void _RotatoR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
parent->_left = SubLR;
if (SubLR) SubLR->_parent = parent;
Node* pparent = parent->_parent;
SubL->_right = parent;
parent->_parent = SubL;
if (!pparent)
{
_root = SubL;
SubL->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = SubL;
}
else
{
pparent->_right = SubL;
}
SubL->_parent = pparent;
}
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* pparent = parent->_parent;
subR->_left = parent; //将subR的左指针指向parent
parent->_parent = subR;//将parent的父指针指向subR
if (!pparent ) //判断parent是否是头节点
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = ppnode;
}
}
红黑树的插入过程和二叉查找树插入过程基本类似,不同的地方在于,红黑树插入新节点后,需要进行调整,以满足红黑树的性质。
性质1规定红黑树节点的颜色要么是红色要么是黑色,那么在插入新节点时,这个节点应该是红色还是黑色呢?
答案是红色。
原因也不难理解。如果插入的节点是黑色,那么就必定出现 有一条路径的黑色节点数目变多了,那么这个该如何调整呢?显然是比较困难。
那么我们如果插入红色节点呢,显然只影响性质4,只可能出现连续两个红色节点的情况,这个我们就需要引入变色和旋转来解决,但总归是比插入黑色好解决的多。
红黑树的插入,大部分情况都是看叔叔节点。
插入时满足性质4,即不破坏红黑树的任何性质
假设我们插入时,其父亲节点为黑色,那么我们就不用做调整,直接跳过。
插入时不满足性质4时,我们一共有八种情况,其分为三类。
其形式如下:
(p为parent节点,g为grand节点,c为cur节点,u为uncle节点)
在这种情况下,我们不需要旋转,只需要变色就可以解决,我们将
将p节点和u节点的颜色都改为黑色,然后将g节点的颜色改为红色,我们发现,其改变后这一部分不在违反红黑树的任何特性,但是其g之上的部分可能被影响(因为改变了g的颜色),所以说这种情况我们应该放在循环里面,我们将g赋值给p,将g->p赋值给p,然后循环在g上面的路径寻找破坏特性的部分。
代码如下:
//开始判断是否需要变色
while (parent && parent->_co == Red)
{
Node* grand = parent->_parent;
if (parent == grand->_left)
{
Node* uncle = grand->_right;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
else
{
if(cur=)
}
}
else
{
Node* uncle = grand->_left;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
}
}
这种情况和以下的各种情况都不是单纯的插入引发的,而是通不断调整引发的。
这种情况下我们只需要做一个简单的单旋就可以解决,因为这种情况的产生肯定是因为parent那条路径的插入所导致的(看上一种情况引发的变色),所以我们可以近似成单纯的左右旋转两种情况:
这玩意是不是非常眼熟,其形式和我们前期学的旋转样例一模一样,所以我们只剩下了一个问题,变色。
经过旋转后,大概变成了这个形状,因此我们可以发现,我们不仅每条路径的黑色节点数目不一样,并且还出现了连续的红色节点。
在此,为了维护红黑树的各种特点,我们将p变为黑色,将g变为红色,如下图表示
因此C节点下面是必定有节点,并且为黑色,所以这样变色就不会违背红黑树的各种特性,因此,我们可以得出如下代码。
if (parent == grand->_left)
{
Node* uncle = grand->_right;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
_RotatoR(grand);
grand->_co = Red;
parent->_co = Black;
}
else
{
}
break;
}
}
else
{
Node* uncle = grand->_left;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
_RotatoL(grand);
grand->_co = Red;
parent->_co = Black;
}
else
{
}
break;
}
}
这种情况下,最直接的引发情况就是,parent节点和cur节点都为红色。
具体图得:
因此,我们可得出这种情况下的完整代码:
bool Insert(const pair& data)
{
if (!_root)
{
_root = new Node(data);
_root->_co = Black;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_data.first < data.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data.first>data.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(data);
cur->_co = Red;
if (parent->_data.first < cur->_data.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//开始判断是否需要变色
while (parent && parent->_co == Red)
{
Node* grand = parent->_parent;
if (parent == grand->_left)
{
Node* uncle = grand->_right;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
_RotateR(grand);
grand->_co = Red;
parent->_co = Black;
}
else
{
_RotateL(parent);
_RotateR(grand);
cur->_co = Black;
grand->_co = Red;
}
break;
}
}
else
{
Node* uncle = grand->_left;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
_RotateL(grand);
grand->_co = Red;
parent->_co = Black;
}
else
{
_RotateR(parent);
_RotateL(grand);
cur->_co = Black;
grand->_co = Red;
}
break;
}
}
}
_root->_co = Black;
return true;
}
红黑树的验证可以从两部分入手,一部分是其本身二叉搜索树的性质,也就是中序遍历出来是一个有序的数组,第二部分是其红黑树本身的五个性质。
第一部分很简单,代码如下:
void Inorder()
{
_Inorder(_root);
}
private:
void _Inorder(Node* root)
{
if (!root)
{
return;
}
_Inorder(root->_left);
cout << root->_data.first <<":" << root->_data.second << endl;
_Inorder(root->_right);
}
第二部分,我们需要逐个排查红黑树的五条性质,在这一部分,我们建议分俩个函数来写,在第一个函数IsBalance中,我们需要排查头结点的颜色,并且我们需要统计出一条路径上的黑色节点数目,我们将黑色节点的数目传入第二个函数Check,让他来排查每一条路径上有没有连续的红色节点,还有每条路径的黑色节点数目是否等于isbalance给出的黑色节点数目。
代码如下:
bool IsBalance()
{
return _IsBalance();
}
private:
bool _IsBalance()
{
if (!_root) return true;
if (_root->_co == Red)
{
cout << "根节点为红色" << endl;
return false;
}
int BlackSum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_co == Black) BlackSum++;
cur = cur->_left;
}
return _Check(_root, 0, BlackSum);
}
bool _Check(Node* root, int Blacknum, int BlackSum)
{
if (!root )
{
if (Blacknum != BlackSum)
{
cout << "某条路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_co == Black)
{
++Blacknum;
}
if (root->_co == Red&& root->_parent&& root->_parent->_co ==Red)
{
cout << "存在连续的红色节点" << endl;
return false;
}
return _Check(root->_left, Blacknum, BlackSum)&& _Check(root->_right, Blacknum, BlackSum);
}
红黑树的查找与二叉搜索树的查找一样,逻辑如下:
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_data.first < key)
{
cur = cur->_right;
}
else if (cur->_data.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
完整代码如下:
#pragma once
#include
using namespace std;
namespace My
{
enum Color
{
Red,
Black
};
//直接实现kv模型的红黑树
template
class RBTreeNode
{
public:
RBTreeNode(const pair& data)
:_co(Red)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
{}
Color _co;
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
pair _data;
};
template
class RBTree
{
public:
typedef RBTreeNode Node;
bool Insert(const pair& data)
{
if (!_root)
{
_root = new Node(data);
_root->_co = Black;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_data.first < data.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data.first>data.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(data);
cur->_co = Red;
if (parent->_data.first < cur->_data.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//开始判断是否需要变色
while (parent && parent->_co == Red)
{
Node* grand = parent->_parent;
if (parent == grand->_left)
{
Node* uncle = grand->_right;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
_RotateR(grand);
grand->_co = Red;
parent->_co = Black;
}
else
{
_RotateL(parent);
_RotateR(grand);
cur->_co = Black;
grand->_co = Red;
}
break;
}
}
else
{
Node* uncle = grand->_left;
if (uncle && uncle->_co == Red)
{
//这里只变色就好
parent->_co = uncle->_co = Black;
grand->_co = Red;
cur = grand;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
_RotateL(grand);
grand->_co = Red;
parent->_co = Black;
}
else
{
_RotateR(parent);
_RotateL(grand);
cur->_co = Black;
grand->_co = Red;
}
break;
}
}
}
_root->_co = Black;
return true;
}
void Inorder()
{
_Inorder(_root);
}
bool IsBalance()
{
return _IsBalance();
}
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_data.first < key)
{
cur = cur->_right;
}
else if (cur->_data.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
private:
bool _IsBalance()
{
if (!_root) return true;
if (_root->_co == Red)
{
cout << "根节点为红色" << endl;
return false;
}
int BlackSum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_co == Black) BlackSum++;
cur = cur->_left;
}
return _Check(_root, 0, BlackSum);
}
bool _Check(Node* root, int Blacknum, int BlackSum)
{
if (!root )
{
if (Blacknum != BlackSum)
{
cout << "某条路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_co == Black)
{
++Blacknum;
}
if (root->_co == Red&& root->_parent&& root->_parent->_co ==Red)
{
cout << "存在连续的红色节点" << endl;
return false;
}
return _Check(root->_left, Blacknum, BlackSum)&& _Check(root->_right, Blacknum, BlackSum);
}
void _Inorder(Node* root)
{
if (!root)
{
return;
}
_Inorder(root->_left);
cout << root->_data.first <<":" << root->_data.second << endl;
_Inorder(root->_right);
}
void _RotateR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
parent->_left = SubLR;
if (SubLR) SubLR->_parent = parent;
Node* pparent = parent->_parent;
SubL->_right = parent;
parent->_parent = SubL;
if (_root==parent)
{
_root = SubL;
SubL->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = SubL;
}
else
{
pparent->_right = SubL;
}
SubL->_parent = pparent;
}
}
void _RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
subR->_left = parent; //将subR的左指针指向parent
Node* pparent = parent->_parent;
parent->_parent = subR;//将parent的父指针指向subR
if (subRL)
subRL->_parent = parent;
if (_root == parent) //判断parent是否是头节点
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
}
Node* _root = nullptr;
};
}