关于红黑树,在HashMap中是怎么应用的?

前言

在阅读HashMap源码时,会发现在HashMap中使用了红黑树,所以需要先了解什么是红黑树,以及其原理。从而再进一步阅读HashMap中的链表到红黑树的转换,红黑树的增删节点等。

  1. 什么是红黑树?
  2. 在HashMap中是怎么应用的?

什么是红黑树?

红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它在1972年由鲁道夫·贝尔发明,被称为"对称二叉B树",它现代的名字源于Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在O(logN)时间内完成查找、插入和删除,这里的n是树中元素的数目。

红黑树的性质

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

红黑树操作

左旋、右旋


插入

  1. 以二叉查找树的方法增加节点
  2. 新插入节点为红色(如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。)

注意:

  1. 性质1和性质3是永远保持着的。
  2. 性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。
  3. 性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。

插入时会遇到以下五种情形:

情形1:插入第一个节点

情形2:插入新节点,父节点是黑色

情形3:插入新节点,父节点是红色,叔父节点是红色

情形4:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是右子节点,父节点又是其父节点的左子节点

情形5:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是左子节点,父节点又是其父节点的左子节点。

  • 情形1:

操作:插入第一个节点

违反性质2:" 根是黑色。 "

情形:直接插入红色节点,然后进行染色为黑色

  • 情形2:

操作:插入新节点,父节点是黑色

未违反性质

情形:直接插入

  • 情形3:

操作:插入新节点,父节点是红色,叔父节点是红色

违反性质4:" 每个红色节点必须有两个黑色的子节点。 "

情形:将祖父节点染色,祖父节点染色后再进行重新判断进行染色或旋转

  • 情形4:

操作:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是右子节点,父节点又是其父节点的左子节点

违反性质4:" 每个红色节点必须有两个黑色的子节点。 "

情形:进行左旋,旋转后父节点变成左子节点,新节点变成父节点,然后重新判断进行染色或旋转

  • 情形5:

操作:插入新节点,父节点是红色,叔父节点是黑色或缺省,新节点是左子节点,父节点又是其父节点的左子节点。

违反性质4:" 每个红色节点必须有两个黑色的子节点。 "

情形:父节点染色为黑色,进行右旋,祖父节点变为右子节点,然后重新判断进行染色或旋转

HashMap

结构

  1. static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
  2. TreeNode<K,V> parent; // red-black tree links
  3. TreeNode<K,V> left;
  4. TreeNode<K,V> right;
  5. TreeNode<K,V> prev; // needed to unlink next upon deletion
  6. boolean red;
  7. // ... 省略
  8. }

三个参数

  1. /**
  2. * 链表转为树阈值。
  3. * 大于等于8时,会转换为树。
  4. * 8 是综合性能考虑确定的值
  5. */
  6. static final int TREEIFY_THRESHOLD = 8;
  7. /**
  8. * 从树转换为链表的阈值
  9. */
  10. static final int UNTREEIFY_THRESHOLD = 6;
  11. /**
  12. * 最小树形化容量,只有哈希表元素数到达64才会进行树转换
  13. */
  14. static final int MIN_TREEIFY_CAPACITY = 64;

