[数据结构] 红黑树的详解
Posted 哦哦呵呵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[数据结构] 红黑树的详解相关的知识,希望对你有一定的参考价值。
1. 红黑树概念
红黑树,本质是一颗二叉搜索树 + 节点颜色限制(红/黑) + 规则约定(最长路径中的节点个数不超过最短路径中节点个数的2倍)
在二叉搜索树中,每个节点上增加一个存储位表示节点的颜色,可能是红色或黑色。
对于任何一条根到叶子节点路径上各个节点的着色方式的限制,红黑树确保没有没有一条比其它路径长出两倍,因而接近平衡二叉树,或者说近似平衡。
2. 红黑树性质
- 每个节点不是黑色就是红色
- 根节点是黑色
- 如果有一个节点是红色的,则它的两个孩子节点必定是黑色,不可能出现连在一起的红色节点
- 对于每个节点,从该节点到其它所有后代叶节点的路径上,均包含相同数据的黑色节点,每条路径中的黑色节点数量相同
- 每个空指针域都是黑色
为什么满足上述性质,就能保证红黑树最长路径长度不超过最短路径的2倍?
最重要的是要保证每条路径中黑色节点的个数相同,并且红色节点不能连续出现,所以就保证了最长路径不超过最短路径的二倍。
3. 红黑树的调整算法
新插入节点默认位红色,因为在向红黑树中插入新节点时,如果其双亲结点的颜色是黑色的,则不会违反红黑树的性质,如果其双亲是红色的,插入红色结点后,就会违反性质四,需要对红黑树进行调整。如果新插入结点颜色位黑色,那么每次插入新节点都会违反性质四。
3.1 红黑树插入时需要调整的情况
根据上述我们知道,插入默认节点的颜色时红色,当其父节点为黑色时,树不需要进行调整,但当其父节点为红色时需要调整。
当前结点:cur,父节点:p,祖父结点:g,叔叔结点:u
1. 当前结点为红色,父节点为红色,祖父结点为黑色,叔叔结点存在且为红色
此种情况较简单,只需要修改父节点为黑色,叔叔节点为黑色,祖父结点为红色即可。
注意: 如果祖父为树中的一颗子树,则需要注意祖父结点的父节点的状态,如果祖父结点的父节点为红色,那么违反了性质三,还需要向上调整。
2. 当前结点为红色,父节点为红色,祖父结点为黑色,叔叔结点不存在/为黑,cur是p的左孩子
- 如果叔叔结点不存在,则cur一定是新插入结点,如果cur不是新插入结点,则cur和p一定有一个结点的颜色时黑色,则cur和p一定有一个颜色是黑色,已经不满足性质了,所以不会到这一步。
- 如果叔叔几结点存在,则其一定为黑色,并且cur原来的结点一定是黑色的,cur变红的原因是cur的子树在调整的过程中影响了该结点。
这里我们只看一种情况,因为上述两种情况差不了多少。
p是g的左孩子,cur是p的左孩子: 先右单旋,修改p为黑色,g为红色
3. 当前结点为红,父节点为红,祖父结点为黑,叔叔结点不存在/为黑,cur是p的右孩子
先针对p
结点进行左旋,就会变成情况二的样子,之后按照情况二的处理方式处理即可。
注意:还有以上三种情况的逆情况,即树型与上述相仿,就不再赘述,只需要将操作反过来即可,下方代码中会有实现。
4. 模拟一下
序列:1 2 9 4 6 5
5. 代码实现
// 请模拟实现红黑树的插入--注意:为了后序封装map和set,本文在实现时给红黑树多增加了一个头结点
enum Color { RED, BLACK };
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _pLeft;
RBTreeNode<T>* _pRight;
RBTreeNode<T>* _pParent;
T _data;
Color _color;
RBTreeNode(const T& val = T(), Color c = RED)
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(val)
, _color(c)
{}
};
template<class T>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
RBTree()
{
_pHead = new Node;
_pHead->_pLeft = _pHead;
_pHead->_pRight = _pHead;
}
// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
// 注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{
Node* pRoot = GetRoot();
// 如果为空树, 则插入头节点并且为黑色
if (nullptr == pRoot)
{
pRoot = new Node(data, BLACK);
pRoot->_pParent = pHead;
return true;
}
// 非空树
else
{
// 1.查找待插入节点在红黑树中的位置
Node* pCur = pRoot;
Node* pParent = nullptr;
while (pCur)
{
pParent = pCur;
if (data < pCur->_data)
{
pCur = pCur->_pLeft;
}
else if (data > pCur->_data)
{
pCur = pCur->_pRight;
}
else
{
return false;
}
}
// 2. 插入新节点,调整使其满足红黑树的性质
pCur = new Node(data);
if (data < pParent->_data)
{
pParent->_pLeft = pCur;
}
else
{
pParent->_pRight = pCur;
}
pCur->_pParent = pParent;
// 3. 红黑树性质可能遭到破坏
// 插入的新节点默认为红色
// 看pCur双亲是否时黑色,如果是黑色,则没有破坏性质
// 不是黑色,破坏了性质3,需要调整
// 子树调整完之后,可能破坏了上面树的性质,所以还需要向上调整
while (pParent != _pHead && RED == pParent->_color)
{
Node* granderFather = pParent->_pParent;
// 情况分为三种
if (pParent == granderFather->_pLeft)
{
Node* uncle = granderFather->_pRight;
// 情况一: cur为红, pParent为红,grand为黑,uncle存在且为红
// 该情况下,只需要判断叔叔节点,引为其它节点颜色一定满足,因为他们之前是一颗满足条件的红黑树
if (uncle && RED == uncle->_color)
{
// 只需要调整颜色即可
// 祖父节点黑色,父节点与叔叔节点调整为红色
granderFather->_color = RED;
pParent->_color = BLACK;
uncle->_color = BLACK;
// 移动节点位置,继续向上调整
pCur = granderFather;
pParent = pCur->_pParent;
}
else
{
// 情况二 三:cur为红,parent为红,grander为黑,u不存在或者为黑
// 情况二:cur为parent的左子树
// 情况三:cur为parent的右子树-->旋转一下,调整为parent的左子树即可
if (pCur == pParent->_rRight)
{
RotateL(pParent);
std::swap(pParent, pCur);
}
// 调整颜色后旋转 使其满足性质4
pParent->_color = BLACK;
granderFather->_color = RED;
RotateR(granderFather);
}
}
else
{
// 反着的情况一二三
Node* uncle = granderFather->_pLeft;
if (uncle && RED == uncle->_color)
{
// 只需要调整颜色即可
// 祖父节点黑色,父节点与叔叔节点调整为红色
granderFather->_color = RED;
pParent->_color = BLACK;
uncle->_color = BLACK;
// 移动节点位置,继续向上调整
pCur = granderFather;
pParent = pCur->_pParent;
}
else
{
if (pCur == pParent->_rLeft)
{
RotateL(pParent);
std::swap(pParent, pCur);
}
// 调整颜色后旋转 使其满足性质4
pParent->_color = BLACK;
granderFather->_color = RED;
RotateR(granderFather);
}
}
}
}
// 根节点一定要为黑色
pRoot->_color = BLACK;
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
Node* Find(const T& data)
{
Node* pCur = GetRoot();
while (pCur)
{
if (data < pCur->_data)
{
pCur = pCur->_pLeft;
}
else if (data > pCur->_data)
{
pCur = pCur->_pRight;
}
else
{
return pCur;
}
}
return nullptr;
}
// 获取红黑树最左侧节点
Node* LeftMost()
{
Node* pCur = GetRoot();
if (nullptr == pCur)
{
return _pHead;
}
while (pCur->_pLeft)
{
pCur = pCur->_pLeft;
}
return pCur;
}
// 获取红黑树最右侧节点
Node* RightMost()
{
Node* pCur = GetRoot();
if (nullptr == pCur)
{
return _pHead;
}
while (pCur->_pRight)
{
pCur = pCur->_pRight;
}
return pCur;
}
// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
bool IsValidRBTRee()
{
Node* pRoot = GetRoot();
// 空树满足红黑树的性质
if (nullptr == pRoot)
{
return true;
}
if (BLACK != pRoot->_color)
{
cout << "违反性质2:根节点应为黑色" << endl;
return false;
}
// 检测性质4:每条路径的黑色节点应当一致
// 获取一条路径的黑色节点个数
Node* pCur = root;
size_t blackCount = 0;
while (pCur)
{
if (BLACK == pCur->_color)
{
blackCount++;
}
pCur = pCur->_pLeft;
}
// 检测其它路径黑色节点的个数
return _IsValidRBTRee(pRoot, blackCount, 0);
}
private:
bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{
if (nullptr == pRoot)
{
return true;
}
Node* pParent = pRoot->_pParent;
// 顺便检测性质三
if (pParent != _pHead && RED == pParent->_color && RED == pRoot->_color)
{
cout << "违反性质3:出现了连在一起的红色节点" << endl;
return false;
}
if (BLACK == pRoot->_color)
{
pathBlack++;
}
// pRoot已经遍历到了某条路径的末尾
if (nullptr == pRoot->_pLeft && nullptr == pRoot->_pRight)
{
if (pathBlack != blackCount)
{
cout << "违反性质4:每条路径黑色节点个数不相同" << endl;
reutrn false;
}
}
return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) && _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
}
// 左单旋
void RotateL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
pParent->_pRight = subRL;
if (subRL)
{
subRL->_pParent = pParent;
}
subR->_pLeft = pParent;
Node* ppParent = pParent->_pParent;
pParent->_pParent = subR;
subR->_pParent = ppParent;
if (ppParent == _pHead)
{
_pHead->_pParent = subR;
}
else
{
if (pParent == ppParent->_pLeft)
{
ppParent->_pLeft = subR;
}
else
{
ppParent->_pRight = subR;
}
}
}
// 右单旋
void RotateR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
pParent->_pLeft = subRL;
if (subRL)
{
subRL->_pParent = pParent;
}
subR->_pRight = pParent;
Node* ppParent = pParent->_pParent;
pParent->_pParent = subR;
subR->_pParent = ppParent;
if (ppParent == _pHead)
{
_pHead->_pParent = subR;
}
else
{
if (pParent == ppParent->_pRight)
{
ppParent->_pRight = subR;
}
else
{
ppParent->_pLeft = subR;
}
}
}
// 为了操作树简单起见:获取根节点
Node*& GetRoot()
{
return _pHead->_pParent;
}
private:
Node* _pHead;
};
以上是关于[数据结构] 红黑树的详解的主要内容,如果未能解决你的问题,请参考以下文章
[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现
[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现
[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现
[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现