资讯专栏INFORMATION COLUMN

树 - (二叉查找树,红黑树,B树)- 红黑树

yangrd / 1706人阅读

摘要:需要执行的操作依次是首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除然后,通过旋转和重新着色等一系列来修正该树,使之重新成为一棵红黑树。

虽是读书笔记,但是如转载请注明出处 http://segmentfault.com/blog/exploring/
.. 拒绝伸手复制党

关于二叉树的基本知识,可以参见:Java 实现基本数据结构 2(树)

以下是算法导论第13章的学习笔记

红黑树

BST的各种操作的时间复杂度是依赖于树的高度,通过使得BST成为红黑树,确保每次对BST进行插入和删除之后,树的高度上限依然是logn.

红黑树,本质上来说就是一棵二叉查找树,而且是平衡的查找二叉树 -- 让BST效率更优

定义

红黑树中每个结点包含五个域:color,key,left,rightp。通过对一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长两倍。

如果某结点没有一个子结点或父结点,则该域指向 NIL。

我们把 NIL 视为二叉树的外结点 (叶子),而带关键字的结点视为内结点。

一棵二叉树如果满足下面的红黑性质,则为一棵红黑树:

1) 每个结点或是红的,或是黑的。

2) 根结点是黑的。

3) 每个叶结点 (NIL) 是黑的。

4) 如果一个结点是红的,则它的两个儿子都是黑的。

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

采用哨兵来代表 NIL,它的 color 域为 BLACK,其它域为任意值。

从某个结点 x 出发 (不包括该结点) 到达一个叶结点的任意一条路径上,黑色结点的个数称为该结点x黑高度,用bh(x) 表示。

引理:一颗有 n 个内结点的红黑树的高度至多为 2lg(n+1)。

操作 旋转

旋转的目的是让树保持红黑树的特性。

对 x 进行左旋,意味着,将 “x 的右孩子” 设为 “x 的父亲节点”;即,将 x 变成了一个左节点 (x 成了为 z 的左孩子)!。 因此,左旋中的 “左”,意味着 “被旋转的节点将变成一个左节点”。

对 x 进行右旋,意味着,将 “x 的左孩子” 设为 “x 的父亲节点”;即,将 x 变成了一个右节点 (x 成了为 y 的右孩子)! 因此,右旋中的 “右”,意味着 “被旋转的节点将变成一个右节点”

        // 左旋x
    public void rotate(TreeNode root, TreeNode x){
        if(x.right != null){
            //处理x的右孩子
            TreeNode y = x.right;
            x.right = y.left;
            if(y.left != null)
                y.left.parent = x;
            // 处理x的父节点
            y.parent = x.parent ;
            if(x.parent != null){
                // 判断y链接的位置
                if(x.parent.left == x){
                    x.parent.left = y;
                }
                if(x.parent.right == x){
                    x.parent.right = y;
                }
            }else{
                root = y;
            }
            // 链接新的父节点
            x.parent = y;
            y.left = x;
        }
    }

Note: 右旋转的时候可以把代码中的left换成right就好了。

插入

关于插入和删除整理自July大神的blog和youtube的短视频
youtube

重温下RedBlack tree的五条性质:
1 节点 r or b
2 根 b
3 叶子 b
4 红色节点孩子必为黑
5 任意节点其叶子节点的路径包含相同个数黑节点

红黑树插入过程的思想是:利用BST的插入方法,寻找待插入元素的位置并插入[所以这一部分可以把BST的直接挪过来]。然后(sth different:) 将待插入元素涂红色。为了保证红黑树的五条性质,需要调用辅助程序rbInsertFixup来调整,对节点重新着色并旋转。

插入情况(插入的节点p设置为红)有三:
1. 原tree为空树,所以p设置为根节点 -- 解决方案:Just 设置p为黑就可以
2. 插入节点的父节点为 -- 无需解决方法,插入后无影响。
3. ** 插入节点的父节点为 -- 需要rbInsertFixup
case 1: p节点和叔节点都为 -- 解决方案:父+叔 都涂;祖父涂p = 祖父从新的当前结点重新开始算法
case 2: p节点为,且p是父节点的子 -- 解决方案:p = 父, 左旋p
(case2 实际上有两种,看youtube 视频时候才发现)
(这两种情况可以想象成一个菱形的两半。只要右子就左旋,左子就右旋)
case 3: p节点为,且p是父节点的子 -- 解决方案:父+叔 都涂, 父节点涂,祖父涂红,祖父右旋。
(case3 实际上也有两种,这两种情况可以想象成两条直线,三角形除了底的两条边)

上面三种情况 (Case) 处理问题的核心思路都是:将红色的节点移到根节点;然后,将根节点设为黑色。