链表转红黑树-treeifyBin

  1. 数组(哈希表)长度到达64
  2. 当链表长度大于等于8是会将链表转换为红黑树
  1. final void treeifyBin(Node<K,V>[] tab, int hash) {
  2. int n, index; Node<K,V> e;
  3. // 数组为null或者数组长度小于MIN_TREEIFY_CAPACITY(64)时,进行扩容
  4. if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  5. resize();
  6. else if ((e = tab[index = (n - 1) & hash]) != null) {
  7. // 头尾节点 hd-头 tl-尾
  8. TreeNode<K,V> hd = null, tl = null;
  9. do {
  10. // 创建树节点 Node -> TreeNode
  11. // 循环执行完之后得到的是双向链表
  12. TreeNode<K,V> p = replacementTreeNode(e, null);
  13. if (tl == null)
  14. hd = p;
  15. else {
  16. p.prev = tl;
  17. tl.next = p;
  18. }
  19. tl = p;
  20. } while ((e = e.next) != null);
  21. // 此时得到的仅仅是双向链表
  22. // 指针指向链表头
  23. if ((tab[index] = hd) != null)
  24. // 将双向链表转换为树
  25. hd.treeify(tab);
  26. }
  27. }
  1. final void treeify(Node<K,V>[] tab) {
  2. TreeNode<K,V> root = null;
  3. for (TreeNode<K,V> x = this, next; x != null; x = next) {
  4. next = (TreeNode<K,V>)x.next;
  5. x.left = x.right = null;
  6. if (root == null) {
  7. // 情形1:插入第一个节点
  8. x.parent = null;
  9. x.red = false;
  10. root = x;
  11. }
  12. else {
  13. // 当前节点的 key 和 hash
  14. K k = x.key;
  15. int h = x.hash;
  16. Class<?> kc = null;
  17. // 再次循环
  18. for (TreeNode<K,V> p = root;;) {
  19. int dir, ph;
  20. // 内层循环的key
  21. K pk = p.key;
  22. // 当前节点的hash和内层循环的hash值作比较
  23. if ((ph = p.hash) > h)
  24. // < 0 left查找
  25. dir = -1;
  26. else if (ph < h)
  27. // > 0 right 查找
  28. dir = 1;
  29. else if ((kc == null &&
  30. (kc = comparableClassFor(k)) == null) ||
  31. (dir = compareComparables(kc, k, pk)) == 0)
  32. // 比较对象
  33. dir = tieBreakOrder(k, pk);
  34. TreeNode<K,V> xp = p;
  35. // dir <= 0 则走 left查找 > 0 则走 right查找
  36. if ((p = (dir <= 0) ? p.left : p.right) == null) {
  37. x.parent = xp;
  38. if (dir <= 0)
  39. xp.left = x;
  40. else
  41. xp.right = x;
  42. // 正式转换为红黑树
  43. root = balanceInsertion(root, x);
  44. break;
  45. }
  46. }
  47. }
  48. }
  49. moveRootToFront(tab, root);
  50. }

  1. // root 根节点
  2. // x 要操作的节点
  3. static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {
  4. // 默认节点为红色
  5. x.red = true;
  6. // xp:x的父节点
  7. // xpp:x的祖父节点
  8. // xppl:x祖父节点的左子节点
  9. // xppr:x祖父节点的右子节点
  10. for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
  11. // 情形1: 父节点为null, 直接置为根
  12. if ((xp = x.parent) == null) {
  13. x.red = false;
  14. return x;
  15. }
  16. // 父节点黑色 或者 祖父节点为空,直接返回
  17. // 情形2:插入新节点,父节点是黑色
  18. else if (!xp.red || (xpp = xp.parent) == null)
  19. return root;
  20. // 父节点是祖父节点的左子节点
  21. if (xp == (xppl = xpp.left)) {
  22. // 祖父节点的右子节点不为空且是红色
  23. // 情形3:插入新节点,父节点是红色,叔父节点是红色
  24. if ((xppr = xpp.right) != null && xppr.red) {
  25. xppr.red = false; //祖父节点的右子节点设置为黑色
  26. xp.red = false; // 父节点设置为黑色
  27. xpp.red = true; // 祖父节点设置为红色
  28. x = xpp; // 继续操作祖父节点
  29. }
  30. // 旋转
  31. else {
  32. // 新插入的是右子节点
  33. if (x == xp.right) {
  34. // 插入的x是父节点的右子节点, 进行左旋
  35. root = rotateLeft(root, x = xp);
  36. xpp = (xp = x.parent) == null ? null : xp.parent;
  37. }
  38. if (xp != null) {
  39. // 父节点设置为黑色
  40. xp.red = false;
  41. if (xpp != null) {
  42. xpp.red = true;
  43. // 右旋
  44. root = rotateRight(root, xpp);
  45. }
  46. }
  47. }
  48. }
  49. // 父节点是祖父节点的右子节点
  50. else {
  51. // 祖父节点的左子节点不为空且为红色
  52. if (xppl != null && xppl.red) {
  53. xppl.red = false; // 祖父节点的左子节点设置为黑色
  54. xp.red = false; // 父节点设置为黑色
  55. xpp.red = true; // 祖父节点设置为红色
  56. x = xpp; // 继续操作祖父节点
  57. }
  58. // 旋转
  59. else {
  60. if (x == xp.left) {
  61. root = rotateRight(root, x = xp);
  62. xpp = (xp = x.parent) == null ? null : xp.parent;
  63. }
  64. if (xp != null) {
  65. xp.red = false;
  66. if (xpp != null) {
  67. xpp.red = true;
  68. root = rotateLeft(root, xpp);
  69. }
  70. }
  71. }
  72. }
  73. }
  74. }

