红黑树是一种平衡二叉搜索树,其首先必须满足二叉搜索树的条件(注意,不用满足AVL树的条件,即不用满足两个子树高度差不能超过一),然后还要满足下述条件:
1.每个节点不是红色就是黑色
2.根节点为黑色
3.如果节点为红,其子节点必须为黑(父子结点不能同时为红)
4.任一节点至NULL(树尾端,无物)的任何路径,所含黑节点数必须相同
(为求方便,我们把NULL视为黑节点)
根据这些规则,对新插入的结点有两点要求:新节点必须为红(规则4),新节点父节点必须为黑(前面要求和规则3)
插入时有一种情况是违反规则的也就是插入节点X必为红,如果父节点P也为红,那就违反了规则3,此时祖父节点G为黑,曾祖父节点为GG,违反规则了我们就要通过旋转使其符合规则,根据伯父节点S可以分为四种类型:(注意:null节点为黑)
1.S为黑且X为外侧插入,则单旋转P并改变P,G颜色。
2.S为黑且X为内侧插入,则先单旋转X再更改G,X颜色,在对X做一次单旋转。
3.S为红且X为外侧插入,则先对P做一次单旋转,并改变X的颜色,如果此时GG为黑,OK;如果GG为红,则再看属于三种情况的哪一种。
其实我觉得书上的这种分类是有问题的,因为很明显少分了一种,即S为红且X为内侧插入,根据网上的资料,确实可以只分为3种情况,即第三种情况变为S为红,把插入节点的父节点和叔叔节点涂黑,祖父节点涂红,把插入节点指向祖父节点,再看是符合或归结于前两种情况中的一种。
红黑树详解
这篇文章对红黑树讲解的很好,我觉得不用参照书上的这种插入方法,可以按照上面这篇文章的插入方法,它的插入方法主要分为3中情况,第一种情况可以跳到第二种情况,第二种情况可以跳到第三种情况,逻辑比较清晰简单。(当然其实书上的改进方法逻辑也是这样,不过第二种情况有些许差别)。
按照书上的方法的状况3,有可能出现迭代的往上发展的可能造成效率的瓶颈,所以我们可以施行一个从上而下的程序。加入新增节点为A,则沿着从根节点到A的路径,只要发现某结点X的两个子节点均为红色,则把X给为红色,把两个子节点改为黑色,如果X的父节点也为红色,则归结为状况1或2,我们可以发现,这个程序是由上而下的,即上面的节点先发现先改。
完成这个程序后,再插入节点A就不会出现状况4的情况了。
其实这个程序就是我们补充内容的加强版,补充内容是插入发现问题了改一次,这个是先改了再插入。
总而言之,红黑树的插入分为三种情况,且这三种情况是依次使用的,可以用参考资料的情况3加书上的情况1,2,也可以用参考资料的情况3加参考资料的情况1,2,也可以用由上而下的程序加书上的情况1,2。
红黑树的节点和迭代器同slist类似,同时采用双层架构,基本节点存放指针颜色,高级节点存放节点值。同时还有两个求极值的函数,很简单,一直往左走或往右走就行,返回一个基本节点的指针。
红黑树的迭代器也采用了双层架构,基本迭代器有一个指向基本节点的指针,并且有increment和decrement两个函数,这两个函数中有两种特殊情况是因为在采用了一个特殊header的原因,然后高级迭代器就按照正常迭代器的设计,有一个指向高级节点的指针,还有一些重载操作。
红黑树的迭代器的++操作是移动到比当前迭代器所指节点大的最少的节点的迭代器,–操作是移动到比当前迭代器所指节点小的最少的节点迭代器,所以可以用这个来输出有序序列。
红黑树的这个数据结构怎么说呢,感觉和之前的那些也差不多,定义了一些类型,然后是get_node和create_node还有销毁函数,然后是红黑树本身的一些数据,只有3个,结点数,header,比较准则,另外的根节点,最大最小节点都是通过header获取,还有几个函数用来获取节点的成员,还有插入删除函数,初始化函数,构造析构函数。额,还有一个重载=的函数,但是没有函数体,有点不太懂 //待完善
这一小节啥也没说,因为上一节已经有get_node和create_node这种内存管理了,还有构造函数和析构函数了,不过树状结构的各种操作
最需要注意的就是边界条件的发生,即走到根节点时要有特殊的处理,为了简化这种处理,我们设置了一个header,header和根节点互为父节点。
insert_equal:插入新值,允许键值重复,这个就是一直比较然后看去左边还是右边直到比较到右节点然后插入。
insert_unique:插入新值,不允许键值重复,如果重复插入无效,返回一个pair类型,这个怎么保证不插入重复的呢,奥秘在于这个比较函数是左边小于右边为真,大于等于右边为假,所以我们要做的就是找到第一个跳到右边的对应的节点,如果一直是跳到左边(即最后插入左端,同时父节点是最左节点),那就直接插入即可,如果最后是插入右端,那父节点就是对应的节点,已知父节点≤插入节点,这个时候用比较函数(父节点,子节点),如果返回为真则父节点<子节点,那就说明不重复,返回为假说明父节点≥子节点,结合前面的信息,那就说明重复了,如果插入左端但父节点不是最左节点,说明中途还是跳过右边,那就找到跳的时候对应的那个结点进行比较,原理同上。
__insert:真正的插入执行程序,当你确定好插入点后,就用这个函数完成插入,这个函数主要是判断是第一个插入的结点还是插左边还是插右边。
__rb_tree_rebalance:调整红黑树,每次插入后都要调用这个函数,这个函数是一个循环函数,分为两种大情况和三种小情况,大情况就是当前节点的父节点是左子节点还是右子节点,小情况就是上面插入情况里的网页资料中的三种情况。
两个旋转函数:这两个比较简单,只要你会单旋转就能看懂,注意红黑树的数据结构中没有root,所谓的root,都是header->parent。
find:元素的查找函数,这个函数比较高效,而且有点难理解说实话,不过它运用的原理和insert_unique一样,都是用大于小于去夹,如果你往右边走,说明你是大于x的键值的,大于,就说明没有等于的可能性,如果你是往左边走,说明是小于等于x的键值的,这个至少有等于的可能性,所以要让y指向它,最后再夹一下判断就行,且后面只要有一个左拐,说明前面的左拐对应的结点肯定不是了(可以用反证法)。
红黑树是一种平衡二叉搜索树,利用其自身的特点在二叉搜索树的基础上可以完成平衡(利用规则4和规则3可以保证子树之间高度有两倍以上的差异),其节点和迭代器为了灵活都采用了双层架构,函数操作主要涉及插入查找,而插入中又有旋转变色的操作,不得不说,是一种很精妙的数据结构。
随记:对于这个红黑树,一开始是真不懂啊,虽然以前上数据结构课的时候讲平衡树见过单旋转双旋转这些,但是当时划水去了,完全没听,把这个红黑树看完,真的中途很多不懂的,特别是代码中的一些写法,但是自己慢慢琢磨也搞懂了,真挺不容易的,今晚吃泡面的时候加个鸡腿。