红黑树的设计,相比 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. 分布式Jmeter

    遇到的问题 1.压力不够大 2.单台瓶颈 3.网络瓶颈 分布式系统是由一组通过网络进行通信.为了完成共同的任务而协调工作的计算机节点组成的系统.分布式系统的出现是为了用廉价的.普通的机器完成单个计算机 ...

  2. redis 如何查看版本

    ./redis-cli -h 127.0.0.1 info | grep 'redis_version' redis-server -v

  3. win10下Anaconda3配置环境变量

    有时候在win10安装好Anaconda3后,使用conda命令时依然会出现: C:\Users\dell\PycharmProjects\pytorch>conda list 'conda' ...

  4. 反射技术的入口 获取类的Class信息

    package com.sxt.reflect; import com.sxt.reflect.entity.Student; /* * 获取类的Class信息 */ public class Tes ...

  5. python 里内嵌函数是可以修改外部环境里的变量的

    python 里内嵌函数是可以修改外部环境里的变量的 关键是细节. 如果是简单变量类型, 那么不可以. 但是如果是容器类变量, 则没问题了. 代码如下: class G: pass def f(): ...

  6. 解决Python操作MySQL中文乱码的问题

    原始代码: import os, sys, string import MySQLdb MYSQL_HOST = 'localhost' MYSQL_PORT = ' MYSQL_USER = 'ro ...

  7. Ubuntu18.10创建软件图标

    解压下载包都/opt目录 创建并编辑/usr/share/applications/xxx.desktop [Desktop Entry] Encoding=UTF-8 Name=Pycharm Co ...

  8. 日志 5.27 关于AssetBundle

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/zxsean/article/details/27228783 大概日志就这么写的吧.没什么太专业的东 ...

  9. 设置WPF窗口相对于非WPF窗口的位置

    原文:设置WPF窗口相对于非WPF窗口的位置 在前一个Post当中,指出了在WPF的WindowInteropHelper类中的一个BUG:通过WindowInteropHelper的Owner属性不 ...

  10. MapReduce数据流-输入