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

后续解答

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

(0)

相关推荐

  • Java TreeMap排序算法实例

    本文实例讲述了Java TreeMap排序算法.分享给大家供大家参考,具体如下: TreeMap 和 HashMap 用法大致相同,但实际需求中,我们需要把一些数据进行排序: 以前在项目中,从数据库查询出来的数据放在List中,顺序都还是对的,但放在HashMap中,顺序就完全乱了. 为了处理排序的问题: 1. 对于一些简单的排序,如:数字,英文字母等 TreeMap hm = new TreeMap<String, String>(new Comparator() { public int

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • Java中HashMap和TreeMap的区别深入理解

    首先介绍一下什么是Map.在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value.这就是我们平时说的键值对. HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的). HashMap 非线程安全 TreeMap 非线程安全 线程安全 在Java里,线程安全一般体

  • java 中HashMap、HashSet、TreeMap、TreeSet判断元素相同的几种方法比较

    java 中HashMap.HashSet.TreeMap.TreeSet判断元素相同的几种方法比较 1.1     HashMap 先来看一下HashMap里面是怎么存放元素的.Map里面存放的每一个元素都是key-value这样的键值对,而且都是通过put方法进行添加的,而且相同的key在Map中只会有一个与之关联的value存在.put方法在Map中的定义如下. V put(K key, V value); 它用来存放key-value这样的一个键值对,返回值是key在Map中存放的旧va

  • java中treemap和treeset实现红黑树

    TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 TreeSet 之间的关系,下面先看 TreeSet 类的部分源代码: public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializab

  • 详解Java中HashSet和TreeSet的区别

    详解Java中HashSet和TreeSet的区别 1. HashSet HashSet有以下特点: 不能保证元素的排列顺序,顺序有可能发生变化 不是同步的 集合元素可以是null,但只能放入一个null 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置. 简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个

  • 浅谈java中的TreeMap 排序与TreeSet 排序

    TreeMap: package com; import java.util.Comparator; import java.util.TreeMap; public class Test5 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub TreeMap<String, String> tree = new TreeMap<String,

  • java HashMap,TreeMap与LinkedHashMap的详解

     java HashMap,TreeMap与LinkedHashMap的详解 今天上午面试的时候 问到了Java,Map相关的事情,我记错了HashMap和TreeMap相关的内容,回来赶紧尝试了几个demo理解下 package Map; import java.util.*; public class HashMaps { public static void main(String[] args) { Map map = new HashMap(); map.put("a", &

  • 图解红黑树及Java进行红黑二叉树遍历的方法

    红黑树 红黑树是一种数据结构与算法课堂上常常提到但又不会细讲的树,也是技术面试中经常被问到的树,然而无论是书上还是网上的资料,通常都比较刻板难以理解,能不能一种比较直观的方式来理解红黑树呢?本文将以图形的方式来解释红黑树的插入与删除操作. 对树结构的学习是一个递进的过程,我们通常所接触的树都是二叉树,二叉树简单来说就是每个非叶子节点都有且只有两个孩子,分别叫做左孩子和右孩子.二叉树中有一类特殊的树叫二叉查找树,二叉查找树是一种有序的树,对于每个非叶子节点,其左子树的值都小于它,其右子树的值都大于

  • TreeSet详解和使用示例_动力节点Java学院整理

    第1部分 TreeSet介绍 TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口. TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法. TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法.比如查找与指定目标最匹配项. TreeSet 实现了Cl

随机推荐