红黑树的设计,相比 jdk1.7 的 HashMap 而言,jdk1.8 最重要的就是引入了红黑树的设计,当冲突的链表长度超过 8 个的时候,链表结构就会转为红黑树结构。

01、故事的起因

JDK1.8 最重要的就是引入了红黑树的设计(当冲突的链表长度超过 8 个的时候),为什么要这样设计呢?好处就是避免在最极端的情况下冲突链表变得很长很长,在查询的时候,效率会非常慢。

  • 红黑树查询:其访问性能近似于折半查找,时间复杂度 O(logn);

  • 链表查询:这种情况下,需要遍历全部元素才行,时间复杂度 O(n);

本文主要是讲解红黑树的实现,只有充分理解了红黑树,对于之前的分析才会更加理解。

简单的说,红黑树是一种近似平衡的二叉查找树,其主要的优点就是“平衡“,即左右子树高度几乎一致,以此来防止树退化为链表,通过这种方式来保障查找的时间复杂度为 log(n)。

关于红黑树的内容,网上给出的内容非常多,主要有以下几个特性:

  • 1、每个节点要么是红色,要么是黑色,但根节点永远是黑色的;

  • 2、每个红色节点的两个子节点一定都是黑色;

  • 3、红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色);

  • 4、从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;

  • 5、所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);

在树的结构发生改变时(插入或者删除操作),往往会破坏上述条件 3 或条件 4,需要通过调整使得查找树重新满足红黑树的条件。

02、调整方式

上面已经说到当树的结构发生改变时,红黑树的条件可能被破坏,需要通过调整使得查找树重新满足红黑树的条件。

调整可以分为两类:一类是颜色调整,即改变某个节点的颜色,这种比较简单,直接将节点颜色进行转换即可;另一类是结构调整,改变检索树的结构关系。结构调整主要包含两个基本操作:左旋(Rotate Left),右旋(RotateRight)

2.1、左旋

左旋的过程是将 p 的右子树绕 p 逆时针旋转,使得 p 的右子树成为 p 的父亲,同时修改相关节点的引用,使左子树的深度加 1,右子树的深度减 1,通过这种做法来调整树的稳定性。过程如下:

以 jdk1.8 为例,打开 HashMap 的源码部分,红黑树内部类 TreeNode 属性分析:

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
//指向父节点的指针
TreeNode<K,V> parent;
//指向左孩子的指针
TreeNode<K,V> left;
//指向右孩子的指针
TreeNode<K,V> right;
//前驱指针,跟next属性相反的指向
TreeNode<K,V> prev;
//是否为红色节点
boolean red;
......
}

左旋方法 rotateLeft 如下:

/*
* 左旋逻辑
*/
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
//root:表示根节点
//p:表示要调整的节点
//r:表示p的右节点
//pp:表示p的parent节点
//rl:表示p的右孩子的左孩子节点
TreeNode<K,V> r, pp, rl;
//r判断,如果r为空则旋转没有意义
if (p != null && (r = p.right) != null) {
//多个等号的连接操作从右往左看,设置rl的父亲为p
if ((rl = p.right = r.left) != null)
rl.parent = p;
//判断p的父亲,为空,为根节点,根节点的话就设置为黑色
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
//判断p节点是左儿子还是右儿子
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}

2.2、右旋

了解了左旋转之后,相应的就会有右旋,逻辑基本也是一样,只是方向变了。右旋的过程是将 p 的左子树绕 p 顺时针旋转,使得 p 的左子树成为 p 的父亲,同时修改相关节点的引用,使右子树的深度加 1,左子树的深度减 1,通过这种做法来调整树的稳定性。实现过程如下:

同样的,右旋方法 rotateRight 如下:

/*
* 右旋逻辑
*/
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
//root:表示根节点
//p:表示要调整的节点
//l:表示p的左节点
//pp:表示p的parent节点
//lr:表示p的左孩子的右孩子节点
TreeNode<K,V> l, pp, lr;
//l判断,如果l为空则旋转没有意义
if (p != null && (l = p.left) != null) {
//多个等号的连接操作从右往左看,设置lr的父亲为p
if ((lr = p.left = l.right) != null)
lr.parent = p;
//判断p的父亲,为空,为根节点,根节点的话就设置为黑色
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
//判断p节点是右儿子还是左儿子
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}

03、操作示例介绍

3.1、插入调整过程图解

3.2、删除调整过程图解

3.3、查询过程图解

04、总结

至此,红黑树的实现就基本完成了,关于红黑树的结构,有很多种情况,情况也比较复杂,但是整体调整流程,基本都是先调整结构然后调整颜色,直到最后满足红黑树特性要求为止。如果有理解不当之处,欢迎指正!