case2 和 case3 的区分是1. 二者二叉树的结构不同,菱形和三角 2. 解决方案不同,涂黑or not
最后,把根结点涂为色,整棵红黑树便重新恢复了平衡。

        //插入
        public RBNode insert(RBNode root, RBNode x){
        RBNode y = this.Nil; // Nil
        RBNode p = root;
        // if the node inserted is null
        if(x == null){
            return root;
        }
        // seek the place where x to be inserted
        while(p!=null){         
            if(x.val > p.val){
                y = p;
                p = p.right;
            }
            if(x.val < p.val){
                y = p;
                p = p.left;
            }
        }
        // insert
        if(y == Nil){
            root = x;
        }
        else
        {
            x.parent = y;
            if(x.val > y.val){
                y.right = x;
            }
            else{
                y.left = x;
            }
        }
        // something different from BST insert 
        x.left = Nil;
        x.right = Nil;
        x.color = 0; // set it red;
        // fixup
        rbInsertFixup(root, x);
        return root;
    }
        public void rbInsertFixup(RBNode root, RBNode x){
        // the fixup occurs when x.partent is red
        while(x.parent.color == 0){
            // 又分为父结点是祖父结点的左子还是右子,对于对称性,我们只要解开一个方向就可以了
            if(x.parent == x.parent.parent.left){
                RBNode uncle = x.parent.parent.right;
                // case 1 
                if(uncle.color == 0){
                    x.parent.color = 1;
                    uncle.color = 1; 
                    x.parent.parent.color = 0;
                    x = x.parent.parent;
                }

                else
                {
                    // case 2
                    if(x == x.parent.right){
                    {   
                        x = x.parent;
                        this.rotateLeft(root, x);
                    }
                    // case 3
                    {
                        x.parent.color = 1;
                        x.parent.parent.color = 0;
                        this.rotateRight(root, x.parent.parent);
                    }
                }
            else
            {
                    // same as the clause with right and left child
            }
                    }
                }
            root.color = 1;
            }
    }
删除

摘录整理自blog
将红黑树内的某一个节点删除。需要执行的操作依次是:
首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;
然后,通过 "旋转和重新着色" 等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:

第一步:将红黑树当作一颗二叉查找树,将节点删除。
这和 "删除常规二叉查找树中删除节点的方法是一样的"。分 3 种情况:
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就 OK 了。
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把 “它的后继节点的内容” 复制给 “该节点的内容”;之后,删除 “它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给 "被删除节点" 之后,再将后继节点删除。这样就巧妙的将问题转换为 "删除后继节点" 的情况了,下面就考虑后继节点。 在 "被删除节点" 有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然 "的后继节点" 不可能双子都非空,就意味着 "该节点的后继节点" 要么没有儿子,要么只有一个儿子。若没有儿子,则按 "情况①" 进行处理;若只有一个儿子,则按 "情况②" 进行处理。

第二步:通过 "旋转和重新着色" 等一系列来修正该树,使之重新成为一棵红黑树。
因为 "第一步" 中删除节点之后,可能会违背红黑树的特性。所以需要通过 "旋转和重新着色" 来修正该树,使之重新成为一棵红黑树。

性能
-- BST 红黑树 Btree
遍历 O(n)
插入 O(h) O(h) 4
删除 O(h) O(h) 1
查询 O(h) O(h) 2
最小(大) O(h) O(h) 2
后继(前驱) O(h) O(h)
旋转 - O(1) -
用途

红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是 O(lgn),效率非常之高。
例如,Java 集合中的 TreeSet 和 TreeMap,C++ STL 中的 set、map,以及 Linux 虚拟内存的管理,都是通过红黑树去实现的。

和AVL比较

AVL比RBtree更加平衡,但是AVL的插入和删除会带来大量的旋转。
所以如果插入和删除比较多的情况,应该使用RBtree, 如果查询操作比较多,应该使用AVL.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64282.html

相关文章

  • Map集合、散列表、红黑介绍

    摘要:并且,我们的底层都是红黑树来实现的。红黑树是一种平衡二叉树因此它没有节点。 前言 声明,本文用得是jdk1.8 前面已经讲了Collection的总览和剖析List集合: Collection总览 List集合就这么简单【源码剖析】 原本我是打算继续将Collection下的Set集合的,结果看了源码发现:Set集合实际上就是HashMap来构建的! showImg(https:/...

    2json 评论0 收藏0
  • JDK源码那些事儿之红黑基础上篇

    摘要:用这种范例表示红黑树是可能的,但是这会改变一些性质并使算法复杂。插入会出现种情况为根节点,即红黑树的根节点。 说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树。 前言 限于篇幅,本文只对红黑树的基础进行说明,暂不涉及源码部分,大部分摘抄自维基百科,这里也贴出对应链接...

    qylost 评论0 收藏0
  • 数据结构与算法:二叉算法

    摘要:因此,根据题目给出的先序遍历和中序遍历,可以画出二叉树选参考数据结构与算法描述实现二叉树算法浅谈数据结构二叉树慕课网实现二叉树算法前端树控件腾讯软件开发面试题 内容衔接上一章 数据结构与算法:常见排序算法 内容提要 什么是树   - 为什么使用树 二叉树 二叉查找树 红黑树 B、B+树 堆 伸展树 树 可以点击链接感受下笔者用d3.js画的tree https://codepen...

    Little_XM 评论0 收藏0
  • 红黑,超强动静图详解,简单易懂

    摘要:写在前面红黑树,对很多童鞋来说,是既熟悉又陌生。每次需要查看红黑树内容时都很难以更生动形象的方式来理解其内容。 写在前面 红黑树,对很多童鞋来说,是既熟悉又陌生。学校中学过,只了解大概;工作中不怎么使用,但面试又是重点。每次需要查看红黑树内容时都很难以更生动形象的方式来理解其内容。没错,本文内容就是要解决这个问题,用简单的语言,搭配静图和动图(利用大脑图形记忆方式),让你对红黑树有更深...

    Scorpion 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<