HashMap之红黑树
红黑树的设计,相比 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之红黑树的更多相关文章
- 怎样的操作才能让HashMap以红黑树类型存储数据? (文中没有解答该问题)
怎样才能让HashMap以红黑树类型存储数据? 看上面的代码可知:如果一个Node的长度大于等于7.就会触发Node转TreeNode的操作. 我向一个map中插入了一百万条数据(插入一亿条时,内存溢 ...
- 为什么HashMap使用红黑树而不使用AVL树
为什么HashMap使用红黑树而不使用AVL树? 红黑树适用于大量插入和删除:因为它是非严格的平衡树:只要从根节点到叶子节点的最长路径不超过最短路径的2倍,就不用进行平衡调节 AVL 树是严格的平衡树 ...
- 关于JDK1.7+中HashMap对红黑树场景的思考
背景 在1.7之前的版本,当数组元素较多(几百.几千,或者更多)的时候,在这种前提扩容,涉及全量元素的遍历和坐标的重新定位,这个耗时会比较长.这是之前存在的一个弊端吧.那么引入红黑树之后就解决了问题, ...
- 浅析Java源码之HashMap外传-红黑树Treenode(已鸽)
(这篇文章暂时鸽了,有点理解不能,点进来的小伙伴可以撤了) 刚开始准备在HashMap中直接把红黑树也过了的,结果发现这个类不是一般的麻烦,所以单独开一篇. 由于红黑树之前完全没接触过,所以这篇博客相 ...
- java随笔——HashMap与红黑树
前言: hashmap是一种很常用的数据结构,其使用方便快捷,接下来笔者将给大家深入解析这个数据结构,让大家能在用的时候知其然,也知其所以然. 一.Map 首先,从最基本的讲起,我们先来认识一下map ...
- jdk1.8源码解析:HashMap底层数据结构之链表转红黑树的具体时机
本文从三个部分去探究HashMap的链表转红黑树的具体时机: 一.从HashMap中有关“链表转红黑树”阈值的声明: 二.[重点]解析HashMap.put(K key, V value)的源码: 三 ...
- HashMap 链表和红黑树的转换
HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树:若桶中元素小于等于6时,树结构还原成链表形式. 原因: 红黑树的平均查找长度是log(n),长度为8 ...
- JAVA中的数据结构 - 1,红黑树
背景: 在JDK源码中, 有treeMap和JDK8的HashMap都用到了红黑树去存储 红黑树可以看成B树的一种: 二叉树-->搜索二叉树-->平衡搜索二叉树-->B树--> ...
- JAVA中的数据结构 - 真正的去理解红黑树
一, 红黑树所处数据结构的位置: 在JDK源码中, 有treeMap和JDK8的HashMap都用到了红黑树去存储 红黑树可以看成B树的一种: 从二叉树看,红黑树是一颗相对平衡的二叉树 二叉树--&g ...
随机推荐
- react-jd-index
看见一些代码的产物,会觉得非常的漂亮~感谢无私开源的程序员们~你们是最可爱的人儿~~ //index.jsx require('./app/lib/common.css'); import React ...
- openjudge dp水题记录
当发现自己竟然不会打dp的时候内心是崩溃的,然后按照一年前的刷题记录刷openjudge,然后发现自己准确率比一年前(刚学信竞两个月时)的准确率低得多,已经没救. 列一下最近打的几道sb题 2985: ...
- PLAY2.6-SCALA(六) 异步处理结果
1.创建异步的controller Play是一个自底向上的异步框架,play处理所有的request都是异步.非阻塞的.默认的方式是使用异步的controller.换句话说,contrller中的应 ...
- 【错误收集】SVN冲突解决 标签: 错误收集 2016-03-13 08:44 624人阅读 评论(24) 收藏
最近在倒代码,这真的是一件挺低效率的事情的,但是为了之后工作的进行,必须把这些已经做好的界面,做好的功能搬到新的框架上来,所以安排了10来个同学一起倒代码,因为大家共用一个解决方案,所以使用svn来进 ...
- LeetcCode102 Binary Tree Level Order Traversal
Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, ...
- Python多版本pip安装库的问题
引 机器上总是会有Python2.7的版本和Python3.x的版本,今天接触到一台服务器上面有Python2.7和Python3.4,想在Python3.4下安装一个TensorFlow,但不管怎么 ...
- Microsoft.SQL.Server2012.Performance.Tuning.Cookbook学习笔记(二)
Creating trace with system stored procedures Following are the stored procedures which you should kn ...
- oracle函数 REPLACE(c1,c2[,c3])
[功能]将字符表达式值中,部分相同字符串,替换成新的字符串 [参数] c1 希望被替换的字符或变量 c2 被替换的字符串 c3 要替换的字符串,默认为空(即删除之意,不是空格) [返回]字 ...
- UITableView 刷新问题
遇到的问题: 在程序里异步请求服务器后回调函数中处理数据和界面数据的刷新,但是更新UITableView的时候总是很慢才更新完,打印TableView的代理方法也都很快打印. 解决办法就是: [sel ...
- GitHub 上 10 款免费开源 Windows 工具
GitHub 上 10 款免费开源 Windows 工具 GitHub 是如今所有开源事物的中央仓库, 这个网站最近发布了一个叫做<2016 Octoverse 状态报告>,详细列出了从 ...