Java数据结构之红黑树的真正理解

 更新时间:2017年11月02日 08:51:53   作者:何锦彬  
这篇文章主要为大家详细介绍了Java数据结构之红黑树的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

真正的帮助大家理解红黑树:

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

在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);
         }
        }
       }
      }
 }

情况图如下

情况1

情况2

情况3

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

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

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

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

从2-3-4树去理解

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

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

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

(此图转自网上)

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

比如

节点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)呢?

后续解答

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot整合Scala构建Web服务的方法

    SpringBoot整合Scala构建Web服务的方法

    这篇文章主要介绍了SpringBoot整合Scala构建Web服务的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Spring Boot 整合 Fisco Bcos的案例分析(区块链)

    Spring Boot 整合 Fisco Bcos的案例分析(区块链)

    本篇文章介绍的 Spring Boot 整合 Fisco Bcos的案例,是在阿里云服务器上部署验证的。大家可根据自己的电脑环境,对比该案例进行开发即可,具体案例代码跟随小编一起看看吧
    2022-01-01
  • java中对象转json字符串的几种常用方式举例

    java中对象转json字符串的几种常用方式举例

    这篇文章主要给大家介绍了关于java中对象转json字符串的几种常用方式,在Java中可以使用许多库将对象转换为JSON字符串,其中最常用的是Jackson和Gson,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • Java多线程 线程同步与死锁

    Java多线程 线程同步与死锁

    这篇文章主要介绍了 Java多线程 线程同步与死锁的相关资料,需要的朋友可以参考下
    2017-07-07
  • Java spring webmvc如何实现控制反转

    Java spring webmvc如何实现控制反转

    这篇文章主要介绍了Java spring webmvc如何实现控制反转,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 浅谈Springboot之于Spring的优势

    浅谈Springboot之于Spring的优势

    这篇文章主要介绍了浅谈Springboot之于Spring的优势,简述了在Java EE开发中遇到的问题,言简意赅,需要的朋友可以参考下。
    2017-09-09
  • 启动 Eclipse 弹出 Failed to load the JNI shared library jvm.dll 错误的解决方法

    启动 Eclipse 弹出 Failed to load the JNI shared library jvm.dll

    这篇文章主要介绍了有时候,新电脑上回碰到打开Eclipse时,弹出提示“Failed to load the JNI shared library jvm.dll”错误,这里给大家分享解决方案
    2016-08-08
  • Maven使用Nexus创建私服的实现

    Maven使用Nexus创建私服的实现

    本文主要介绍了Maven使用Nexus创建私服的实现,通过建立自己的私服,就可以降低中央仓库负荷、节省外网带宽、加速Maven构建、自己部署构件等,从而高效地使用Maven,感兴趣的可以了解一下
    2024-04-04
  • org.slf4j.Logger中info()方法的使用详解

    org.slf4j.Logger中info()方法的使用详解

    这篇文章主要介绍了org.slf4j.Logger中info()方法的使用详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java文本文件操作方法实例详解

    Java文本文件操作方法实例详解

    这篇文章主要介绍了Java文本文件操作方法,以实例形式较为详细的分析了java操作文本文件的相关技巧,需要的朋友可以参考下
    2015-06-06

最新评论