关于红黑树,在HashMap中是怎么应用的?的更多相关文章

  1. HashMap中的TreeNode,红黑树源码分析

    在看HashMap的源码时候看到了TreeNode.因此需要对其进行一个了解.是一个红黑树.可以百度一下红黑树的数据结构.分析了下源码,还是比较枯燥的 红黑树的性质:本身是一个二叉查找树(所有左节点的 ...

  2. jdk1.8 HashMap红黑树操作详解-putTreeVal()

    以前也看过hashMap源码不过是看的jdk1.7的,由于时间问题看的也不是太深入,只是大概的了解了一下他的基本原理:这几天通过假期的时间就对jdk1.8的hashMap深入了解了下,相信大家都是对红 ...

  3. HashMap 的工作原理及代码实现,什么时候用到红黑树

    HashMap工作原理及什么时候用到的红黑树: 在jdk 1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即has ...

  4. Java HashMap源码分析(含散列表、红黑树、扰动函数等重点问题分析)

    写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...

  5. 红黑树之 原理和算法详细介绍(阿里面试-treemap使用了红黑树) 红黑树的时间复杂度是O(lgn) 高度<=2log(n+1)1、X节点左旋-将X右边的子节点变成 父节点 2、X节点右旋-将X左边的子节点变成父节点

    红黑树插入删除 具体参考:红黑树原理以及插入.删除算法 附图例说明   (阿里的高德一直追着问) 或者插入的情况参考:红黑树原理以及插入.删除算法 附图例说明 红黑树与AVL树 红黑树 的时间复杂度 ...

  6. JDK源码那些事儿之红黑树基础下篇

    说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树,上一讲已经给出插入平衡的调整 ...

  7. JDK源码那些事儿之红黑树基础上篇

    说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树. 前言 限于篇幅,本文只对红 ...

  8. Java 集合 | 红黑树 | 前置知识

    一.前言 0tnv1e.png 为啥要学红黑树吖? 因为笔者最近在赶项目的时候,不忘抽出时间来复习 Java 基础知识,现在准备看集合的源码啦啦.听闻,HashMap 在 jdk 1.8 的时候,底层 ...

  9. 红黑树(二)之 C语言的实现

    概要 红黑树在日常的使用中比较常用,例如Java的TreeMap和TreeSet,C++的STL,以及Linux内核中都有用到.之前写过一篇文章专门介绍红黑树的理论知识,本文将给出红黑数的C语言的实现 ...

  10. 红黑树(四)之 C++的实现

    概要 前面分别介绍红黑树的理论知识和红黑树的C语言实现.本章是红黑树的C++实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章. 目录1. 红黑树的介绍2. 红黑树的C++ ...

随机推荐

  1. P5035金坷垃题解(快速幂的讲解)

      首先经过读题,我们发现找到合格的金坷垃,怎么样的金坷垃才是合格的呢?(我们不难发现1肯定是合格的[题目已经给出了]) 然后我们开始手推一下之后合格的金坷垃: 2-1=1(合格) 3-1-1=1(不 ...

  2. 基础篇:深入解析JAVA泛型和Type类型体系

    目录 1 JAVA的Type类型体系 2 泛型的概念 3 泛型类和泛型方法的示例 4 类型擦除 5 参数化类型ParameterizedType 6 泛型的继承 7 泛型变量TypeVariable ...

  3. 072 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 04 综合案例-数组移位-在指定位置处插入数据方法

    072 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 04 综合案例-数组移位-在指定位置处插入数据方法 本文知识点:综合案例-数组移位-在指定位置处插入数据方法 ...

  4. 达梦产品技术支持培训-day7-DM8数据库备份与还原-原理

    (本文部分内容摘自DM产品技术支持培训文档,如需要更详细的文档,请查询官方操作手册,谢谢) 1.DM8备份还原简介 1.1.基本概念 (1)表空间与数据文件 ▷ DM8表空间类型: ▷ SYSTEM ...

  5. [学习笔记] Treap

    想必大家都知道一种叫做二叉搜索树这东西吧,那么我们知道,在某些特殊情况下,二叉搜索树会退化成一条链,而且如果出题人成心想卡你的话也很简单,分分钟把你(n log n)的期望卡成.那么我们该如何避免这种 ...

  6. Docker笔记4:在 CentOS 上安装 Docker

    Docker 是一个开源的应用容器引擎,主要有两个分支,一个是社区免费版(Docker CE),一个是企业版(Docker EE). 第1步:系统环境要求 Docker 支持的 CentOS 版本: ...

  7. Python+Appium自动化测试(14)-yaml配置Desired capabilities

    一,前言 在之前的appium自动化测试示例中,我们都是把构造driver实例对象的数据(即Desired Capabilities)写在业务代码里,如下: # -*- coding:utf-8 -* ...

  8. 超好用的UnixLinux 命令技巧 大神为你详细解读

    1.删除一个大文件 我在生产服务器上有一个很大的200GB的日志文件需要删除.我的rm和ls命令已经崩溃,我担心这是由于巨大的磁盘IO造成的,要删除这个大文件,输入: > /path/to/fi ...

  9. spring boot:用spring security加强spring boot admin的安全(spring boot admin 2.3.0 / spring boot 2.3.3)

    一,spring boot admin的安全环节: 1,修改context-path,默认时首页就是admin, 我们修改这个地址可以更安全 2,配置ip地址白名单,有ip限制才安全, 我们使用了sp ...

  10. solr之functionQuery(函数查询)【转】

    函数查询 让我们可以利用 numeric域的值 或者 与域相关的的某个特定的值的函数,来对文档进行评分. 怎样使用函数查询 这里主要有两种方法可以使用函数查询,这两种方法都是通过solr http 接 ...