当前位置: 首页 > news >正文

JAVA中的数据结构 - 真正的去理解红黑树

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

一, 红黑树所处数据结构的位置

在JDK源码中, 有treeMap和JDK8的HashMap都用到了红黑树去存储

红黑树可以看成B树的一种: 

从二叉树看,红黑树是一颗相对平衡的二叉树

二叉树-->搜索二叉树-->平衡搜索二叉树--> 红黑树

 

从N阶树看,红黑树就是一颗 2-3-4树

N阶树-->B(B-)树

 

故我提取出了红黑树部分的源码,去说明红黑树的理解

看之前,理解红黑树的几个特性,后面的操作都是为了让树符合红黑树的这几个特性,从而满足对查找效率的O(logn)

 

二,红黑树特性,以及保持的手段

1,根和叶子节点都是黑色的

2,不能有有连续两个红色的节点

3, 从任一节点到它所能到达得叶子节点的所有简单路径都包含相同数目的黑色节点

 

这几个特效,个人理解就是规定了红黑树是一颗2-3-4的B树了,从而满足了O(logn)查找效率

 

保持特性的手段,通过下面这些手段,让红黑树满足红黑树的特性,如果要尝试理解,可以从2-3-4树的向上增长,后面有详细介绍

 

当然,这些改变也都是在O(logn)内完成的,主要改变方式有

1, 改变颜色

2, 左旋

3, 右旋

 

 

三,从JDK源码来理解

主要看我的注释,逻辑的理解

先看TreeMap

