关于红黑树,在HashMap中是怎么应用的?
关于红黑树,在HashMap中是怎么应用的?
前言
在阅读HashMap源码时,会发现在HashMap中使用了红黑树,所以需要先了解什么是红黑树,以及其原理。从而再进一步阅读HashMap中的链表到红黑树的转换,红黑树的增删节点等。
- 什么是红黑树?
- 在HashMap中是怎么应用的?
什么是红黑树?
红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它在1972年由鲁道夫·贝尔发明,被称为"对称二叉B树",它现代的名字源于Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在O(logN)时间内完成查找、插入和删除,这里的n是树中元素的数目。
红黑树的性质
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
- 节点是红色或黑色。
- 根是黑色。
- 所有叶子都是黑色(叶子是NIL节点)。
- 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
- 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
红黑树操作
左旋、右旋
插入
- 以二叉查找树的方法增加节点
- 新插入节点为红色(如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。)
注意:
- 性质1和性质3是永远保持着的。
- 性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。
- 性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。
插入时会遇到以下五种情形:
情形1:插入第一个节点
情形2:插入新节点,父节点是黑色
情形3:插入新节点,父节点是红色,叔父节点是红色
情形4:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是右子节点,父节点又是其父节点的左子节点
情形5:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是左子节点,父节点又是其父节点的左子节点。
- 情形1:
操作:插入第一个节点
违反性质2:" 根是黑色。 "
情形:直接插入红色节点,然后进行染色为黑色
- 情形2:
操作:插入新节点,父节点是黑色
未违反性质
情形:直接插入
- 情形3:
操作:插入新节点,父节点是红色,叔父节点是红色
违反性质4:" 每个红色节点必须有两个黑色的子节点。 "
情形:将祖父节点染色,祖父节点染色后再进行重新判断进行染色或旋转
- 情形4:
操作:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是右子节点,父节点又是其父节点的左子节点
违反性质4:" 每个红色节点必须有两个黑色的子节点。 "
情形:进行左旋,旋转后父节点变成左子节点,新节点变成父节点,然后重新判断进行染色或旋转
- 情形5:
操作:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是左子节点,父节点又是其父节点的左子节点。
违反性质4:" 每个红色节点必须有两个黑色的子节点。 "
情形:父节点染色为黑色,进行右旋,祖父节点变为右子节点,然后重新判断进行染色或旋转
HashMap
结构
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
// ... 省略
}
三个参数
/**
* 链表转为树阈值。
* 大于等于8时,会转换为树。
* 8 是综合性能考虑确定的值
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 从树转换为链表的阈值
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 最小树形化容量,只有哈希表元素数到达64才会进行树转换
*/
static final int MIN_TREEIFY_CAPACITY = 64;
链表转红黑树-treeifyBin
- 数组(哈希表)长度到达64
- 当链表长度大于等于8是会将链表转换为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 数组为null或者数组长度小于MIN_TREEIFY_CAPACITY(64)时,进行扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 头尾节点 hd-头 tl-尾
TreeNode<K,V> hd = null, tl = null;
do {
// 创建树节点 Node -> TreeNode
// 循环执行完之后得到的是双向链表
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
// 此时得到的仅仅是双向链表
// 指针指向链表头
if ((tab[index] = hd) != null)
// 将双向链表转换为树
hd.treeify(tab);
}
}
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (root == null) {
// 情形1:插入第一个节点
x.parent = null;
x.red = false;
root = x;
}
else {
// 当前节点的 key 和 hash
K k = x.key;
int h = x.hash;
Class<?> kc = null;
// 再次循环
for (TreeNode<K,V> p = root;;) {
int dir, ph;
// 内层循环的key
K pk = p.key;
// 当前节点的hash和内层循环的hash值作比较
if ((ph = p.hash) > h)
// < 0 left查找
dir = -1;
else if (ph < h)
// > 0 right 查找
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
// 比较对象
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
// dir <= 0 则走 left查找 > 0 则走 right查找
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 正式转换为红黑树
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
// root 根节点
// x 要操作的节点
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {
// 默认节点为红色
x.red = true;
// xp:x的父节点
// xpp:x的祖父节点
// xppl:x祖父节点的左子节点
// xppr:x祖父节点的右子节点
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
// 情形1: 父节点为null, 直接置为根
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// 父节点黑色 或者 祖父节点为空,直接返回
// 情形2:插入新节点,父节点是黑色
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 父节点是祖父节点的左子节点
if (xp == (xppl = xpp.left)) {
// 祖父节点的右子节点不为空且是红色
// 情形3:插入新节点,父节点是红色,叔父节点是红色
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false; //祖父节点的右子节点设置为黑色
xp.red = false; // 父节点设置为黑色
xpp.red = true; // 祖父节点设置为红色
x = xpp; // 继续操作祖父节点
}
// 旋转
else {
// 新插入的是右子节点
if (x == xp.right) {
// 插入的x是父节点的右子节点, 进行左旋
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
// 父节点设置为黑色
xp.red = false;
if (xpp != null) {
xpp.red = true;
// 右旋
root = rotateRight(root, xpp);
}
}
}
}
// 父节点是祖父节点的右子节点
else {
// 祖父节点的左子节点不为空且为红色
if (xppl != null && xppl.red) {
xppl.red = false; // 祖父节点的左子节点设置为黑色
xp.red = false; // 父节点设置为黑色
xpp.red = true; // 祖父节点设置为红色
x = xpp; // 继续操作祖父节点
}
// 旋转
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
关于红黑树,在HashMap中是怎么应用的?的更多相关文章
- HashMap中的TreeNode,红黑树源码分析
在看HashMap的源码时候看到了TreeNode.因此需要对其进行一个了解.是一个红黑树.可以百度一下红黑树的数据结构.分析了下源码,还是比较枯燥的 红黑树的性质:本身是一个二叉查找树(所有左节点的 ...
- jdk1.8 HashMap红黑树操作详解-putTreeVal()
以前也看过hashMap源码不过是看的jdk1.7的,由于时间问题看的也不是太深入,只是大概的了解了一下他的基本原理:这几天通过假期的时间就对jdk1.8的hashMap深入了解了下,相信大家都是对红 ...
- HashMap 的工作原理及代码实现,什么时候用到红黑树
HashMap工作原理及什么时候用到的红黑树: 在jdk 1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即has ...
- Java HashMap源码分析(含散列表、红黑树、扰动函数等重点问题分析)
写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...
- 红黑树之 原理和算法详细介绍(阿里面试-treemap使用了红黑树) 红黑树的时间复杂度是O(lgn) 高度<=2log(n+1)1、X节点左旋-将X右边的子节点变成 父节点 2、X节点右旋-将X左边的子节点变成父节点
红黑树插入删除 具体参考:红黑树原理以及插入.删除算法 附图例说明 (阿里的高德一直追着问) 或者插入的情况参考:红黑树原理以及插入.删除算法 附图例说明 红黑树与AVL树 红黑树 的时间复杂度 ...
- JDK源码那些事儿之红黑树基础下篇
说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树,上一讲已经给出插入平衡的调整 ...
- JDK源码那些事儿之红黑树基础上篇
说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树. 前言 限于篇幅,本文只对红 ...
- Java 集合 | 红黑树 | 前置知识
一.前言 0tnv1e.png 为啥要学红黑树吖? 因为笔者最近在赶项目的时候,不忘抽出时间来复习 Java 基础知识,现在准备看集合的源码啦啦.听闻,HashMap 在 jdk 1.8 的时候,底层 ...
- 红黑树(二)之 C语言的实现
概要 红黑树在日常的使用中比较常用,例如Java的TreeMap和TreeSet,C++的STL,以及Linux内核中都有用到.之前写过一篇文章专门介绍红黑树的理论知识,本文将给出红黑数的C语言的实现 ...
- 红黑树(四)之 C++的实现
概要 前面分别介绍红黑树的理论知识和红黑树的C语言实现.本章是红黑树的C++实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章. 目录1. 红黑树的介绍2. 红黑树的C++ ...
随机推荐
- Paxos 协议
可用性与一致性 为了向用户提供更好的服务体验,现代软件架构越来越注重系统的可用性availability. 正是在这种趋势的驱动下,微服务与容器化技术才能在今天大行其道. 而高可用架构的前提是冗余: ...
- DMZ是什么
刚刚接触安全域,实在是佩服自己真的是菜,,,啥都不懂,看看过段时间能有多大进步吧... 概念 DMZ:它是一个缓冲区,一个隔离区.它是位于两台防火墙之间的区域,相对于INTER网来说安全级别高一些,但 ...
- vue3.0版本安装
如果安装过其他版本的vue的话先卸载 npm uninstall -g vue-cli //卸载指令 卸载不会影响以前项目的启动 然后安装 NPM安装: npm install -g @vue/cli ...
- JavaScript动态生成表格
要求: HTML标签只写一行表头 通过JS来写动态的表格(有多少组数据,就自动创建多少行表格) 为学习和演示,采用固定的数据,不涉及调用后台数据 代码实现: HTML内容: <table cel ...
- Linux系统编程—有名管道
▋****1. 管道的概念 管道,又名「无名管理」,或「匿名管道」,管道是一种非常基本,也是使用非常频繁的IPC方式. 1.1 管道本质 管道的本质也是一种文件,不过是伪文件,实际上是一块内核缓冲区, ...
- Black-Lives-Matter-Resources
下载 Black-Lives-Matter-ResourcesBlack-Lives-Matter-Resources 关于最近在美国发生的事件的资源列表 链接 描述 由于(可选) 插入链接 在这里插 ...
- 十一长假我肝了这本超硬核PDF,现决定开源!!
写在前面 在 [冰河技术] 微信公众号中的[互联网工程]专题,更新了不少文章,有些读者反馈说,在公众号中刷 历史文章不太方便,有时会忘记自己看到哪一篇了,当打开一篇文章时,似乎之前已经看过了,但就是不 ...
- 为什么在M3架构中 PC总是返回加4
由于CPU是3级流水线的方式运行.在执行第一条指令时候,已经对第二条指令译码,对第三条指令取值. PC总是指向正在取值的指令.由于在M3架构中,采用Thumb-2指令,每个指令占据2个字节,所以PC总 ...
- win10简单方法安装杜比v4音效!win10 1909适用!
先下载这个! 链接: https://pan.baidu.com/s/1zAOOf-1aCJsjBgy36SiGWA 密码: s9n7 这个是杜比V4文件,257MB大小,适用32位64位系统!下 载 ...
- Spring Aop 详解一
Aop 是一个编程思想,最初是一个理论,最后落地成了很多的技术实现. 我们写一个系统,都希望尽量少写点儿重复的东西.而很多时候呢,又不得不写一些重复的东西.比如访问某些方法的权限,执行某些方法性能的日 ...