红黑树的实现

红黑树首先是一棵二叉查找树,它每个结点都被标上了颜色(红色或黑色),红黑树满足以下5个性质:

1、 每个结点的颜色只能是红色或黑色。

2、 根结点是黑色的。

3、 每个叶子结点都带有两个空的黑色结点(被称为黑哨兵),如果一个结点n的只有一个左孩子,那么n的右孩子是一个黑哨兵;如果结点n只有一个右孩子,那么n的左孩子是一个黑哨兵。

4、 如果一个结点是红的,则它的两个儿子都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

5、 对于每个结点来说,从该结点到其子孙叶结点的所有路径上包含相同数目的黑结点。

红黑树的这5个性质中,第3点是比较难理解的,但它却非常有必要。我们看图1中的左边这张图,如果不使用黑哨兵,它完全满足红黑树性质,结点50到两个叶结点8和叶结点82路径上的黑色结点数都为2个。但如果加入黑哨兵后(如图1右图中的小黑圆点),叶结点的个数变为8个黑哨兵,根结点50到这8个叶结点路径上的黑高度就不一样了,所以它并不是一棵红黑树。

要看真正的红黑树请在以上动画中添加几个结点,看看是否满足以上性质。

红黑树的旋转操作

红黑树的旋转操作和AVL树一样,分为LL、RR、LR、RL四种旋转类型,差别在于旋转完成后改变的是结点的颜色,而不是平衡因子。旋转动画演示请参考AVL这篇文章中的Flash动画:

http://www.cnblogs.com/abatei/archive/2008/11/17/1335031.html

红黑树上结点的插入

在讨论红黑树的插入操作之前必须要明白,任何一个即将插入的新结点的初始颜色都为红色。这一点很容易理解,因为插入黑点会增加某条路径上黑结点的数目,从而导致整棵树黑高度的不平衡。但如果新结点父结点为红色时(如图2所示),将会违返红黑树性质:一条路径上不能出现相邻的两个红色结点。这时就需要通过一系列操作来使红黑树保持平衡。

为了清楚地表示插入操作以下在结点中使用“新”字表示一个新插入的结点;使用“父”字表示新插入点的父结点;使用“叔”字表示“父”结点的兄弟结点;使用“祖”字表示“父”结点的父结点。插入操作分为以下几种情况:

1、黑父

如图3所示,如果新点的父结点为黑色结点,那么插入一个红点将不会影响红黑树的平衡,此时插入操作完成。红黑树比AVL树优秀的地方之一在于黑父的情况比较常见,从而使红黑树需要旋转的几率相对AVL树来说会少一些。

2.红父

如果新点的父结点为红色,这时就需要进行一系列操作以保证整棵树红黑性质。如图3所示,由于父结点为红色,此时可以判定,祖父结点必定为黑色。这时需要根据叔父结点的颜色来决定做什么样的操作。青色结点表示颜色未知。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点。

2.1 红叔

当叔父结点为红色时,如图4所示,无需进行旋转操作,只要将父和叔结点变为黑色,将祖父结点变为红色即可。但由于祖父结点的父结点有可能为红色,从而违反红黑树性质。此时必须将祖父结点作为新的判定点继续向上进行平衡操作。

需要注意,无论“父”在“叔”的左边还是右边,无论“新”是“父”的左孩子还是右孩子,它们的操作都完全一样。

2.2 黑叔

当叔父结点为黑色时,需要进行旋转,以下图示了所有的旋转可能

情形1:

情形2:

情形3:

情形4:

可以观察到,当旋转完成后,新的旋转根全部为黑色,此时不需要再向上回溯进行平衡操作,插入操作完成。需要注意,上面四张图的“叔”、“1”、“2”、“3”结点有可能为黑哨兵结点。

其实红黑树的插入操作不是很难,甚至比AVL树的插入操作还更简单些。但删除操作就远远比AVL树复杂得多,下面就介绍红黑树的删除操作。

红黑树上结点的删除

红黑树本身是一棵二叉查找树,它的删除和二叉查找树的删除类似。首先要找到真正的删除点,当被删除结点n存在左右孩子时,真正的删除点应该是n的中序遍在前驱,关于这一点请复习二叉查找树的删除。如图9所示,当删除结点20时,实际被删除的结点应该为18,结点20的数据变为18。

所以可以推断出,在进行删除操作时,真正的删除点必定是只有一个孩子或没有孩子的结点。而根据红黑树的性质可以得出以下两个结论:

1、 删除操作中真正被删除的必定是只有一个红色孩子或没有孩子的结点

2、 如果真正的删除点是一个红色结点,那么它必定是一个叶子结点