//对treeMap的红黑树理解注解. 2017.02.16 by 何锦彬  JDK,1.7.51<br>  <br>/** From CLR */
    private void fixAfterInsertion(Entry<K, V> x) {
  
          
          
        //新加入红黑树的默认节点就是红色
        x.color = RED;
        /**
         * 1. 如为根节点直接跳出
         */
        while (x != null && x != root && x.parent.color == RED) {
             
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                  
                //如果X的父节点(P)是其父节点的父节点(G)的左节点
                //即 下面这种情况
                /**
                 *                         G
                 *               P(RED)              U
                 */             
                //获取其叔(U)节点
                Entry<K, V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    // 这种情况,对应下面 图:情况一
                    /**
                     *                         G
                     *               P(RED)              U(RED)
                     *          X
                     */
                    //如果叔节点是红色的(父节点有判断是红色). 即是双红色,比较好办,通过改变颜色就行. 把P和U都设置成黑色然后,X加到P节点。 G节点当作新加入节点继续迭代
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //处理红父,黑叔的情况
                    if (x == rightOf(parentOf(x))) {
                        // 这种情况,对应下面 图:情况二
                        /**
                         *                           G
                         *            P(RED)                        U(BLACK)
                         *                     X
                         */
                        //如果X是右边节点
                        x = parentOf(x);
                        // 进行左旋
                        rotateLeft(x);
                    }
                    //左旋后,是这种情况了,对应下面 图:情况三
                    /**
                     *                           G
                     *            P(RED)                        U(BLACK)
                     *      X
                     */
                      
                    // 到这,X只能是左节点了,而且P是红色,U是黑色的情况
                    //把P改成黑色,G改成红色,以G为节点进行右旋
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                //父节点在右边的
                /**
                 *                         G
                 *                 U               P(RED)
                 */             
                //获取U
                Entry<K, V> y = leftOf(parentOf(parentOf(x)));
                  
                if (colorOf(y) == RED) {
                    //红父红叔的情况
                    /**
                     *                          G
                     *             U(RED)               P(RED)
                     */   
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    //把G当作新插入的节点继续进行迭代
                    x = parentOf(parentOf(x));
                } else {
                    //红父黑叔,并且是右父的情况
                    /**
                     *                          G
                     *             U(RED)               P(RED)
                     */   
                    if (x == leftOf(parentOf(x))) {
                    //如果插入的X是左节点
                     /**
                        *                            G
                        *            U(BLACK)                        P(RED)
                        *                                       X             
                        */
                        x = parentOf(x);
                        //以P为节点进行右旋
                        rotateRight(x);
                    }
                    //右旋后
                    /**
                     *                            G
                     *            U(BLACK)                        P(RED)
                     *                                                        X
                     */
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    //以G为节点进行左旋
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        //红黑树的根节点始终是黑色
        root.color = BLACK;
    }

再看看HashMap的实现

在HashMap中,在JDK8后开始用红黑树代替链表,查找由O(n) 变成了 O(Logn)

源码分析如下:

for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //JDK8 的hashmap,链表到了8就需要变成颗红黑树了
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

红黑树的维护代码部分如下:

//hashmap的红黑树平衡
         static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                            TreeNode<K,V> x) {
                    x.red = true;
                    //死循环加变量定义,总感觉JAVA很少这样写代码 哈
                    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                        //xp X父节点, XPP X的祖父节点,  XPPL 祖父左节点  XXPR 祖父右节点  
                        if ((xp = x.parent) == null) {
                            x.red = false;
                            return x;
                        }
                        // 如果父节点是黑色, 或者XP父节点是空,直接返回
                        else if (!xp.red || (xpp = xp.parent) == null)
                            return root;
                        
                        // 下面的代码就和上面的很treeMap像了,
                       
                        if (xp == (xppl = xpp.left)) {
                             // 第一种情况, 赋值xppl
                            //父节点是左节点的情况,下面这种
                            /**
                             *                         G
                             *               P(RED)              U
                             */              
                            if ((xppr = xpp.right) != null && xppr.red) {
                                //如果红叔的情况
                                 // 这种情况,对应下面 图:情况一
                                /**
                                 *                         G
                                 *               P(RED)              U(RED)
                                 *          X
                                 */
                                 //改变其颜色,
                                xppr.red = false;
                                xp.red = false;
                                xpp.red = true;
                                x = xpp;
                            }
                            else {
                                 // 黑叔的情况
                                //  这种情况
                              /**
                                 *                         G
                                 *               P(RED)              U(BLACK)
                                 */
                                if (x == xp.right) {
                                    //如果插入节点在右边 这种
                                     // 这种情况,对应下面 图:情况二
                                    /**
                                     *                           G
                                     *            P(RED)                        U(BLACK)
                                     *                     X
                                     */
                                    //需要进行左旋
                                    root = rotateLeft(root, x = xp);
                                    xpp = (xp = x.parent) == null ? null : xp.parent;
                                }
                                //左旋后情况都是这种了,对应下面 图:情况三
                                /**
                                 *                           G
                                 *            P(RED)                        U(BLACK)
                                 *      X
                                 */
                                // 到这,X只能是左节点了,而且P是红色,U是黑色的情况
                                if (xp != null) {
                                //把P改成黑色,G改成红色, 以G为节点进行右旋
                                    xp.red = false;
                                    if (xpp != null) {
                                        xpp.red = true;
                                        root = rotateRight(root, xpp);
                                    }
                                }
                            }
                        }
                        else {
                             //父节点在右边的
                            /**
                             *                         G
                             *                 U               P(RED)
                             */              
                            //获取U
                            if (xppl != null && xppl.red) {
                                //红父红叔的情况
                                 /**
                                 *                           G
                                 *                 U(RED)               P(RED)
                                 */              
                                xppl.red = false;
                                xp.red = false;
                                xpp.red = true;
                                x = xpp;
                            }
                            else {
                             
                                if (x == xp.left) {
                                    //如果插入的X是右节点
                                  /**
                                     *                            G
                                     *            U(BLACK)                        P(RED)
                                     *                                       X              
                                     */
                                    root = rotateRight(root, x = xp);
                                    xpp = (xp = x.parent) == null ? null : xp.parent;
                                }
                                //右旋后
                                /**
                                 *                            G
                                 *            U(BLACK)                        P(RED)
                                 *                                                        X
                                 */
                                if (xp != null) {
                                   //把P改成黑色,G改成红色,
                                    xp.red = false;
                                    if (xpp != null) {
                                        xpp.red = true;
                                        //以G节点左旋
                                        root = rotateLeft(root, xpp);
                                    }
                                }
                            }
                        }
                    }

情况图如下

 

情形3示意图

                        情况1

 

情形4示意图

                      情况2

 

情形5示意图

                         情况3

 

JDK源码处理红黑树的流程图

 

可见,其实处理逻辑实现都一样的

 

 

 

三,个人对红黑树理解的方法

1, 如何理解红黑树的O(lgN)的特性?

从2-3-4树去理解

红黑树,其实是一颗 2-3-4的B树,B树都是向上增长的,如果不理解向上增长可以先看看2-3树,这样理解就能知道为什么能O(logn)的查找了

 

2, 如何理解红黑树的红黑节点意义?

可以把红色节点看成是连接父节点的组成的一个大节点(2个或3个或4个节点组成的一个key),如下:

image_thumb54

                                         (此图转自网上)

 

红色的就是和父节点组成了大节点,

比如 

节点7和6,6是红色节点组成,故和它父节点7组成了一个大节点,即 2-3-4树的 6, 7节点

又如

节点 9和10和11,9和10为红色节点,故和10组成了一个2-3-4的3阶节点, 9,10,11(注意顺序有的关性)

 

3 , B树是如何保持O(lgn)的复杂度的呢?

 B+树都是从底布开始往上生长,自动平衡,如 2-3-4树,当节点达到了3个时晋升到上个节点,所以不会产生单独生长一边的情况,形成平衡。

 

留个问题

4, 数据库里的索引为什么不用红黑树而是用B+树(Mysql)呢? 

后续解答

 

转载于:https://my.oschina.net/weiweiblog/blog/469867

相关文章:

  • ViewPager+seekBar的联动效果
  • Babel 手记
  • 【Spring学习笔记-MVC-10】Spring MVC之数据校验
  • 为什么使用MAVEN 3.2.1会有版本问题?
  • ng6--错误信息小结(持续更新)
  • org.tinygroup.context2object-参数对象构建
  • 思考 | 云计算 + 区块链 = ?
  • devexpress表格控件gridcontrol设置隔行变色、焦点行颜色、设置(改变)显示值、固定列不移动(附源码)...
  • Day 33 三剑客-awk
  • HDU 4828 (卡特兰数+逆)
  • python学习-文件处理
  • 四、oracle 用户管理二
  • 3.字典常用功能
  • java多线程下载
  • MySQLdb的安装与使用
  • 【5+】跨webview多页面 触发事件(二)
  • ES6系列(二)变量的解构赋值
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • Hexo+码云+git快速搭建免费的静态Blog
  • IP路由与转发
  • Laravel Telescope:优雅的应用调试工具
  • maya建模与骨骼动画快速实现人工鱼
  • Python爬虫--- 1.3 BS4库的解析器
  • 闭包,sync使用细节
  • 从零搭建Koa2 Server
  • 多线程 start 和 run 方法到底有什么区别?
  • 浮现式设计
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 聊聊redis的数据结构的应用
  • 排序算法之--选择排序
  • 前端临床手札——文件上传
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 我看到的前端
  • 做一名精致的JavaScripter 01:JavaScript简介
  • 你对linux中grep命令知道多少?
  • 阿里云API、SDK和CLI应用实践方案
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (四)linux文件内容查看
  • (五)网络优化与超参数选择--九五小庞
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]
  • .Net core 6.0 升8.0
  • .NET 的程序集加载上下文
  • .NET 跨平台图形库 SkiaSharp 基础应用
  • .Net 路由处理厉害了
  • .NET 命令行参数包含应用程序路径吗?
  • .Net6 Api Swagger配置
  • .net反编译的九款神器
  • ::前边啥也没有
  • [Android]常见的数据传递方式