HashMap之红黑树的更多相关文章

  1. 怎样的操作才能让HashMap以红黑树类型存储数据? (文中没有解答该问题)

    怎样才能让HashMap以红黑树类型存储数据? 看上面的代码可知:如果一个Node的长度大于等于7.就会触发Node转TreeNode的操作. 我向一个map中插入了一百万条数据(插入一亿条时,内存溢 ...

  2. 为什么HashMap使用红黑树而不使用AVL树

    为什么HashMap使用红黑树而不使用AVL树? 红黑树适用于大量插入和删除:因为它是非严格的平衡树:只要从根节点到叶子节点的最长路径不超过最短路径的2倍,就不用进行平衡调节 AVL 树是严格的平衡树 ...

  3. 关于JDK1.7+中HashMap对红黑树场景的思考

    背景 在1.7之前的版本,当数组元素较多(几百.几千,或者更多)的时候,在这种前提扩容,涉及全量元素的遍历和坐标的重新定位,这个耗时会比较长.这是之前存在的一个弊端吧.那么引入红黑树之后就解决了问题, ...

  4. 浅析Java源码之HashMap外传-红黑树Treenode(已鸽)

    (这篇文章暂时鸽了,有点理解不能,点进来的小伙伴可以撤了) 刚开始准备在HashMap中直接把红黑树也过了的,结果发现这个类不是一般的麻烦,所以单独开一篇. 由于红黑树之前完全没接触过,所以这篇博客相 ...

  5. java随笔——HashMap与红黑树

    前言: hashmap是一种很常用的数据结构,其使用方便快捷,接下来笔者将给大家深入解析这个数据结构,让大家能在用的时候知其然,也知其所以然. 一.Map 首先,从最基本的讲起,我们先来认识一下map ...

  6. jdk1.8源码解析:HashMap底层数据结构之链表转红黑树的具体时机

    本文从三个部分去探究HashMap的链表转红黑树的具体时机: 一.从HashMap中有关“链表转红黑树”阈值的声明: 二.[重点]解析HashMap.put(K key, V value)的源码: 三 ...

  7. HashMap 链表和红黑树的转换

    HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树:若桶中元素小于等于6时,树结构还原成链表形式. 原因: 红黑树的平均查找长度是log(n),长度为8 ...

  8. JAVA中的数据结构 - 1,红黑树

    背景: 在JDK源码中, 有treeMap和JDK8的HashMap都用到了红黑树去存储 红黑树可以看成B树的一种: 二叉树-->搜索二叉树-->平衡搜索二叉树-->B树--> ...

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

    一, 红黑树所处数据结构的位置: 在JDK源码中, 有treeMap和JDK8的HashMap都用到了红黑树去存储 红黑树可以看成B树的一种: 从二叉树看,红黑树是一颗相对平衡的二叉树 二叉树--&g ...

随机推荐

  1. Servlet容器container

    通俗点说,所谓容器,就是放东西的地方.Servlet容器自然就是放Servlet的地方.J2EE开发,是有分工的.一般的程序员,写得都是应用开发,我们会按照一定的规则,开发我们的系统,比如用Servl ...

  2. 目录中的pwd、cd及ls命令

    1.pwd命令(确定当前所在目录) pwd命令以绝对路径的方式显示用户当前工作目录.命令将当前目录的全路径名称(从根目录)写入标准输出.全部目录使用/分隔.第一个/表示根目录,最后一个目录是当前目录. ...

  3. 2019-10-31-C#-强转空会不会出现异常

    title author date CreateTime categories C# 强转空会不会出现异常 lindexi 2019-10-31 8:53:6 +0800 2019-9-10 11:4 ...

  4. Effective C++: 06继承与面向对象设计

    32:确定你的public继承塑模出is-a关系 以C++进行面向对象编程,最重要的一个规则是:public继承表示的是"is-a"(是一种)的关系. 如果令class D以pub ...

  5. java中的volatile和synchronized

    关于volatile和同步相关的东西,网上有太多错误和解释不清的东西, 所以查阅相关书籍和文章后总结如下, 如果还是也存在不正确的内容,请一定要指出来, 以免误人子弟:) 1. 原子性与可视性 原子性 ...

  6. javascript导图 标签: javascript 2015-12-06 16:37 721人阅读 评论(24)

  7. Keycode对照表(键码对照表)

    https://segmentfault.com/a/1190000005828048 字母和数字键的键码值(keyCode) 按键 键码 按键 键码 按键 键码 按键 键码 A 65 J 74 S ...

  8. 是时候了解React Native了

    文章首发于简书,欢迎关注 随着科技的发展,手机开发也在向好的方向不停的转变.IOS和Android两大手机操作横空出世,称霸江湖.我们每开发一个手机软件最少都需要开发这两个终端. 两大操作系统都在不断 ...

  9. 高二小假期集训—D5

    刚调完了一个非常恶心的题(可能是我写的太恶心了),心累……先写会博客吧. 今天上午该完了考试的三道题,感觉第二道真的是个好题(学长说是经常会遇到的一类题……完了完了),看了一个小时std才看懂,写了篇 ...

  10. SVN的使用与教程

    1.先下载SVN安装包 SVN安装教程