红黑树的实现(图文详解)
Posted AllenSquirrel
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树的实现(图文详解)相关的知识,希望对你有一定的参考价值。
红黑树的实现
-
红黑树的定义
红黑树本质上也是一棵二叉搜索树,满足二叉搜索树的基本性质,但二叉搜索树容易形成单边树,导致搜索效率下降,需要进行平衡限制
例如AVL树就通过引平衡因子来实现平衡树AVL树的实现(图文详解)
而红黑树在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩
倍,因而是接近平衡的。
红黑树的性质:
- 根节点必须是黑色的
- 红色节点不可连续,黑色节点可连续
- 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
具体如下图所示:
-
红黑树的结构
红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 Parent 指向红黑树的根节点,Left指向红黑树中最小的节点,Right指向红黑树中最大的节点,如下:
// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template<class k,class v>
struct RBTreeNode
{
RBTreeNode(const pair<k,v>& data = pair<k,v>())//默认红色
: _left(nullptr), _right(nullptr), _parent(nullptr)
, _data(data), _color(RED)
{}
RBTreeNode<k,v>* _left; // 节点的左孩子
RBTreeNode<k,v>* _right; // 节点的右孩子
RBTreeNode<k,v>* _parent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
pair<k,v> _data; // 节点的值
Color _color; // 节点的颜色
};
相比较于AVL树,定义了节点的颜色替换AVL树的平衡因子,初始化节点为红色
其主要原因在于:插入新节点默认为红色,如果与其父节点构成连续红色破坏红黑树的性质则进入调整,如果默认插入为黑色节点,由于没有限制黑色节点不可连续,无法继续向上调整,导致添加后黑色节点数目无法保证每条路径上不变化
-
红黑树节点插入
红黑树节点插入步骤与AVL树基本一致:
- 搜索
- 创建新节点,插入合适位置
- 调整结构或调整颜色
与AVL树不同的是,AVL根据每个节点平衡因子来决定是否进行结构调整,而红黑树通过节点颜色限制来进行结构和颜色调整
因此,新节点插入后,需要检测红黑树的性质是否被破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
情况1:当前节点为红,父节点为红,父节点在祖父节点左侧,叔叔节点存在且为红色
如果此时pparen祖父节点不为根结点需要继续向上更新,此时将祖父节点作为当前节点
情况2:当前节点为红,父节点为红,父节点在祖父节点左侧,叔叔节点不存在或为黑色
如果cur=parent->left
如果cur=parent->right
情况3:当前节点为红,父节点为红,父节点在祖父节点右侧,叔叔节点存在且为红色
如果此时pparen祖父节点不为根结点需要继续向上更新,此时将祖父节点作为当前节点
情况4:当前节点为红,父节点为红,父节点在祖父节点右侧,叔叔节点不存在或为黑色
如果cur=parent->right
如果cur=parent->left
代码如下:
bool insert(const pair<k, v>& data)
{
//1,搜索树插入
if (_header->_parent == nullptr)//如果为空树
{
Node* root = new Node(data);
_header->_parent = root;
root->_parent = _header;
_header->_left = _header->_right = root;
root->_color = BLACK;
return true;
}
//从根节点开始搜索
Node* cur = _header->_parent;//_header->_parent即为root
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_data.first == data.first)
{
return false;//不能重复
}
else if (cur->_data.first > data.first)
{
cur = cur->_left;
}
else
cur = cur->_right;
}
//创建待插入节点
cur = new Node(data);
if (parent->_data.first > cur->_data.first)//选择插入位置
{
parent->_left=cur;
}
else
parent->_right = cur;
cur->_parent = parent;
//结构调整 颜色调整
//默认插入红色节点 只有可能破坏规则:红色不能连续
//需要向上查是否有红色连续
while (cur != _header->_parent&&cur->_parent->_color == RED)
{
Node* pparent = cur->_parent->_parent;
parent = cur->_parent;
if (pparent->_left == parent)//父节点在祖父节点左边
{
Node* uncle = pparent->_right;
//uncle存在,且为红色
if (uncle&&uncle->_color == RED)
{
uncle->_color = parent->_color = BLACK;
pparent->_color = RED;
//继续向上更新
cur = pparent;
}
else //uncle不存在 或存在为黑色
{
//cout << "进行旋转" << endl;
//判断双旋场景
if (cur == parent->_right) //左边的右边 左旋
{
RotateL(parent);
swap(cur, parent);//交换两个节点指向,退化成左边的左边,即仅右旋场景
}
RotateR(pparent);//左边的左边 右旋
parent->_color = BLACK;
pparent->_color = RED;
break;
}
}
else//父节点在祖父节点右边 pparent->_right == parent
{
Node* uncle = pparent->_left;
//uncle存在,且为红色
if (uncle&&uncle->_color == RED)
{
uncle->_color = parent->_color = BLACK;
pparent->_color = RED;
//继续向上更新
cur = pparent;
}
else
{
//cout << "进行旋转" << endl;
if (cur == parent->_left)//右边的左边
{
RotateR(parent);//先右旋
swap(cur, parent);//交换 cur和parent
}
//无uncle节点 或uncle节点存在且为黑色 仅左旋
RotateL(pparent);//右边的右边 左旋 cur 和parent 均在右侧
parent->_color = BLACK;
pparent->_color = RED;
break;
}
}
}
//直至根结点 根结点必须为黑色
_header->_parent->_color = BLACK;
//更新header左右指向
_header->_left = leftMost();
_header->_right = rightMost();
return true;
}
其中左旋 右旋具体参考AVL树的实现(图文详解)注意到heade最终左右指向需要更新到最左和最右子节点
Node* leftMost()
{
Node* cur = _header->_parent;
while (cur&&cur->_left)
{
cur = cur->_left;
}
return cur;
}
Node* rightMost()
{
Node* cur = _header->_parent;
while (cur&&cur->_right)
{
cur = cur->_right;
}
return cur;
}
-
红黑树的检测
红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2. 检测其是否满足红黑树的性质
//红黑树验证
bool isBalance()
{
if (_header->_parent == nullptr)
return true;
Node* root = _header->_parent;
if (root->_color == RED)//判断根节点是否为黑色
return false;
//统计一条路径上黑色节点数目 作为每条路径黑色节点数参考值
int count = 0;
Node* cur = root;
while (cur)
{
if (cur->_color == BLACK)
count++;
cur = cur->_left;
}
//遍历每一条路径
int curcount = 0;
return travel(root, count, curcount);
}
bool travel(Node* root, int& count, int curcount)
{
if (root == nullptr)
{
if (count != curcount)
return false;
else
return true;
}
//判断节点是否为黑色
if (root->_color == BLACK)
curcount++;
//判断是否红色连续
if (root->_parent&&root->_color == RED && root->_parent->_color == RED)
{
return false;
}
return travel(root->_left, count, curcount) && travel(root->_right, count, curcount);
}
测试如下:
- 根为黑色
- 颜色更改
- 平衡判断
以上是关于红黑树的实现(图文详解)的主要内容,如果未能解决你的问题,请参考以下文章
[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现
[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现