• HashMap-红黑树插入平衡、左旋、右旋源码解析


    目录

    一、树的演变

    二、红黑树

    1.红黑树的特点

    2.树左旋右旋的过程 

    3.红黑树插入节点情景分析:

    三、HashMap插入平衡、左旋、右旋源码解析

     1.添加值

     2.插入平衡

     3.左旋、右旋


    一、树的演变

    为什么会有,因为链表的查询效率是logOn,树的查询效率是Log2n。

    为什么会有二叉搜索树,在查找某个值的时候,一共就只有三种结果,大于,小于,等于,根据这三种情况并设计出了二叉查找树,为了方便检索。

    为什么会有平衡二叉搜索树(AVL),因为二叉搜索树会退化成一个链表。变成单支树

    为什么会后红黑树,因为AVL树在插入或者删除数据的时候,为了保证树结构的严格的平衡性,就需要经常的去调整树的结构,所以插入数据的效率是比较低的,反之获取数据的效率比较高。红黑树结构上比较平衡,对平衡的要求确没有自平衡二叉搜索树那么高

    红黑树平衡的代价较低, 其平均统计性能要强于 AVL

    二、红黑树

    1.红黑树的特点

    性质1. 结点是红色或黑色。

    性质2. 根结点是黑色。

    性质3. 所有叶子都是黑色。(叶子是NIL结点)

    性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)

    性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。(黑色是平衡的)

    总结红黑树是特殊点AVL树,黑根黑叶红不邻,同祖等高只数黑

    2.树左旋右旋的过程 

    左旋:

    右旋:左旋的逆过程

     

    3.红黑树插入节点情景分析:

    数据结构学习之树与红黑树(java1.8 hashMap底层实现)_倔强的耗子的博客-CSDN博客

    三、HashMap插入平衡、左旋、右旋源码解析

     putval():添加值

     resize():扩容

     treeifyBin():树化,节点变为TreeNode

     //插入平衡,左旋,右旋的过程其实就是染色(赋值)+改变节点的指针

     balanceInsertion():插入平衡

     rotateLeft() :左旋

     rotateRight :右旋

     1.添加值

    1. //执行 put()
    2. public V put(K key, V value) {//key = "java" value = PRESENT 共享
    3. return putVal(hash(key), key, value, false, true);
    4. }
    5. //执行 putVal
    6. final V putVal ( int hash, K key, V value,boolean onlyIfAbsent,
    7. boolean evict){
    8. Node[] tab; Node p;int n, i; //定义了辅助变量
    9. //table 就是 HashMap 的一个数组,类型是 Node[]
    10. //if 语句表示如果当前 table 是 null, 或者 大小=0
    11. //就是第一次扩容,到 16 个空间.
    12. if ((tab = table) == null || (n = tab.length) == 0)
    13. n = (tab = resize()).length;
    14. //如果 p 为 null, 表示还没有存放元素,创建Node对象,插入
    15. if ((p = tab[i = (n - 1) & hash]) == null)
    16. tab[i] = newNode(hash, key, value, null);
    17. else {
    18. Node e; K k; //
    19. //如果当前索引位置有值,就覆盖
    20. if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
    21. e = p;
    22. //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
    23. else if (p instanceof TreeNode)
    24. e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
    25. else {
    26. //此时 table 对应索引位置,已经是一个链表, 就使用 for 循环比较
    27. for (int binCount = 0; ; ++binCount) {
    28. if ((e = p.next) == null) {
    29. p.next = newNode(hash, key, value, null); //元素添加到链表后
    30. //该链表是否已经达到 8 个结点
    31. if (binCount >= TREEIFY_THRESHOLD(8) - 1)
    32. treeifyBin(tab, hash); //树化
    33. break;
    34. }
    35. if (e.hash == hash &&
    36. ((k = e.key) == key || (key != null && key.equals(k))))
    37. break;
    38. p = e;
    39. }
    40. }
    41. if(e !=null) {
    42. // existing mapping for key V oldValue = e.value;
    43. if (!onlyIfAbsent || oldValue == null)
    44. e.value = value;
    45. afterNodeAccess(e);
    46. return oldValue;
    47. }
    48. }
    49. ++modCount;
    50. //size 就是我们每加入一个结点 Node(k,v,h,next), size++
    51. if (++size > threshold)
    52. resize();//扩容
    53. afterNodeInsertion(evict);
    54. return null;
    55. }

     2.插入平衡

    1. /**
    2. * 插入平衡(分多钟情况,左旋,右旋)
    3. * @param root 当前根节点
    4. * @param x 当前要插入的节点
    5. * @return 返回根节点(平衡涉及左旋右旋会将根节点改变,所以需要返回最新的根节点)
    6. */
    7. static TreeNode balanceInsertion(TreeNode root,TreeNode x) {
    8. x.red = true; //首先将要插入的节点染色成红色,不要问为什么不是黑色,因为黑色会破坏黑高(黑色完美平衡)
    9. //xp:x的父节点,xpp:x的爷爷节点,xppl:爷爷节点的左子节点,xppr:爷爷节点的右子节点
    10. for (TreeNode xp, xpp, xppl, xppr;;) { //死循环,直到找到根节点才结束。
    11. if ((xp = x.parent) == null) { //如果当前插入节点的父节点为空,那么说明当前节点就是根节点,
    12. x.red = false; //染色为黑色(根节点规定为黑色)
    13. return x;
    14. }
    15. else if (!xp.red || (xpp = xp.parent) == null) //如果爸爸节点为黑色或者爷爷节点为空(插入后不影响黑色完美平衡,直接返回)
    16. return root;
    17. if (xp == (xppl = xpp.left)) { //当前插入节点的父节点为红色,并且是爷爷节点的左子节点(有两种情况:LL或者LR)
    18. if ((xppr = xpp.right) != null && xppr.red) { //叔叔节点存在并且为红色
    19. xppr.red = false; //将爸爸节点和叔叔节点染色成黑
    20. xp.red = false;
    21. xpp.red = true; //将爷爷节点染色成红
    22. x = xpp; //最后将爷爷节点设置为当前节点进行下一轮操作
    23. }
    24. else { //叔叔节点不存在或者为黑色
    25. if (x == xp.right) { // 当前插入节点是父节点的右子节点(LR的情景)
    26. root = rotateLeft(root, x = xp); //以爸爸节点为旋转节点进行左旋
    27. xpp = (xp = x.parent) == null ? null : xp.parent; //设置爷爷节点
    28. }
    29. if (xp != null) { //左旋完了之后,就回到了LL的情景(爸爸节点是爷爷节点的左子节点,当前节点是爸爸节点的左子节点),然后爸爸节点又是红色,当前插入节点也是红色,违反了红黑色的性质,红色不能两两相连,所以接下来需要进行染色;
    30. xp.red = false; //将爸爸节点染色为黑
    31. if (xpp != null) {
    32. xpp.red = true; //将爷爷节点染色为红,然后在对爷爷节点右旋。
    33. root = rotateRight(root, xpp);
    34. }
    35. }
    36. }
    37. }
    38. else { //爸爸节点是爷爷节点的右子节点,爸爸节点为红色,也有两种情况(RR 或者 RL)
    39. if (xppl != null && xppl.red) { //叔叔节点不为空并且为红色
    40. xppl.red = false; //需要将爸爸节点和叔叔节点染色成黑
    41. xp.red = false;
    42. xpp.red = true; //将爷爷节点染色成红
    43. x = xpp; //并且爷爷节点设置为当前节点进行下一轮操作
    44. }
    45. else { //叔叔节点不存在或者为黑色
    46. if (x == xp.left) { //当前插入节点是爸爸节点的左子节点(RL的情景)
    47. root = rotateRight(root, x = xp); //先将爸爸节点右旋变成的RR的情景
    48. xpp = (xp = x.parent) == null ? null : xp.parent;
    49. }
    50. if (xp != null) { //这个时候已经变成了RR的情况,需要染色在意爷爷节点左旋来维持平衡
    51. xp.red = false; //将爸爸节点染色为黑
    52. if (xpp != null) {
    53. xpp.red = true; //将爷爷节点染色成红
    54. root = rotateLeft(root, xpp); //在对爷爷节点左旋
    55. }
    56. }
    57. }
    58. }
    59. }
    60. }

     3.左旋、右旋

    1. /**
    2. * 左旋
    3. * @param root 当前根节点
    4. * @param p 指定的旋转节点
    5. * @return 返回根节点(平衡涉及左旋右旋会将根节点改变,所以需要返回最新的根节点)
    6. * 左旋示意图:左旋p节点
    7. pp pp
    8. | |
    9. p r
    10. / \ ----> / \
    11. l r p rr
    12. / \ / \
    13. rl rr l rl
    14. 左旋做了几件事?
    15. * 1、将rl设置为p的右子节点,将rl的父节点设置为p
    16. * 2、将r的父节点设置pp,将pp的左子节点或者右子节点设置为r
    17. * 3、将r的左子节点设置为p,将p的父节点设置为r
    18. */
    19. static TreeNode rotateLeft(TreeNode root,TreeNode p) {
    20. // r:旋转节点的右子节点; pp:旋转节点的父节点, rl:旋转节点的右子节点的左子节点
    21. TreeNode r, pp, rl;
    22. if (p != null && (r = p.right) != null) { //旋转节点非空并且旋转节点的右子节点非空
    23. if ((rl = p.right = r.left) != null) //将p节点的右子节点设置为右子节点的左子节点
    24. rl.parent = p; //将rl的父节点设置为p
    25. if ((pp = r.parent = p.parent) == null)//将r的爸爸节点设置为p的爸爸节点,如果是空的话
    26. (root = r).red = false;//染色成黑
    27. else if (pp.left == p) //判断父节点是爷爷节点的左子节点还是右子节点
    28. pp.left = r; //如果是左子节点,那么就把爷爷节点的左子节点设置为r
    29. else
    30. pp.right = r; //如果是右子节点,就把爷爷节点的右子节点设置为r
    31. r.left = p; //最后将r的左子节点设置为p
    32. p.parent = r; //将p的爸爸节点设置为r
    33. }
    34. return root;
    35. }
    36. /**
    37. * 右旋
    38. * @param root 当前根节点
    39. * @param p 指定的旋转节点
    40. * @return 返回根节点(平衡涉及左旋右旋会将根节点改变,所以需要返回最新的根节点)
    41. * 右旋示意图:右旋p节点
    42. pp pp
    43. | |
    44. p l
    45. / \ ----> / \
    46. l r ll p
    47. / \ / \
    48. ll lr lr r
    49. * 右旋都做了几件事?
    50. * 1.将lr设置为p节点的左子节点,将lr的父节点设置为p
    51. * 2.将l的父节点设置为pp,将pp的左子节点或者右子节点设置为l
    52. * 3.将l的右子节点设置为p,将p的父节点设置为l
    53. */
    54. static TreeNode rotateRight(TreeNode root,TreeNode p) {
    55. //l:p节点的左子节点 pp:p节点的爸爸节点 lr:p节点的左子节点的右子节点
    56. TreeNode l, pp, lr;
    57. if (p != null && (l = p.left) != null) { //旋转节点p非空并且p节点的左子节点非空
    58. if ((lr = p.left = l.right) != null) //将p节点的左子节点设置为左子节点的右子节点
    59. lr.parent = p; //然后将p节点的左子节点的右子节点的父节点设置为p
    60. if ((pp = l.parent = p.parent) == null) //将p节点的左子节点的父节点设置为p的父节点,如果为空的话,说明l就是根节点了
    61. (root = l).red = false; //染色成黑
    62. else if (pp.right == p) //判断p节点是pp节点的左子节点还是右子节点,
    63. pp.right = l; //如果p节点是pp节点的右子节点的话,将爸爸节点pp的右子节点设置为l
    64. else //如果p节点是pp节点的左子节点的话,将爸爸节点pp的左子节点设置为l
    65. pp.left = l;
    66. l.right = p; //最后将l节点的右子节点设置为p
    67. p.parent = l; //将p节点的父节点设置为l
    68. }
    69. return root;
    70. }
  • 相关阅读:
    CentOS系统利用kickstart自动生成工具通过图形化配置的方式生成ks.cfg文件
    QT(超详细从0开始)
    ElasticSearch-全文检索和分析引擎学习Day01
    ES 生命周期管理
    【Netty 从成神到升仙系列 三】Netty 凭什么成为国内最流行的网络通信框架?
    Java项目:电器商城系统(java+SSM+JSP+jQuery+javascript+Mysql)
    jenkins声明式流水线pipline深入学习
    如何生成和调试 Linux 程序崩溃产生的 core 文件
    Django 实现连续请求
    (尚硅谷)JavaWeb新版教程10-书城项目的实现(第二部分)
  • 原文地址:https://blog.csdn.net/promsing/article/details/126555227