(本文主要参考与网络上一些文章加上自己的理解,代码来自JULY http://blog.csdn.net/v_JULY_v/article/details/6169600 )
在真正理解红黑树之前,我觉得它是一座难以翻越的大山;当我有点理解了它,如释重负。
看红黑树之前,我看了AVL树,觉得还是比较好理解,对于红黑树,我曾经有一些疑问:
1.为什么要用红黑来标记?
2.红黑结点如何做到平衡?
3.相对于AVL树,红黑树的优势、意义是什么呢?
在逐步学习的过程中,我大概理解了上述疑问。
首先,红黑树必须满足下面几条性质:
1. 每个结点要么是红的要么是黑的。
2. 根结点是黑的。
3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
4. 如果一个结点是红的,那么它的两个儿子都是黑的(没有连续的2个红色结点) 。
5. 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
对于上述1,2,3属于基本规则,遵守就行了;
4,5相辅相成,是保证平衡的关键,确保了左右子树高度相差最大为"2倍"(多的那1倍都是红色结点)。
相对于AVL树,AVL树左右子树高度相差最大为"1".
关于插入
每次插入的都是红色结点。(当父结点为黑色时,不做任何操作,减少了旋转与涂色操作)
插入过程:
1.插入(必须满足BST)
2.判断是否满足红黑树性质
3.不满足,旋转,改变结点颜色
(或先改变结点颜色,再旋转,详见代码)
4.再判断是否满足性质
5.再旋转、改变结点颜色
旋转的过程与AVL是一样的,参考前一篇文章《AVL树》
关于插入改变结点颜色可以总结为下面几种情况:
当插入一个(子)结点时(插入结点为红色),
1.插入的是根结点,直接涂黑色;
2.父结点为黑色,不做任何改变;
3.父结点为红色,叔结点为红:
父结点涂黑色: uncle->color = BLACK;
叔结点涂黑色: parent->color = BLACK;
祖结点涂红色: gparent->color = RED;
(如果祖结点为根结点,再改为黑色:root->color = BLACK;)
4.父结点为红色,叔结点为黑(或NULL,NIL)
在(变换)为 根-左-左 或 根-右-右 情况下:
父结点(未来的根节点)涂黑色: parent->color = BLACK;
祖结点(未来的叔节点)涂红色:gparent->color = RED;
再右旋或左旋
关于删除
删除相对很繁琐,我的思路很简单:左侧少了一个黑结点,右侧必须“想方设法”消除一个。
删除先按照BST的方法删除结点,大概可归纳为:
1.删除结点为红色结点,直接删除,不会影响红黑树性质
2.删除结点为黑色结点(分三种情况):
1.没有子结点
以删除结点的叶子(NULL, 黑色)作为补充结点,进入: static rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root)
此时参数中: node 为删除结点的叶子(NULL, 黑色)
parent为删除结点的父结点
兄弟结点other = parent->right;
2.有一个子结点
以删除结点的子结点作为补充结点,进入: static rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root)
此时的实参为:
rb_erase_rebalance(child, parent, root);
对应的形参:node是曾经被删除结点的子结点,现在的“基准”结点,取代之前的“删除”结点
parent是曾经被删除结点的父结点,也是现在node的父结点
3.有2个子结点
将删除结点用其用右子树最小结点值取代(或用左子树最大结点值取代),
在真正理解红黑树之前,我觉得它是一座难以翻越的大山;当我有点理解了它,如释重负。
看红黑树之前,我看了AVL树,觉得还是比较好理解,对于红黑树,我曾经有一些疑问:
1.为什么要用红黑来标记?
2.红黑结点如何做到平衡?
3.相对于AVL树,红黑树的优势、意义是什么呢?
在逐步学习的过程中,我大概理解了上述疑问。
首先,红黑树必须满足下面几条性质:
1. 每个结点要么是红的要么是黑的。
2. 根结点是黑的。
3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
4. 如果一个结点是红的,那么它的两个儿子都是黑的(没有连续的2个红色结点) 。
5. 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
对于上述1,2,3属于基本规则,遵守就行了;
4,5相辅相成,是保证平衡的关键,确保了左右子树高度相差最大为"2倍"(多的那1倍都是红色结点)。
相对于AVL树,AVL树左右子树高度相差最大为"1".
关于插入
每次插入的都是红色结点。(当父结点为黑色时,不做任何操作,减少了旋转与涂色操作)
插入过程:
1.插入(必须满足BST)
2.判断是否满足红黑树性质
3.不满足,旋转,改变结点颜色
(或先改变结点颜色,再旋转,详见代码)
4.再判断是否满足性质
5.再旋转、改变结点颜色
旋转的过程与AVL是一样的,参考前一篇文章《AVL树》
关于插入改变结点颜色可以总结为下面几种情况:
当插入一个(子)结点时(插入结点为红色),
1.插入的是根结点,直接涂黑色;
2.父结点为黑色,不做任何改变;
3.父结点为红色,叔结点为红:
父结点涂黑色: uncle->color = BLACK;
叔结点涂黑色: parent->color = BLACK;
祖结点涂红色: gparent->color = RED;
(如果祖结点为根结点,再改为黑色:root->color = BLACK;)
4.父结点为红色,叔结点为黑(或NULL,NIL)
在(变换)为 根-左-左 或 根-右-右 情况下:
父结点(未来的根节点)涂黑色: parent->color = BLACK;
祖结点(未来的叔节点)涂红色:gparent->color = RED;
再右旋或左旋
关于删除
删除相对很繁琐,我的思路很简单:左侧少了一个黑结点,右侧必须“想方设法”消除一个。
删除先按照BST的方法删除结点,大概可归纳为:
1.删除结点为红色结点,直接删除,不会影响红黑树性质
2.删除结点为黑色结点(分三种情况):
1.没有子结点
以删除结点的叶子(NULL, 黑色)作为补充结点,进入: static rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root)
此时参数中: node 为删除结点的叶子(NULL, 黑色)
parent为删除结点的父结点
兄弟结点other = parent->right;
2.有一个子结点
以删除结点的子结点作为补充结点,进入: static rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root)
此时的实参为:
rb_erase_rebalance(child, parent, root);
对应的形参:node是曾经被删除结点的子结点,现在的“基准”结点,取代之前的“删除”结点
parent是曾经被删除结点的父结点,也是现在node的父结点
3.有2个子结点
将删除结点用其用右子树最小结点值取代(或用左子树最大结点值取代),