理解这两点非常重要,如图10所示,除了情况(a)外,其他任一种况结点N都无法满足红黑树性质。

在以下讨论中,我们使用蓝色箭头表示真正的删除点,它也是旋转操作过程中的第一个判定点;真正的删除点使用“旧”标注,旧点所在位置将被它的的孩子结点所取代(最多只有一个孩子),我们使用“新”表示旧点的孩子结点。删除操作可分为以下几种情形:

1、旧点为红色结点

若旧点为红色结点,则它必定是叶子结点,直接删除即可。如图11所示

2、一红一黑

当旧点为黑色结点,新点为红色结点时,将新点取代旧点位置后,将新点染成黑色即可(如图12所示)。这里需要注意:旧点为红色,新点为黑色的情况不可能存在。

3、双黑

当旧点和新点都为黑色时(新点为空结点时,亦属于这种情况),情况比较复杂,需要根据旧点兄弟结点的颜色来决定进行什么样的操作。我们使用“兄”来表示旧点的兄弟结点。这里可分为红兄和黑兄两种情况:

3.1 红兄

由于兄弟结点为红色,所以父结点必定为黑色,而旧点被删除后,新点取代了它的位置。下图演示了两种可能的情况:

红兄的情况需要进行RR或LL型旋转,然后将父结点染成红色,兄结点染成黑色。然后重新以新点为判定点进行平衡操作。我们可以观察到,旋转操作完成后,判定点没有向上回溯,而是降低了一层,此时变成了黑兄的情况。

3.2 黑兄

黑兄的情况最为复杂,需要根据黑兄孩子结点(这里用“侄”表示)和父亲结点的颜色来决定做什么样的操作。

3.2.1 黑兄二黑侄红父

如图14所示,这种情况比较简单,只需将父结点变为黑色,兄结点变为黑色,新结点变为黑色即可,删除操作到此结束。

3.2.2 黑兄二黑侄黑父

如图15所示,此时将父结点染成新结点的颜色,新结点染成黑色,兄结点染成红色即可。当新结点为红色时,父结点被染成红色,此时需要以父结点为判定点继续向上进行平衡操作。

(2010-3-25 注意:经网友BourneHan的指正,现已确定3.2.1和3.2.2中的新结点应为黑色,而不是现在的不确定颜色。基于以下2点原因,我并不打算在代码及博文中更改这个错误:

1、这个错误对代码及动画的正确性没有影响。

2、之前的代码及动画经过了大量测试,需要花很多时间,更改代码意味着重新测试,现在的确抽不出这么多时间来做这项工作。

这里只能对各位读者说声对不起了,最快的补救方法就是在此点出错误,让读者明了。)

3.2.3 黑兄红侄

黑兄红侄有以下四种情形,下面分别进行图示:

情形1:

情形2:

情形3:

情形4:

由以上图例所示,看完以上四张图的兄弟有可能会有一个疑问,如果情形1和情形2中的两个侄子结点都为红色时,是该进行LL旋转还是进行LR旋转呢?答案是进行LL旋转。情形3和情形4则是优先进行RR旋转的判定。

代码:

using System;
namespace PaintBST
{
    public class RBTree : IBinaryTree //实现画树接口
    {    //成员变量
        private Node _head; //头指针
        private Node[] path = new Node[32]; //记录访问路径上的结点
        private int p; //表示当前访问到的结点在_path上的索引
        INode IBinaryTree.Head //显式接口实现
        {
            get { return (INode)_head; }
        }
        public bool Add(int value) //添加一个元素
        {  //如果是空树,则新结点成为二叉排序树的根
            if (_head == null)
            {
                _head = new Node(value);
                _head.IsRed = false;
                return true;
            }
            p = 0;
            //parent为上一次访问的结点,current为当前访问结点
            Node parent = null, current = _head;
            while (current != null)
            {
                path[p++] = current; //将路径上的结点插入数组
                //如果插入值已存在,则插入失败
                if (current.Data == value)
                {
                    return false;
                }
                parent = current;
                //当插入值小于当前结点,则继续访问左子树,否则访问右子树
                current = (value < parent.Data) ? parent.Left : parent.Right;
            }
            current = new Node(value); //创建新结点
            current.IsRed = true;
            if (value < parent.Data) //如果插入值小于双亲结点的值
            {
                parent.Left = current; //成为左孩子
            }
            else //如果插入值大于双亲结点的值
            {
                parent.Right = current; //成为右孩子
            }
            if (!parent.IsRed)
            {
                return true;
            }
            path[p] = current;
            //回溯并旋转
            while ((p -= 2) >= 0) //现在p指向插入点的祖父结点
            {
                Node grandParent = path[p];
                parent = path[p + 1];
                if (!parent.IsRed)
                {
                    break;
                }
                Node uncle = grandParent.Left == parent ? grandParent.Right : grandParent.Left;
                current = path[p + 2];
                if (IsRed(uncle)) //叔父存在并且为红色的情况
                {
                    parent.IsRed = false;
                    uncle.IsRed = false;
                    if (p > 0) //如果祖父不是根结点,则将其染成红色
                    {
                        grandParent.IsRed = true;
                    }
                }
                else //叔父不存在或为黑的情况需要旋转
                {
                    Node newRoot;
                    if (grandParent.Left == parent) //如果当前结点及父结点同为左孩子或右孩子
                    {
                        newRoot = (parent.Left == current) ? LL(grandParent) : LR(grandParent);
                    }
                    else
                    {
                        newRoot = (parent.Right == current) ? RR(grandParent) : RL(grandParent);
                    }
                    grandParent.IsRed = true; //祖父染成红色
                    newRoot.IsRed = false; //新根染成黑色
                    //将新根同曾祖父连接
                    ReplaceChildOfNodeOrRoot((p > 0) ? path[p - 1] : null, grandParent, newRoot);
                    return true; //旋转后不需要继续回溯,添加成功,直接退出
                }
            }
            return true;
        }
        //旋转根旋转后换新根
        private void ReplaceChildOfNodeOrRoot(Node parent, Node child, Node newChild)
        {
            if (parent != null)
            {
                if (parent.Left == child)
                {
                    parent.Left = newChild;
                }
                else
                {
                    parent.Right = newChild;
                }
            }
            else
            {
                _head = newChild;
            }
        }
        private static bool IsBlack(Node node)
        {
            return ((node != null) && !node.IsRed);
        }

private static bool IsNullOrBlack(Node node)
        {
            if (node != null)
            {
                return !node.IsRed;
            }
            return true;
        }

private static bool IsRed(Node node)
        {
            return ((node != null) && node.IsRed);
        }
        //删除指定值
        public bool Remove(int value)
        {
            p = -1;
            //parent表示双亲结点,node表示当前结点
            Node node = _head;
            //寻找指定值所在的结点
            while (node != null)
            {
                path[++p] = node;
                //如果找到,则调用RemoveNode方法删除结点
                if (value == node.Data)
                {
                    RemoveNode(node);//现在p指向被删除结点
                    return true; //返回true表示删除成功
                }
                if (value < node.Data)
                {   //如果删除值小于当前结点,则向左子树继续寻找
                    node = node.Left;
                }
                else
                {   //如果删除值大于当前结点,则向右子树继续寻找
                    node = node.Right;
                }
            }
            return false; //返回false表示删除失败
        }
        //删除指定结点
        private void RemoveNode(Node node)
        {
            Node tmp = null; //tmp最终指向实际被删除的结点
            //当被删除结点存在左右子树时
            if (node.Left != null && node.Right != null)
            {
                tmp = node.Left; //获取左子树
                path[++p] = tmp;
                while (tmp.Right != null) //获取node的中序遍历前驱结点,并存放于tmp中
                {   //找到左子树中的最右下结点
                    tmp = tmp.Right;
                    path[++p] = tmp;
                }
                //用中序遍历前驱结点的值代替被删除结点的值
                node.Data = tmp.Data;
            }
            else
            {
                tmp = node;
            }
            //当只有左子树或右子树或为叶子结点时
            //首先找到惟一的孩子结点
            Node newTmp = tmp.Left;
            if (newTmp == null) //如果只有右孩子或没孩子
            {
                newTmp = tmp.Right;
            }
            if (p > 0)
            {
                Node parent = path[p - 1];
                if (parent.Left == tmp)
                {   //如果被删结点是左孩子
                    parent.Left = newTmp;
                }
                else
                {   //如果被删结点是右孩子
                    parent.Right = newTmp;
                }
                if (!tmp.IsRed && IsRed(newTmp))
                {
                    newTmp.IsRed = false;
                    return;
                }
            }
            else  //当删除的是根结点时
            {
                _head = newTmp;
                if (_head != null)
                {
                    _head.IsRed = false;
                }
                return;
            }
            path[p] = newTmp;

//如果被删除的是红色结点,则它必定是叶子结点,删除成功,返回
            if (IsRed(tmp))
            {
                return;
            }
            //删除完后进行旋转,现在p指向实际被删除的位置,这个位置可能为空,tmp指向被删除的旧点,
            while (p > 0)
            {   //剩下的是双黑的情况
                //首先找到兄弟结点
                Node current = path[p];
                Node parent = path[p - 1];
                bool currentIsLeft = (parent.Left == current);
                Node sibling = currentIsLeft ? parent.Right : parent.Left;
                //红兄的情况,需要LL旋转或RR旋转
                if (IsRed(sibling))
                {
                    Node newRoot;
                    if (currentIsLeft)
                    {
                        newRoot = RR(parent);
                    }
                    else
                    {
                        newRoot = LL(parent);
                    }
                    ReplaceChildOfNodeOrRoot(p > 1 ? path[p - 2] : null, parent, newRoot);
                    sibling.IsRed = false;
                    parent.IsRed = true;
                    //回溯点降低
                    path[p - 1] = newRoot;
                    path[p] = parent;
                    path[++p] = current;
                }
                else //黑兄的情况
                {
                    //黑兄二黑侄
                    if (IsNullOrBlack(sibling.Left) && IsNullOrBlack(sibling.Right))
                    {  //红父的情况
                        if (parent.IsRed)
                        {
                            parent.IsRed = false;
                            sibling.IsRed = true;
                            if (current != null)
                            {
                                current.IsRed = false;
                            }
                            break; //删除成功
                        }
                        else //黑父的情况
                        {
                            parent.IsRed = IsRed(current);
                            if (current != null)
                            {
                                current.IsRed = false;
                            }
                            sibling.IsRed = true;
                            p--; //需要继续回溯
                        }
                    }
                    else //黑兄红侄
                    {
                        Node newRoot;
                        if (currentIsLeft) //兄弟在右边
                        {
                            if (IsRed(sibling.Right)) //红侄在右边
                            {  //RR型旋转
                                newRoot = RR(parent);
                                sibling.Right.IsRed = false;
                            }
                            else
                            {  //RL型旋转
                                newRoot = RL(parent);
                            }
                        }
                        else //兄弟在左边
                        {
                            if (IsRed(sibling.Left))
                            {  //LL型旋转
                                newRoot = LL(parent);
                                sibling.Left.IsRed = false;
                            }
                            else
                            {  //LR型旋转
                                newRoot = LR(parent);
                            }
                        }
                        if (current != null)
                        {
                            current.IsRed = false;
                        }
                        newRoot.IsRed = parent.IsRed;
                        parent.IsRed = false;
                        ReplaceChildOfNodeOrRoot(p > 1 ? path[p - 2] : null, parent, newRoot);
                        break; //不需要回溯,删除成功
                    }
                }
            }
        }
        //root为旋转根,rootPrev为旋转根双亲结点
        private Node LL(Node root) //LL型旋转,返回旋转后的新子树根
        {
            Node left = root.Left;
            root.Left = left.Right;
            left.Right = root;
            return left;
        }
        private Node LR(Node root) //LR型旋转,返回旋转后的新子树根
        {
            Node left = root.Left;
            Node right = left.Right;
            root.Left = right.Right;
            right.Right = root;
            left.Right = right.Left;
            right.Left = left;
            return right;
        }
        private Node RR(Node root) //RR型旋转,返回旋转后的新子树根
        {
            Node right = root.Right;
            root.Right = right.Left;
            right.Left = root;
            return right;
        }
        private Node RL(Node root) //RL型旋转,返回旋转后的新子树根
        {
            Node right = root.Right;
            Node left = right.Left;
            root.Right = left.Left;
            left.Left = root;
            right.Left = left.Right;
            left.Right = right;
            return left;
        }
    }

}

