数据结构——红黑树
Posted 袁百万
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构——红黑树相关的知识,希望对你有一定的参考价值。
目录
概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是RED或BLACK。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
结点的定义
//给出枚举变量,以便于方便区分红黑结点
enum Colour
RED,
BLACK,
;
template<class K, class V>
struct RBTreeNode
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
;
注意:这里待插入结点的默认颜色给的也有说法,如果你插入的是一个黑色结点,百分之百会违反性质4,但是如果我们插入的是红结点则可能违反性质3。
所以我们把结点的默认颜色给为红色,以便于我们做更少的调整。
插入
其实插入跟我们前面学习的二叉搜索树的插入方法是一样的,这里我就不多做阐述。而我们主要关注的是红黑树的调整。这里的调整不同于AVL树,而是根据红黑树特殊的性质进行调整的。
调整
我们根据叔叔是否存在和颜色来分情况考虑。
结点说明:
p--parent
g--grandfater
u--uncle
当p是g的左孩子时
情况一:如果u存在且为红
1、如果g为根节点,则进行第一步颜色调整之后还需要把g的颜色调整为红色,以保证根节点是黑色的。
2、如果g不为根节点,则颜色调整之后还需要向上继续进行调整。
注意:这种情况无论cur在p的左还是右调整步骤是一样的,因为不涉及到旋转。
情况二:如果u不存在/存在且为黑(直线)
1、这里的关键就是u存在且为黑,我们可以发现g的右子树上多了一个黑结点,那么就不能很好的满足g的左右子树黑结点的数量是一致的这个性质,那么我们推算出该cur结点一定是从情况1转化过来的,也就是这颗树的cur子树是先经过了情况一,才到这一步的。
2、u不存在时直接进行一个形如我们AVL树学习的右单旋,然后进行颜色调整就可以解决。
情况三:如果u不存在/存在且为黑(折线)
1、u存在且为黑(折线)与直线的u存在且为黑一样,由于插入前左右黑结点数量就不平衡,所以肯定是首先经过了情况一。具体步骤就是进行两次旋转+调整颜色。
2、u不存在(折线)由于折线的情况,因此也是经过两次旋转+调整颜色。
当p为g的右孩子时
情况一:如果u存在且为红
由于这一步并没有经过旋转,因此整个调整过程与p为g的左孩子时完全一样。
情况二:如果u不存在/存在且为黑(直线)
由于p相对于g的位置发生了改变此时的直线就是一个左边高。仿照p为g的左孩子。
情况三:如果u不存在/存在且为黑(折线)
只是折线的方向与p为g的左孩子相反罢了,其余一样,仿照p为g的左孩子。
插入完整代码
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 (cur->_kv.first < kv.first)
parent = cur;
cur = cur->_right;
else if (cur->_kv.first > kv.first)
parent = cur;
cur = cur->_left;
else
return false;
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first)
parent->_right = cur;
cur->_parent = parent;
else
parent->_left = cur;
cur->_parent = parent;
while (parent && parent->_col == RED)
Node* grandfater = parent->_parent;
if (grandfater->_left == parent)
Node* uncle = grandfater->_right;
if (uncle && uncle->_col == RED)
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater;
parent = cur->_parent;
else
if (cur == parent->_left)//无论是否有uncle 都这样旋转 //直线
RotateR(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
else
RotateL(parent);
RotateR(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
break;
else
Node* uncle = grandfater->_left;
if (uncle && uncle->_col == RED)
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater;
parent = cur->_parent;
else
if (cur == parent->_right)//无论是否有uncle 都这样旋转 //直线
RotateL(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
else
RotateR(parent);
RotateL(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
break;
_root->_col = BLACK;
return true;
void RotateL(Node* parent)
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)
_root = subR;
_root->_parent = nullptr;
else
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
void RotateR(Node* parent)
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//if (_root == parent)
if (ppNode == nullptr)
_root = subL;
_root->_parent = nullptr;
else
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
红黑树的检测
1、检测每条路径的黑色结点数量是否相同。
2、是否存在连续的两个红结点。
bool Check(Node* root, int blackNum, const int ref)
if (root == nullptr)
//cout << blackNum << endl;
if (blackNum != ref)
cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;
return false;
return true;
if (root->_col == RED && root->_parent->_col == RED)
cout << "违反规则:出现连续红色节点" << endl;
return false;
if (root->_col == BLACK)
++blackNum;
return Check(root->_left, blackNum, ref)
&& Check(root->_right, blackNum, ref);
bool IsBalance()
if (_root == nullptr)
return true;
if (_root->_col != BLACK)
return false;
int ref = 0;
Node* left = _root;
while (left)
if (left->_col == BLACK)
++ref;
left = left->_left;
return Check(_root, 0, ref);
红黑树完整代码(包括测试数据)
//给出枚举变量,以便于方便区分红黑结点
enum Colour
RED,
BLACK,
;
template<class K, class V>
struct RBTreeNode
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
;
template<class K, class V>
class RBTree
typedef RBTreeNode<K, V> Node;
public:
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 (cur->_kv.first < kv.first)
parent = cur;
cur = cur->_right;
else if (cur->_kv.first > kv.first)
parent = cur;
cur = cur->_left;
else
return false;
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first)
parent->_right = cur;
cur->_parent = parent;
else
parent->_left = cur;
cur->_parent = parent;
while (parent && parent->_col == RED)
Node* grandfater = parent->_parent;
if (grandfater->_left == parent)
Node* uncle = grandfater->_right;
if (uncle && uncle->_col == RED)
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater;
parent = cur->_parent;
else
if (cur == parent->_left)//无论是否有uncle 都这样旋转 //直线
RotateR(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
else
RotateL(parent);
RotateR(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
break;
else
Node* uncle = grandfater->_left;
if (uncle && uncle->_col == RED)
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater;
parent = cur->_parent;
else
if (cur == parent->_right)//无论是否有uncle 都这样旋转 //直线
RotateL(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
else
RotateR(parent);
RotateL(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
break;
_root->_col = BLACK;
return true;
void RotateL(Node* parent)
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)
_root = subR;
_root->_parent = nullptr;
else
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
void RotateR(Node* parent)
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//if (_root == parent)
if (ppNode == nullptr)
_root = subL;
_root->_parent = nullptr;
else
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
void Inorder()
_Inorder(_root);
void _Inorder(Node* root)
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_Inorder(root->_right);
bool Check(Node* root, int blackNum, const int ref)
if (root == nullptr)
//cout << blackNum << endl;
if (blackNum != ref)
cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;
return false;
return true;
if (root->_col == RED && root->_parent->_col == RED)
cout << "违反规则:出现连续红色节点" << endl;
return false;
if (root->_col == BLACK)
++blackNum;
return Check(root->_left, blackNum, ref)
&& Check(root->_right, blackNum, ref);
bool IsBalance()
if (_root == nullptr)
return true;
if (_root->_col != BLACK)
return false;
int ref = 0;
Node* left = _root;
while (left)
if (left->_col == BLACK)
++ref;
left = left->_left;
return Check(_root, 0, ref);
private:
Node* _root = nullptr;
;
void test_RBTree1()
//写入几个树进行验证
//int a[] = 8, 3, 1,10,6,4,7,14,13 ;
int a[] = 8, 3, 1,10,6,4 ;
//int a[] = 16, 3, 7, 11;
//int a[] = 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 ;
RBTree<int, int> t;
for (auto e : a)
t.Insert(make_pair(e, e));
if (e == 4)
int i = 0;
t.Inorder();
cout << t.IsBalance();
void test_RBTree2()
srand(time(0));
const size_t N = 100000;
RBTree<int, int> t;
for (size_t i = 0; i < N; i++)
size_t x = rand();
t.Insert(make_pair(x, x));
t.Inorder();
cout << t.IsBalance() << endl;
数据结构 - 红黑树(Red Black Tree)删除详解与实现(Java)
本篇要讲的就是红黑树的删除操作
红黑树插入操作请参考 数据结构 - 红黑树(Red Black Tree)插入详解与实现(Java)
红黑树的删除是红黑树操作中比较麻烦且比较有意思的一部分。
在此之前,重申一遍红黑树的五个定义:
1. 红黑树的节点不是黑色的就是红色的
2. 红黑树的根节点一定是黑色的
3. 红黑树的所有叶子节点都是黑色的(注意:红黑树的叶子节点指Nil节点)
4. 红黑树任何路径上不允许出现相邻两个红色节点
5. 从红黑树的任一节点开始向下到任意叶子节点所经过的黑色节点数目相同
接着,请大家谨记你操作的对象都是一颗标准的红黑树,所以不要脑补过多不能存在的情况,如果你考虑的情况不在本文的讨论范围之内,可以往上看看是不是你的情况违反了五条规则其中某一条,若还有疑问,欢迎留言讨论。
D(Delete)表示待删除节点
P(Parent)表示待删除节点的父节点
S(Sibling)表示待删除节点的兄弟节点
U(Uncle)表示带删除节点的叔叔节点
GP(Grandparent)表示待删除节点的祖父节点
XL(Left child of X)表示节点X的左子树节点
XR(Right child of X)表示节点X的右子树节点
删除一个新的节点有以下四种情况:
1. 删除的节点是叶子节点(非Nil)
2. 删除的节点只有左子树
3. 删除的节点只有右子树
*4. 删除的节点同时拥有左子树和右子树
其实只有上面前三种情况,对于第四种情况,可以找到待删除节点的直接后继节点,用这个节点的值代替待删除节点,接着情况转变为删除这个直接后继节点,情况也变为前三种之一。
因为有很多情况是不存在,待删除节点是叶子节点(非Nil)的情况稍微复杂一些,
我们下面先考虑待删除的节点只有左子树或只有右子树的情况。
不存在的情况包括
①
②
? (san)
④
⑤
⑥
请读者分析一下上面不可能情况的原因,不复杂但一定要知道为什么。两个节点的颜色红黑情况加上左右子树情况,总共八种情况,上面已经排除了六种,剩下以下两种可能的的情况。
①
DL表示DL节点原本的值
②
DR表示DR节点原本的值
这两种情况的调整操作比较简单,直接用DL/DR的元素值代替D的元素,再把DL/DR直接删去就好,操作过后不违反红黑树定义,删除结束。
删除节点的四种情况已经解决了三种,剩下最后一种了。
待删除的节点是叶子节点的情况:
因为待删除的节点有可能是红色也可能是黑色。
如果待删除节点是红色的,那直接删去这个节点,删除结束。
如果待删除节点是黑色的,根据父节点P和兄弟节点S的情况,可分为以下五种情况。
情况1:父节点P是红色节点
或者
这两种情况是一样的,我们讨论第一个图就好,当把D删去后,从P的左子树下来的黑色节点数目少了一,对应的调整做法为,把P染成黑色,此时P左子树的黑色结点数目恢复,但此时右子树黑色结点数目多了一,再把S对应染成红色即可。
图例:
情况2:兄弟节点S是红色节点
或者
只能是这两种情形,做法是把P染成红色,S染成黑色,然后以P为轴做相应的旋转操作(如果D为P的左子树节点则以P为轴做左旋操作,如果D为P的右子树节点则以P为轴做右旋操作)
图例(以第一种情形为例):
到这里就把情况二变成了情况一(父节点为红色)的情况,接着按照情况一的处理方式进行操作。
情况3:结点D的远亲侄子为红色节点的情况
此时父节点P的颜色可红可黑,这种情况的调整做法是,交换P和S的颜色,然后把远侄子节点SR/SL设置为黑色,再以P为轴做相应的旋转操作(如果D为P的左子树则左旋,如果D为P的右子树则右旋)
图例(以第一种情形为例):
调整前后从P点下来的所有路径黑色节点数目没有发生变化,删除节点D后结束。(注意此处S的左子树SL可以为Nil节点或者红色节点,但依然是按照上面的规则进行调整,对结果没有影响)
情况4:节点D的近亲侄子为红色节点的情况
注意此处节点D的远侄子节点必须为Nil节点,否则就变成情况3了。这种情况的调整方式是,把S染成红色,把近侄子节点SR/SL染成黑色,然后以节点S为轴做相应的旋转操作(如果D为P的左子树则以S为轴做右旋操作,如果D为P的右子树则以S为轴做左旋操作)。
图例(以第一种情形为例)
然后就真的变成情况3了......接着按照情况3的处理方式进行处理。
情况5:节点D,P,S均为黑色节点
以第一种情形为例,这种情况删除D之后,从P的左子树下来的黑色节点数目少了一,且没有周围也没有红节点来补全这个黑节点,做法就是把D删去,然后把节点S染成红色,这样一来节点P的左右子树路径的黑色节点路径就一样了,但导致节点P整棵子树的任意路径的黑色节点数比其他路径少了一,此时我们再从P开始(即把P当成D),但不再删除P,向上继续调整,直到根节点(一直是情况5)或者遇到情况1~4并调整后结束。
我看过几篇文章,最后一种情况基本讲到我这里就已经结束了,所以我在这种情况上也因此多话了一点时间去理解。若此处有更详细的例子,会更能帮助理解,所以我决定举两个例子,来说明什么叫从P节点开始向上调整,哪种情况就是要直到根节点, 哪种情况就是遇到情况1~4,然后调整后结束。
从节点P往上依然是全黑的情况(父节点,兄弟节点均为黑色)
从节点P往上是其他情况
这里只是举个例子,无论是变成情况1~4的哪种,经过调整之后都无需再继续上溯,因为此时黑色节点数目已经恢复,且例子里面GP不是根节点,因为根节点不可能为红色。
下面倒序总结一下
待删除的节点是黑色叶子(非Nil)节点的情况
待删除的节点是红色叶子节点的情况
情况6 直接删除该节点
待删除的节点只拥有左子树或只拥有右子树的情况
待删除的节点同时拥有左子树和右子树的情况
情况9 找出直接后继节点并转变为情况1~8
至此,关于红黑树删除的所有情况均讨论完毕,以上的每个字以及每个图都是自己写自己画的,花了不少时间,希望大家多看看,结合图理解比较形象,彻底搞懂红黑树的操作,代码却是次要的,因为同一种思路也有不同的代码风格和实现方式。同时也希望这篇文章能对大家有帮助。
下面是删除的代码:
总的公共方法是这样的,找到该元素对应的节点,然后删除该节点:
public boolean delete(int elem) { if (null == this.root) { return false; } else { TreeNode node = this.root; // find out the node need to be deleted while (null != node) { if (node.getElem() == elem) { deleteNode(node); return true; } else if (node.getElem() > elem) { node = node.getLeft(); } else { node = node.getRight(); } } return false; } }
删除节点的方法为私有方法,包含了同时拥有左右子树,只拥有左子树以及只拥有右子树的操作
private void deleteNode(TreeNode node) { if(null == node.getLeft() && null == node.getRight()) { if (node.getColor() == NodeColor.RED) { delete_red_leaf(node, true); } else { delete_black_leaf(node, true); } } else if (null == node.getLeft()) { // the node color must be black and the right child must be red node // replace the element of node with its right child‘s // cut off the the link between node and its right child node.setElem(node.getRight().getElem()); node.setRight(null); } else if (null == node.getRight()) { node.setElem(node.getLeft().getElem()); node.setLeft(null); } else { // both children are not null TreeNode next = node.getRight(); while (null != next.getLeft()) { next = next.getLeft(); } TreeUtils.swapTreeElem(node, next); deleteNode(next); } }
由大及小,删除的节点是红色叶子节点的情况,注意此处待删除的节点肯定不是根节点,所以不需要考虑该节点为根节点的情况
private void delete_red_leaf(TreeNode node, boolean needDel) { TreeNode parent = node.getParent(); if (node == parent.getLeft()) { parent.setLeft(null); } else { parent.setRight(null); } }
最后就是最麻烦的删除的删除黑色叶子(非Nil)节点的情况,找出兄弟节点,找出远侄子节点,找出近侄子节点。
private void delete_black_leaf(TreeNode node, boolean needDel) { TreeNode parent = node.getParent(); if (null != parent) { boolean nodeInLeft = parent.getLeft() == node; TreeNode sibling = nodeInLeft ? parent.getRight() : parent.getLeft(); TreeNode remoteNephew = null == sibling ? null : (nodeInLeft ? sibling.getRight() : sibling.getLeft()); TreeNode nearNephew = null == sibling ? null : (nodeInLeft ? sibling.getLeft() : sibling.getRight()); if (sibling.getColor() == NodeColor.RED) { delete_sibling_red(node); } else if (null != remoteNephew && remoteNephew.getColor() == NodeColor.RED) { delete_remote_nephew_red(node); } else if (null != nearNephew && remoteNephew.getColor() == NodeColor.RED) { delete_near_nephew_red(node); } else { // the sibling is also a leaf if (parent.getColor() == NodeColor.RED) { delete_parent_red(node); } else { sibling.setColor(NodeColor.RED); delete_black_leaf(parent, false); } } } if (needDel) { if (null == parent) { this.root = null; } else if (node.getParent().getLeft() == node) { parent.setLeft(null); } else { parent.setRight(null); } } }
删除叶子节点包含了另外一个参数 boolean needDel ,因为上面提到的有些情况需要继续上溯,所以有些节点不能被删除。
红黑树所有操作大功告成,希望对大家的学习有所帮助。
PS:请问大家有可以画好看的二叉树的软件推荐吗
请尊重知识产权,引用转载请通知作者!
以上是关于数据结构——红黑树的主要内容,如果未能解决你的问题,请参考以下文章
Java 数据结构 & 算法宁可累死自己, 也要卷死别人 12 红黑树