目录
红黑树的概念
二叉树搜索树的应用
红黑树节点的定义
红黑树结构
insert
需调整的多情况的核心思维:
需调整的多情况分类讲解:
情况一:
情况二:
情况三:
总结:
代码实现:
对于红黑树是否建立成功的检查
升序打印
检查是否为红黑树
执行
代码汇总
红黑树的概念
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
二叉树搜索树的应用
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
- 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树。
- 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
比如:英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
- 文单词与其对应的中文<word, chinese>就构成一种键值对;
- 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
- 使用一个结构体pair:
pair的相关文件
红黑树节点的定义
对于红黑树的实现,以K模型做讲解。
// 节点颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
class RBTreeNode {
RBTreeNode<ValueType>* _Right; // 节点的右孩子
RBTreeNode<ValueType>* _Left; // 节点的左孩子
RBTreeNode<ValueType>* _Parent; // 节点的双亲
ValueType _Data; // 节点的值
Color _Color;
// 构造函数
RBTreeNode(const ValueType& data = ValueType(), Color color = RED)
:_Right(nullptr), _Left(nullptr), _Parent(nullptr)
,_Data(data), _Color(color)
{}
};
红黑树结构
首先,由于红黑树符合其最长路径中黑色节点个数不会超过最短路径黑色节点个数的两倍。那么对于一个数据的插入,就会考虑为红色的,因为插入黑色面临黑色节点的个数改变,调整更难,于是考虑简单的红色,因为其不会考虑个数,自会考虑是否由两个红节点相连。
(由于红黑树的模型主要关注的是节点颜色,所以篇博客的图以三角形代表抽象的红黑树结构)
insert
对于insert最友好的情况就是插入节点的父节点就是黑色、插入节点就是根节点,对于节点的父节点就是黑色,且并未干扰到路径的黑色节点个数的同时,也不会有两个红色节点相挨:
插入节点就是根节点:
// 插入的位置是根节点
if (_root == nullptr)
{
_root = new Node(data);
_root->_color = BLACK;
return true;
}
需调整的多情况的核心思维:
- cur(插入)为红,parent为红,grandfather为黑
由于我们插入的颜色为红色,所以主要的调整就是颜色调整,而我们插入的环境是红黑树之下,所以处于颜色的不平衡只会是:插入的节点为红,其父节点为红,父节点的父节点为黑,这个是一定的。
(因为颜色不成立一定是两个红色相挨了,而相挨的只能是插入的与其的父节点,否则原来必定不是红黑树结构)
- insert的多情况由于uncle(父节点的兄弟节点)
我们的插入的节点的干扰到的颜色分布一定是grandfather节点的子树的黑节点个数平衡,而又影响到grandfather节点的parent节点的左右子树,以此向上,一直到根节点,最终就是一节颗树都不符合。所以重点就是左右子树,也就是parent与uncle节点颜色的问题。
而如果uncle为红还好,可以直接与father一起变黑色保持左右黑节点个数的同时解决掉cur(插入)为红,parent为红的情况,随后grandfather节点变为新的插入(具体实现后面细讲)。如此渐渐向上,保持左右子树黑节点个数平衡颜色达标。此外,如果uncle不在呢?uncle为黑色呢,所以,这就是多情况的原因。
需调整的多情况分类讲解:
-
情况一:
-
cur为红,p为红,g为黑,u存在且为红
-
由于a、b、c、d、e的情况有很多很多,所以我们就以简易的具象图结构讲解,原理与复杂结构是一样的:
一. a、c、d、e是NIL
二. a、b、c、d、e是最简单的子树结构
对此由于插入的节点一定是叶子节点,所以不可能由插入在中间的情况,需要分析:
所以,实际上对于此种情况转换过来时的情况为:
随后:
情况一总结:
- 固定情况:
- u存在且为红
- 固定处理:
- p、u变黑、g变红。继续把g当成cur,g不是根继续往上处理,g是根在其变为红色的情况下变为黑色。
-
情况二:
- cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋)
由于u不存在/u存在且为黑可以更加详细的画出:
1. u存在且为黑的情况具像图举例:
cur其实原来是黑色的,是由情况一变化而来的。
处理的核心:
因为红黑树是是一个标准的搜索树,所以一定符合左子树 < 根 < 右子树,那么对于路径长度的变换,是不能通过简单的平移节点所能达成的:
左旋:
根 < 左子树是关键。
// 左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
// 将subR连接到parent的右
parent->_right = subRL;
if (subRL) // 可能subR的左子树不存在
subRL->_parent = parent;
Node* pparent = parent->_parent;
// 将parent连接到subR的左节点上,成为subR的左子树
subR->_left = parent;
parent->_parent = subR;
// 将parent的父节点状态给予subR
if (_root == parent) // parent是根
{
_root = subR;
subR->_parent = nullptr;
}
else // parent不是根
{
if (pparent->_left == parent)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
}
右旋:
根 > 右子树是关键。
// 右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 将subL连接到parent的左
parent->_left = subLR;
if (subLR) // 可能subL的右子树不存在
subLR->_parent = parent;
Node* pparent = parent->_parent;
// 将parent连接到subL的右节点上,成为subL的右子树
subL->_right = parent;
parent->_parent = subL;
// 将parent的父节点状态给予subL
if (_root == parent) // parent是根
{
_root = subL;
subL->_parent = nullptr;
}
else // parent不是根
{
if (pparent->_left == parent)
pparent->_left = subL;
else
pparent->_right = subL;
subL->_parent = pparent;
}
}
对于u不存在的处理方式(右单旋):
以g为轴点右单旋。然后p变黑、g变红。
对于u存在且为黑的处理方式(右单旋):
与u不存在时同理,以g为轴点右单旋。然后p变黑、g变红。
上述是右单旋转的情况,如果p和u的位置换了换,就是左单旋然后p变黑、g变红。
对于u不存在的处理方式(左单旋):
以g为轴点左单旋。然后p变黑、g变红。
对于u存在且为黑的处理方式(左单旋):
与u不存在时同理,以g为轴点左单旋。然后p变黑、g变红。
情况二总结:
- 固定情况:
- u不存在/u存在且为黑
- 固定处理:(单旋变色)
- 单选后,p变黑、g变红
a、 p 为 g 的左孩子,cur 为 p 的左孩子,则进行右单旋转, p变黑、g变红b、 p 为 g 的右孩子,cur 为 p 的右孩子,则进行左单旋转,p变黑、g变红
-
情况三:
- cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋)
由于u不存在/u存在且为黑可以更加详细的画出:
1. u存在且为黑的情况具像图举例:
cur其实原来是黑色的,是由情况一变化而来的。
对于u不存在的处理方式(右单旋):
以p为轴点右单旋。则转换成了情况二。
对于u存在且为黑的处理方式(右单旋):
与u不存在时同理,以p为轴点右单旋。则转换成了情况二。
对于u不存在的处理方式(左单旋):
以p为轴点左单旋。则转换成了情况二。
对于u存在且为黑的处理方式(左单旋):
与u不存在时同理,以p为轴点左单旋。则转换成了情况二。
情况三总结:
- 固定情况:
- u不存在/u存在且为黑
- 固定处理:(单旋变色)
- 单选后,则转换成了情况二
a、 p 为 g 的左孩子, cur 为 p 的右孩子,则针对 p做左单旋转,则转换成了情况二b、 p 为 g 的右孩子, cur 为 p 的左孩子,则针对 p做右单旋转。则转换成了情况 二
总结:
Note:红黑树的关键是叔叔节点
- u存在且为红,变色继续往上处理。
- u不存在或存在且为黑,旋转 + 变色
- 单旋 + 变色
- 双旋 + 变色
代码实现:
核心实现:
对于情况一:问题不大,因为没有左右旋。所以,对于parent处于grandfather左右节点位置,只会是uncle与father,处于位置的变化。
对于情况二、三:uncle与father,处于位置的须注意的同时。又由于需要左右旋,所以,对于parent处于grandfather左右节点位置很重要的,那么就可以以(情况二:单旋+变色 情况三:单旋+情况二)的第一个操作分为一起。
bool insert(const ValueType& data)
{
// 插入的位置是根节点
if (_root == nullptr)
{
_root = new Node(data);
_root->_color = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (data > cur->_data)
{
parent = cur;
cur = cur->_right;
}
else if (data < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(data);
cur->_color = RED;
if (cur->_data > parent->_data)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
while (parent && parent->_color == RED)
{
Node* grandfather = parent->_parent;
assert(grandfather);
assert(grandfather->_color == BLACK);
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:右旋 + p变黑,g变红
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
// g
// p u
// c
else
{
RotateL(parent);
RotateR(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
else
{
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:左旋 + p变黑,g变红
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
// g
// u p
// c
else
{
RotateR(parent);
RotateL(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
}
_root->_color = BLACK;
return true;
}
对于红黑树是否建立成功的检查
升序打印
利用前序递归实现,升序打印,因为this指针没法递归,所以需写一个函数传入根节点。
// 利用递归前序按升序打印红黑树
void InOrder()
{
_InOrder(_root);
cout << endl;
}
// 前序递归
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
检查是否为红黑树
- 是红黑树的重点:
- 颜色分布(无红色节点相连)
- 每条路径的黑色节点个数相同
- 最长路径最多为最短路径的2倍
- 根节点存在即为黑色
对于根节点存在即为黑色、颜色分布(无红色节点相连),第一个:if判断即可,第二个:节点与其的父节点不同时为红色即可。
对于最长路径最多为最短路径的2倍、每条路径的黑色节点个数相同。如果从 " 最长路径最多为最短路径的2倍入手 " ,即以高度入手,是可以保证最长路径最多为最短路径的2倍,但是是没有办法保证颜色的分布,而,如果以 " 每条路径的黑色节点个数相同 " 入手,利用无红色节点相连就可以保证,在每条路径的黑色节点个数相同的前提下,最长路径最多为最短路径的2倍。
// 检测是否符合红黑树
bool IsBalance()
{
if (_root == nullptr)
{
return true;
}
// 判断根节点是否为黑色
if (_root->_color == RED)
{
cout << "根节点不黑色" << endl;
return false;
}
int benchMark = 0; //某路径的黑节点个数,作为基准值
return PrevCheck(_root, 0, benchMark);
}
// 利用深度优先
bool PrevCheck(Node* root, int blackNum, int& benchMark) //利用引用保存基准值
{
if (root == nullptr)
{
if (benchMark == 0) //将第一个路径的黑节点个数给benchMark,作为基准值
benchMark = blackNum;
else if (blackNum != benchMark)
return false;
return true;
}
if (root->_color == BLACK)
++blackNum;
// 检查是否有连续的红色节点
if (root->_color == RED && root->_parent->_color == RED)
{
cout << "存在连续的红节点" << endl;
return false;
}
return PrevCheck(root->_left, blackNum, benchMark)
&& PrevCheck(root->_right, blackNum, benchMark);
}
执行
void TestRBTree()
{
size_t N = 100;
srand(time(0));
RBTree<int> t;
for (size_t i = 0; i < N; ++i)
{
int x = rand();
t.insert(x);
}
t.InOrder();
if (t.IsBalance())
cout << "是红黑树" << endl;
else
cout << "不是红黑树" << endl;
}
int main()
{
TestRBTree();
return 0;
}
代码汇总
#include<iostream>
#include<assert.h>
using namespace std;
// 节点颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode {
RBTreeNode<ValueType>* _right; // 节点的右孩子
RBTreeNode<ValueType>* _left; // 节点的左孩子
RBTreeNode<ValueType>* _parent; // 节点的双亲
ValueType _data; // 节点的值
Color _color;
// 构造函数
RBTreeNode(const ValueType& data = ValueType(), Color color = RED)
:_right(nullptr), _left(nullptr), _parent(nullptr)
,_data(data), _color(color)
{}
};
template<class ValueType>
class RBTree {
typedef RBTreeNode<ValueType> Node;
public:
bool insert(const ValueType& data)
{
// 插入的位置是根节点
if (_root == nullptr)
{
_root = new Node(data);
_root->_color = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
// 查找cur插入的位置
while (cur)
{
if (data > cur->_data)
{
parent = cur;
cur = cur->_right;
}
else if (data < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(data);
cur->_color = RED;
if (cur->_data > parent->_data)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
while (parent && parent->_color == RED)
{
Node* grandfather = parent->_parent;
assert(grandfather);
assert(grandfather->_color == BLACK);
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:右旋 + p变黑,g变红
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
// g
// p u
// c
else
{
RotateL(parent);
RotateR(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
else
{
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:左旋 + p变黑,g变红
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
// g
// u p
// c
else
{
RotateR(parent);
RotateL(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
}
_root->_color = BLACK;
return true;
}
// 利用递归前序按升序打印红黑树
void InOrder()
{
_InOrder(_root);
cout << endl;
}
// 检测是否符合红黑树
bool IsBalance()
{
if (_root == nullptr)
{
return true;
}
// 判断根节点是否为黑色
if (_root->_color == RED)
{
cout << "根节点不黑色" << endl;
return false;
}
int benchMark = 0; //某路径的黑节点个数,作为基准值
return PrevCheck(_root, 0, benchMark);
}
private:
// 利用深度优先
bool PrevCheck(Node* root, int blackNum, int& benchMark) //利用引用保存基准值
{
if (root == nullptr)
{
if (benchMark == 0) //将第一个路径的黑节点个数给benchMark,作为基准值
benchMark = blackNum;
else if(blackNum != benchMark)
return false;
return true;
}
if (root->_color == BLACK)
++blackNum;
// 检查是否有连续的红色节点
if (root->_color == RED && root->_parent->_color == RED)
{
cout << "存在连续的红节点" << endl;
return false;
}
return PrevCheck(root->_left, blackNum, benchMark)
&& PrevCheck(root->_right, blackNum, benchMark);
}
// 前序递归
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
// 右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 将subL连接到parent的左
parent->_left = subLR;
if (subLR) // 可能subL的右子树不存在
subLR->_parent = parent;
Node* pparent = parent->_parent;
// 将parent连接到subL的右节点上,成为subL的右子树
subL->_right = parent;
parent->_parent = subL;
// 将parent的父节点状态给予subL
if (_root == parent) // parent是根
{
_root = subL;
subL->_parent = nullptr;
}
else // parent不是根
{
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;
// 将subR连接到parent的右
parent->_right = subRL;
if (subRL) // 可能subR的左子树不存在
subRL->_parent = parent;
Node* pparent = parent->_parent;
// 将parent连接到subR的左节点上,成为subR的左子树
subR->_left = parent;
parent->_parent = subR;
// 将parent的父节点状态给予subR
if (_root == parent) // parent是根
{
_root = subR;
subR->_parent = nullptr;
}
else // parent不是根
{
if (pparent->_left == parent)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
}
Node* _root = nullptr;
};
void TestRBTree()
{
size_t N = 100;
srand(time(0));
RBTree<int> t;
for (size_t i = 0; i < N; ++i)
{
int x = rand();
t.insert(x);
}
t.InOrder();
if (t.IsBalance())
cout << "是红黑树" << endl;
else
cout << "不是红黑树" << endl;
}
int main()
{
TestRBTree();
return 0;
}