转载地址:http://www.cnblogs.com/abatei/archive/2008/12/17/1356565.html

时间: 2024-11-06 04:03:23

红黑树的实现的相关文章

B树、B+树、红黑树、AVL树比较

B树是为了提高磁盘或外部存储设备查找效率而产生的一种多路平衡查找树. B+树为B树的变形结构,用于大多数数据库或文件系统的存储而设计. B树相对于红黑树的区别 在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率低下的情况.为什么会出现这样的情况,我们知道要获取磁盘上数据,必须先通过磁盘移动臂移动到数据所在的柱面,然后找到指定盘面,接着旋转盘面找到数据所在的磁道,最后对数据进行读写.磁盘IO代价主要花费在查找所需的柱面上,树的深度过大会造成磁盘IO频繁读

红黑树之删除节点

红黑树之删除节点 上一篇文章中讲了如何向红黑树中添加节点,也顺便创建了一棵红黑树.今天写写怎样从红黑树中删除节点. 相比于添加节点,删除节点要复杂的多.不过我们慢慢梳理,还是能够弄明白的. 回顾一下红黑树的性质 红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色.在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求: 节点是红色或黑色. 根节点是黑色. 每个叶节点(这里的叶节点是指NULL节点,在<算法导论>中这个节点叫哨兵节点,除了颜色属性外,其他属性值都为任

数据结构与算法-红黑树

前言 红黑树是工程中最常用到的一种自平衡二叉排序树,其和AVL树类似,都是在进行插入.删除时通过一定的调整操作来维持相对稳定的树高,从而获得较好的查询性能. 性质 1. 节点是红色或黑色. 2. 根节点是黑色. 3 每个叶节点(null节点)是黑色的. 4 每个红色节点的两个子节点都是黑色.(从每个叶子到根的所有路径上不能有两个连续的红色节点) 5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点. 维护红黑树形状(树高)的,主要就是4.5两条性质,性质4决定了最长的路径莫过于红黑间隔

数据结构学习笔记-排序/队/栈/链/堆/查找树/红黑树

排序: 插入排序:每次从剩余数据中选取一个最小的,插入已经排序完成的序列中 合并排序:将数据分成左右两组分别排序,然后合并,对每组数据的排序递归处理. 冒泡排序:重复交换两个相邻元素,从a[1]开始向a[0]方向冒泡,然后a[2]...当a[i]无法继续往前挤的时候说明前面的更小了,而且越往前越小(挤得越往前) 堆排序:构造最大堆,每次取走根结点(自然是最大的),再调用MAX-HEAPIFY算法(见后文的堆)恢复最大堆的性质,重复取走根结点 快速排序(对A[r]-A[n]进行排序): 1.从序列

红黑树与AVL(平衡二叉树)的区别

关于红黑树和AVL树,来自网络: 1 好处 及 用途 红黑树 并不追求"完全平衡 "--它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能. 红黑树能够以 O(log2  n)  的时间复杂度进行搜索.插入.删除操作.此外,由于它的设计,任何不平衡都会在三次旋转之内解决.当然,还有一些更好的,但实现起来更复杂的数据结构 能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较"便宜"的解决方案.红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高

数据结构-红黑树

转自:http://dongxicheng.org/structure/red-black-tree/ 1. 简介 红黑树是一种自平衡二叉查找树.它的统计性能要好于平衡二叉树(AVL树),因此,红黑树在很多地方都有应用.在C++ STL中,很多部分(目前包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持).它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它

红黑树

弄了很久,学习过程中觉得很难,但学完了,其实感觉也就那样,就是情况多了些. 首先是插入,插入的时候其实也就3种情况,因为只有当插入的节点的父亲是红色的时候,此时红黑树的性质遭到破坏,需要旋转,再分1.叔父节点为红,此时只要改变颜色,但祖父节点颜色的改变可能会破坏红黑树的性质,所以要node = grandparent,继续向上,叔父为黑,这时需要旋转,所以得判断,自身的位置,也就是自己和父亲分别是左孩子还是右孩子,2.如果自身是右,父亲是左,的先把自己也旋转到左,再和自己是左,父亲也是左的情况一

定时器管理:nginx的红黑树和libevent的堆

libevent 发生超时后, while循环一次从堆顶del timer——直到最新调整的最小堆顶不是超时事件为止,(实际是del event),但是会稍后把这个timeout的 event放到active 任务list里, 等待处理,event标记为timeout,等处理actvie队列时再由应用层callback函数决定怎么处理标记为timeout的事件. nginx处理超时时,直接删除红黑树中( event结构体里的 )rb node成员,同时调用应用层早已通过add timer注册好的

数据结构——红黑树

红黑树是二叉排序树的改进, 红黑树有几个特点: 1:节点只有2中颜色,红色和黑色. 2:根节点一定是黑色节点. 3:红色节点的子节点一定是黑色节点. 4:黑色高度(根节点到每个叶子节点的路径长度包含相同的黑色节点)相等. 规定的插入的节点一定是红色节点, 红黑树的插入节点后需要调整的规则,插入节点需要调整的情况有3种: 情况1:插入的节点的父节点和叔叔节点都为红色: 以上情况节点4为插入节点(当前节点),这种情况调整方式是将父节点和叔叔节点都调整为黑色节点,祖父节点调整为红色,将祖父节点变为当前

【转】B树、B-树、B+树、B*树、红黑树、 二叉排序树、trie树Double Array 字典查找树简介

B  树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中:否则,如果查询关键字比结点关键字小,就进入左儿子:如果比结点关键字大,就进入右儿子:如果左儿子或右儿子的指针为空,则报告找不到相应的关键字: 如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树的搜索性