一、红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
二、红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
三、红黑树的分析与实现
3.1 红黑树的整体框架
//节点颜色
enum Colour
{
RED,
BLACK
};
//红黑树的节点
template<class K, class V>
struct RBTreeNode
{
pair<K, V> _kv;//data
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv = pair<K, V>())
:_kv(kv),
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_col(RED)//破坏规则三的成本比破坏规则四要低
{}
};
//红黑树
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root=nullptr;
};
3.2 红黑树的查找
bool find(const K& key) const
{
Node* cur = _root;
while (cur)
{
if (key < cur->_kv.first)
{
cur = cur->_left;
}
else if (key > cur->_kv.first)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
3.3 红黑树的插入逻辑
在正式讲解插入之前,想先来讨论一下究竟是插入红色节点好,还是黑色节点好呢?
答案当然是插入红色节点更简洁一些,不难理解,当插入黑色节点时,我们需要保证规则四成立,也就是确定每一条路径上的黑色节点个数相同,这就意味着每次插入一个黑色节点就要检查一遍每条路径的黑色节点个数;而插入红色节点我们只需要考虑规则三即可,这样我们只需要判断插入节点的父节点是否为红节点,这样就方便了我们的实现。
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
- 按照二叉搜索的树规则插入新节点
- 检测新节点插入后,红黑树的性质是否造到破坏
插入的框架:
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 (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//插入节点
cur = new Node(kv);
cur->_parent = parent;
if (kv.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
//调整红黑树
_root->_col = BLACK;//根节点一定是黑色的
return true;
}
3.4 红黑树的调整
下面这张图片是分析插入后的情况:
对上述分析的过程我们需要将其转化为代码:
//调整颜色
//情况一:parent存在且为黑,不需要调整
//情况二:parent存在且为红,需要调整
while (parent && parent->_col == RED)
{
//确定 g 和 u 节点
Node* grandfather = parent->_parent;
Node* uncle = nullptr;
if (parent == grandfather->_left)
uncle = grandfather->_right;
else
uncle = grandfather->_left;
//在情况二下又有如下情况:
//情况1:unclude存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上调整
cur = grandfather;
parent = cur->_parent;
}
//情况2:uncle不存在或uncle为黑
else if (!uncle || uncle->_col == BLACK)
{
//parent 是 grandfather 的左孩子
if (parent == grandfather->_left)
{
//cur 是 parent 的左孩子
if (cur == parent->_left)
{
//旋转加变色
RotateRight(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//cur 是 parent 的右孩子
else
{
//旋转加变色
RotateLeftThenRight(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
}
//parent 是 grandfather 的右孩子
else
{
//cur 是 parent 的左孩子
if (cur == parent->_left)
{
//旋转加变色
RotateRightThenLeft(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//cur 是 parent 的右孩子
else
{
//旋转加变色
RotateLeft(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
}
break;//调整好能退出了
}
else
{
assert(false);//逻辑错误,理论上不可能走到
}
}
_root->_col = BLACK;
3.5 红黑树的插入
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 (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//插入节点
cur = new Node(kv);
cur->_parent = parent;
if (kv.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
//调整颜色
//情况一:parent存在且为黑,不需要调整
//情况二:parent存在且为红,需要调整
while (parent && parent->_col == RED)
{
//确定 g 和 u 节点
Node* grandfather = parent->_parent;
Node* uncle = nullptr;
if (parent == grandfather->_left)
uncle = grandfather->_right;
else
uncle = grandfather->_left;
//在情况二下又有如下情况:
//情况1:unclude存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上调整
cur = grandfather;
parent = cur->_parent;
}
//情况2:uncle不存在或uncle为黑
else if (!uncle || uncle->_col == BLACK)
{
//parent 是 grandfather 的左孩子
if (parent == grandfather->_left)
{
//cur 是 parent 的左孩子
if (cur == parent->_left)
{
//旋转加变色
RotateRight(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//cur 是 parent 的右孩子
else
{
//旋转加变色
RotateLeftThenRight(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
}
//parent 是 grandfather 的右孩子
else
{
//cur 是 parent 的左孩子
if (cur == parent->_left)
{
//旋转加变色
RotateRightThenLeft(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//cur 是 parent 的右孩子
else
{
//旋转加变色
RotateLeft(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
}
break;//调整好能退出了
}
else
{
assert(false);//逻辑错误,理论上不可能走到
}
}
_root->_col = BLACK;
return true;
}
3.6 红黑树的验证
红黑树的检测分为两步:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
- 检测其是否满足红黑树的性质
bool IsBlance()
{
if (_root == nullptr)
return true;
//根节点必须是黑色
if (_root->_col != BLACK)
{
cout << "违反规则二" << endl;
return false;
}
//每一个节点都存储一个值表示从根节点到该节点黑色节点的个数
int BlackCount = 0;
//找到一条路径上的所有黑色节点作为参照
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
BlackCount++;
cur = cur->_left;
}
//进行比对每一条路径黑色节点个数是否相等
return JudgeNumsOfBlack(_root, 0, BlackCount);
}
bool JudgeNumsOfBlack(Node* root, int CurNum, int BlackNums)
{
if (root == nullptr)
{
//为空时,只有到此节点路径和与比对路径黑色节点个数相等才满足规则四
if (CurNum != BlackNums)
{
cout << "违反规则四" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
CurNum++;
//判断是否满足规则三:没有连续的红色节点
Node* parent = root->_parent;
if (parent && parent->_col == RED && root->_col == RED)
{
cout << "违反规则三" << endl;
return false;
}
return JudgeNumsOfBlack(root->_left, CurNum, BlackNums) &&
JudgeNumsOfBlack(root->_right, CurNum, BlackNums);
}
3.7 红黑树的源码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
//节点颜色
enum Colour
{
RED,
BLACK
};
//红黑树的节点
template<class K, class V>
struct RBTreeNode
{
pair<K, V> _kv;//data
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv = pair<K, V>())
:_kv(kv),
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_col(RED)
{}
};
//红黑树
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
Node* copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newnode = new Node(root->_kv);
newnode->_left = copy(root->_left);
newnode->_right = copy(root->_right);
return newnode;
}
void destory(Node* root)
{
if (root == nullptr)
return;
//必须后续销毁节点
destory(root->_left);
destory(root->_right);
delete root;
root = nullptr;
}
public:
//构造
RBTree() = default;
//拷贝构造
RBTree(const RBTree& avl)
{
_root = copy(avl._root);
}
RBTree(initializer_list<pair<K, V>> il)
{
for (const auto& e : il)
insert(e);
}
//赋值重载
RBTree& operator=(const RBTree& bst)
{
//现代写法
RBTree temp(bst);
std::swap(_root, temp._root);
return *this;
}
// 析构
~RBTree()
{
destory(_root);
_root = nullptr;
}
bool find(const K& key) const
{
Node* cur = _root;
while (cur)
{
if (key < cur->_kv.first)
{
cur = cur->_left;
}
else if (key > cur->_kv.first)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
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 (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//插入节点
cur = new Node(kv);
cur->_parent = parent;
if (kv.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
//调整颜色
//情况一:parent存在且为黑,不需要调整
//情况二:parent存在且为红,需要调整
while (parent && parent->_col == RED)
{
//确定 g 和 u 节点
Node* grandfather = parent->_parent;
Node* uncle = nullptr;
if (parent == grandfather->_left)
uncle = grandfather->_right;
else
uncle = grandfather->_left;
//在情况二下又有如下情况:
//情况1:unclude存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上调整
cur = grandfather;
parent = cur->_parent;
}
//情况2:uncle不存在或uncle为黑
else if (!uncle || uncle->_col == BLACK)
{
//parent 是 grandfather 的左孩子
if (parent == grandfather->_left)
{
//cur 是 parent 的左孩子
if (cur == parent->_left)
{
//旋转加变色
RotateRight(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//cur 是 parent 的右孩子
else
{
//旋转加变色
RotateLeftThenRight(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
}
//parent 是 grandfather 的右孩子
else
{
//cur 是 parent 的左孩子
if (cur == parent->_left)
{
//旋转加变色
RotateRightThenLeft(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//cur 是 parent 的右孩子
else
{
//旋转加变色
RotateLeft(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
}
break;//调整好能退出了
}
else
{
assert(false);//逻辑错误,理论上不可能走到
}
}
_root->_col = BLACK;
return true;
}
//中序查找
void inorder()
{
_inorder(_root);
}
bool IsBlance()
{
if (_root == nullptr)
return true;
//根节点必须是黑色
if (_root->_col != BLACK)
{
cout << "违反规则二" << endl;
return false;
}
//每一个节点都存储一个值表示从根节点到该节点黑色节点的个数
int BlackCount = 0;
//找到一条路径上的所有黑色节点作为参照
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
BlackCount++;
cur = cur->_left;
}
//进行比对每一条路径黑色节点个数是否相等
return JudgeNumsOfBlack(_root, 0, BlackCount);
}
private:
bool JudgeNumsOfBlack(Node* root, int CurNum, int BlackNums)
{
if (root == nullptr)
{
//为空时,只有到此节点路径和与比对路径黑色节点个数相等才满足规则四
if (CurNum != BlackNums)
{
cout << "违反规则四" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
CurNum++;
//判断是否满足规则三:没有连续的红色节点
Node* parent = root->_parent;
if (parent && parent->_col == RED && root->_col == RED)
{
cout << "违反规则三" << endl;
return false;
}
return JudgeNumsOfBlack(root->_left, CurNum, BlackNums) &&
JudgeNumsOfBlack(root->_right, CurNum, BlackNums);
}
void _inorder(Node* root)
{
if (root == nullptr)
return;
_inorder(root->_left);
cout << root->_kv.first << " ";
_inorder(root->_right);
}
void RotateLeft(Node* parent)
{
//保存节点,后面链接
Node* parentparent = parent->_parent;
Node* sub = parent->_right;
Node* subleft = sub->_left;
//重新链接
parent->_right = subleft;
if (subleft)
subleft->_parent = parent;
sub->_left = parent;
parent->_parent = sub;
//和上面的节点链接
if (parentparent == nullptr)
{
//说明parent原来是根节点
_root = sub;
sub->_parent = nullptr;
}
else
{
if (parentparent->_left == parent)
{
//原来是上面节点的左子树
parentparent->_left = sub;
}
else
{
parentparent->_right = sub;
}
sub->_parent = parentparent;
}
}
void RotateRight(Node* parent)
{
//保存节点,后面链接
Node* parentparent = parent->_parent;
Node* sub = parent->_left;
Node* subright = sub->_right;
//重新链接
parent->_left = subright;
if (subright)
subright->_parent = parent;
sub->_right = parent;
parent->_parent = sub;
//和上面的节点链接
if (parentparent == nullptr)
{
//说明parent原来是根节点
_root = sub;
sub->_parent = nullptr;
}
else
{
if (parentparent->_left == parent)
{
//原来是上面节点的左子树
parentparent->_left = sub;
}
else
{
parentparent->_right = sub;
}
sub->_parent = parentparent;
}
}
void RotateLeftThenRight(Node* parent)
{
RotateLeft(parent->_left);
RotateRight(parent);
}
void RotateRightThenLeft(Node* parent)
{
RotateRight(parent->_right);
RotateLeft(parent);
}
Node* _root=nullptr;
};
四、红黑树与AVL树对比
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多
五、红黑树的现实应用
- C++ STL库
- Java 库
- linux内核
- 其他一些库