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

article/2025/9/28 17:17:59

目录

一、树的演变

二、红黑树

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.添加值

 //执行 put() public V put(K key, V value) {//key = "java" value = PRESENT 共享return putVal(hash(key), key, value, false, true);}//执行 putVal
final V putVal ( int hash, K key, V value,boolean onlyIfAbsent,boolean evict){Node<K, V>[] tab; Node<K, V> p;int n, i; //定义了辅助变量//table 就是 HashMap 的一个数组,类型是 Node[]//if 语句表示如果当前 table 是 null, 或者 大小=0//就是第一次扩容,到 16 个空间.if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//如果 p 为 null, 表示还没有存放元素,创建Node对象,插入if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k; ////如果当前索引位置有值,就覆盖if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))e = p;//如果是一颗红黑树,就调用 putTreeVal , 来进行添加else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//此时 table 对应索引位置,已经是一个链表, 就使用 for 循环比较for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null); //元素添加到链表后//该链表是否已经达到 8 个结点if (binCount >= TREEIFY_THRESHOLD(8) - 1) treeifyBin(tab, hash); //树化break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if(e !=null)   {// existing mapping for key V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;//size 就是我们每加入一个结点 Node(k,v,h,next), size++if (++size > threshold)resize();//扩容afterNodeInsertion(evict);return null;}

 2.插入平衡


/*** 插入平衡(分多钟情况,左旋,右旋)* @param root 当前根节点* @param x 当前要插入的节点* @return  返回根节点(平衡涉及左旋右旋会将根节点改变,所以需要返回最新的根节点) */
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {x.red = true; //首先将要插入的节点染色成红色,不要问为什么不是黑色,因为黑色会破坏黑高(黑色完美平衡)		     //xp:x的父节点,xpp:x的爷爷节点,xppl:爷爷节点的左子节点,xppr:爷爷节点的右子节点for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { //死循环,直到找到根节点才结束。if ((xp = x.parent) == null) { //如果当前插入节点的父节点为空,那么说明当前节点就是根节点,x.red = false;	//染色为黑色(根节点规定为黑色)return x;}else if (!xp.red || (xpp = xp.parent) == null) //如果爸爸节点为黑色或者爷爷节点为空(插入后不影响黑色完美平衡,直接返回)return root;if (xp == (xppl = xpp.left)) { //当前插入节点的父节点为红色,并且是爷爷节点的左子节点(有两种情况:LL或者LR)if ((xppr = xpp.right) != null && xppr.red) { //叔叔节点存在并且为红色 xppr.red = false; //将爸爸节点和叔叔节点染色成黑xp.red = false;xpp.red = true; //将爷爷节点染色成红x = xpp; //最后将爷爷节点设置为当前节点进行下一轮操作}else { //叔叔节点不存在或者为黑色if (x == xp.right) { // 当前插入节点是父节点的右子节点(LR的情景)root = rotateLeft(root, x = xp); //以爸爸节点为旋转节点进行左旋xpp = (xp = x.parent) == null ? null : xp.parent; //设置爷爷节点}if (xp != null) { //左旋完了之后,就回到了LL的情景(爸爸节点是爷爷节点的左子节点,当前节点是爸爸节点的左子节点),然后爸爸节点又是红色,当前插入节点也是红色,违反了红黑色的性质,红色不能两两相连,所以接下来需要进行染色;xp.red = false;  //将爸爸节点染色为黑if (xpp != null) {xpp.red = true; //将爷爷节点染色为红,然后在对爷爷节点右旋。root = rotateRight(root, xpp);}}}}else { //爸爸节点是爷爷节点的右子节点,爸爸节点为红色,也有两种情况(RR 或者 RL)if (xppl != null && xppl.red) { //叔叔节点不为空并且为红色xppl.red = false; //需要将爸爸节点和叔叔节点染色成黑xp.red = false;xpp.red = true; //将爷爷节点染色成红x = xpp; //并且爷爷节点设置为当前节点进行下一轮操作}else { //叔叔节点不存在或者为黑色if (x == xp.left) { //当前插入节点是爸爸节点的左子节点(RL的情景)root = rotateRight(root, x = xp); //先将爸爸节点右旋变成的RR的情景xpp = (xp = x.parent) == null ? null : xp.parent;}if (xp != null) { //这个时候已经变成了RR的情况,需要染色在意爷爷节点左旋来维持平衡xp.red = false; //将爸爸节点染色为黑if (xpp != null) {xpp.red = true; //将爷爷节点染色成红root = rotateLeft(root, xpp); //在对爷爷节点左旋}}}}}}

 3.左旋、右旋

/*** 左旋* @param root 当前根节点* @param p 指定的旋转节点* @return  返回根节点(平衡涉及左旋右旋会将根节点改变,所以需要返回最新的根节点) * 左旋示意图:左旋p节点pp                  pp|                   |p                   r/ \         ---->   / \l   r               p   rr/ \             / \rl  rr          l   rl左旋做了几件事?* 1、将rl设置为p的右子节点,将rl的父节点设置为p* 2、将r的父节点设置pp,将pp的左子节点或者右子节点设置为r* 3、将r的左子节点设置为p,将p的父节点设置为r*/
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,TreeNode<K,V> p) {// r:旋转节点的右子节点;	pp:旋转节点的父节点, rl:旋转节点的右子节点的左子节点TreeNode<K,V> r, pp, rl;if (p != null && (r = p.right) != null) { //旋转节点非空并且旋转节点的右子节点非空if ((rl = p.right = r.left) != null)  //将p节点的右子节点设置为右子节点的左子节点rl.parent = p; //将rl的父节点设置为pif ((pp = r.parent = p.parent) == null)//将r的爸爸节点设置为p的爸爸节点,如果是空的话(root = r).red = false;//染色成黑else if (pp.left == p) //判断父节点是爷爷节点的左子节点还是右子节点pp.left = r; //如果是左子节点,那么就把爷爷节点的左子节点设置为relse  pp.right = r; //如果是右子节点,就把爷爷节点的右子节点设置为rr.left = p; //最后将r的左子节点设置为pp.parent = r; //将p的爸爸节点设置为r}return root;}/*** 右旋* @param root 当前根节点* @param p 指定的旋转节点* @return  返回根节点(平衡涉及左旋右旋会将根节点改变,所以需要返回最新的根节点) * 右旋示意图:右旋p节点pp                      pp|                       |p                       l/ \          ---->      / \l   r                   ll  p/ \                         / \ll  lr                      lr  r* 右旋都做了几件事?* 1.将lr设置为p节点的左子节点,将lr的父节点设置为p* 2.将l的父节点设置为pp,将pp的左子节点或者右子节点设置为l* 3.将l的右子节点设置为p,将p的父节点设置为l*/
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,TreeNode<K,V> p) {//l:p节点的左子节点 pp:p节点的爸爸节点 lr:p节点的左子节点的右子节点TreeNode<K,V> l, pp, lr;if (p != null && (l = p.left) != null) { //旋转节点p非空并且p节点的左子节点非空if ((lr = p.left = l.right) != null) //将p节点的左子节点设置为左子节点的右子节点lr.parent = p; //然后将p节点的左子节点的右子节点的父节点设置为pif ((pp = l.parent = p.parent) == null) //将p节点的左子节点的父节点设置为p的父节点,如果为空的话,说明l就是根节点了(root = l).red = false; //染色成黑else if (pp.right == p) //判断p节点是pp节点的左子节点还是右子节点,pp.right = l; //如果p节点是pp节点的右子节点的话,将爸爸节点pp的右子节点设置为lelse //如果p节点是pp节点的左子节点的话,将爸爸节点pp的左子节点设置为lpp.left = l;l.right = p; //最后将l节点的右子节点设置为pp.parent = l; //将p节点的父节点设置为l}return root;}

http://chatgpt.dhexx.cn/article/a8BsNpWU.shtml

相关文章

HashMap底层实现原理:红黑树左旋右旋

HashMap中的红黑树左旋、右旋&#xff1a; 一、什么是左旋、右旋&#xff1a; 红黑树的性质&#xff1a; 每个节点要么是黑色&#xff0c;要么是红色根节点是黑色每个叶子节点&#xff08;NIL&#xff09;是黑色每个红色结点的两个子结点一定都是黑色任意一结点到每个叶子结点…

平衡二叉查找树(AVL)的构建——左旋右旋

原文链接&#xff1a;https://www.cnblogs.com/ZhaoxiCheung/p/6012783.html 平衡二叉树&#xff0c;又称AVL&#xff08;Adelson-Velskii和Landis&#xff09;树&#xff0c;是带有平衡条件的二叉查找树。这个平衡条件必须要容易保持&#xff0c;而且它必须保证树的深度是 O&…

详解红黑树之左旋右旋

为什么要左旋右旋&#xff1f; 为了使得左右子树的高度差在一定范围内&#xff0c;需要通过旋转调整&#xff0c;这样就可以保持平稳的搜索效率 左旋&#xff1a; 步骤 1.设原来E的父节点是father&#xff0c;那么左旋之后需要改变的是&#xff1a; 2.S和father的关系&#…

avl树左旋右旋的理解

一直没搞懂非平衡二叉树变平衡二叉树时左旋右旋&#xff0c;今天下定决心搞懂&#xff0c;然后在众多博客中终于找到了这样一篇&#xff0c;非常形象&#xff0c;记录如下&#xff1a; AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一&…

搞懂平衡二叉树的左旋右旋双旋(Java实现)

刚看到韩顺平老师的数据结构与算法对于平衡二叉树的讲解&#xff08;最后会附上地址&#xff09;&#xff0c;有如下理解&#xff0c;希望能帮助大家&#xff01;哪里需要改正的欢迎指正&#xff01; 平衡二叉树&#xff1a;一种二叉排序树&#xff08;BST Binary Sort Tree&a…

图示讲解AVL平衡二叉树的左旋和右旋

AVLTree 高度平衡的搜索二叉树 一棵平衡树&#xff0c;或是空树&#xff0c;或是具有以下性质的二叉搜索树&#xff1a;左子树和右子树都是AVL树&#xff0c;且左右子树的高度之差的绝对值不超过1。 该二叉树&#xff0c;根结点的右子树高度为3&#xff0c;左子树高度为2。…

左右旋对比

目录 前言 一&#xff0c;逆置法 1.1逆置思路 1.2逆置实现 二&#xff0c;左旋和右旋对比 右旋代码分析 特点对比 前言 之前在up主的博客里面已经有过了对于左旋的三种实现方法。当然&#xff0c;如果我们考虑到时间复杂度的问题&#xff0c;那么我们只要一种方法&#xf…

平衡二叉树的左旋右旋详解 看不懂你打我

平衡二叉树的左旋右旋 看不懂你打我 左旋右旋的操作为什么要左旋右旋左旋右旋能保持排序二叉排序树的性质吗下次写平衡二叉树的LL、RR、LR、RL。 左旋右旋的操作 1.左旋&#xff1a;对X节点左旋&#xff0c;即以X的右孩子Y为轴&#xff0c;将X节点转下来&#xff0c;变为Y的左…

二叉树的旋转,左旋和右旋

在AVLTree中经常会有以某个节点为点对子树进行旋转的操作&#xff0c;今天就和大家分享一下什么是二叉树的旋转。 这是一棵二叉树&#xff0c;上图称为图一。 将图一中的树以1为旋转点进行左旋后得到图二&#xff1a; 将图一中的树以1为旋转点进行右旋旋后得到图三&#xff1…

day062:平衡二叉树——左旋、右旋

二叉树、平衡二叉树的介绍&#xff1a;day061&#xff1a;二叉树、二叉查找树、平衡二叉树_ZQyyds&#xff1a;&#xff09;的博客-CSDN博客 目录 一、平衡二叉树的左旋 1.为什么要左旋、右旋&#xff1f; 2.什么是左旋&#xff1f; 3.图解 二、平衡二叉树的右旋 1.什么是…

大型json文件格式化【超简单】

问题 遇到了一个很大的JSON文件&#xff08;约4M&#xff09;&#xff0c;想格式化查看&#xff0c;在线json工具会直接崩溃&#xff0c;网上其他同学说的python方法之类的我也都实验失败&#xff0c;但是发现了vs code中一个超强的代码格式化 解决1&#xff08;2019.2.10更…

Unity存档系统——Json格式的文件

实例场景 点击Save按钮后&#xff0c;查看保存的文件 点击Load按钮后加载文档数据 Json介绍https://www.json.org/json-zh.htmlUnity中自带的JsonUtility可以将可序列化对象与Json格式相互转换。 将对象转为可序列化对象需要添加[SerializeField]&#xff0c;且为public&#…

将json文件格式转化成Excel表格形式

在程序员的工作中&#xff0c;经常有产品或者运营部门的小姐姐需要数据&#xff0c;由于不懂技术&#xff0c;就需要我们将Json格式的数据转换成Excel文档提供给她们进行数据分析。 本文就介绍一个非常简单方便的方法 1.运用的是Excel表格自带的功能 首先打开Excel文件 点击插…

js导出JSON格式文件

在src目录下新建tools文件价&#xff0c;在tools文件中新建index.js(文件内名字随意) 在index.js文件中 const Tools {// 导出文件exportJson(name, data) {let blob new Blob([data]); // 创建 blob 对象let link document.createElement("a");link.href URL.…

json文件

认识一下 json文件 直接使用记事本打开&#xff1a;例如猫狗二分类 一般都使用类似字典的方式存储&#xff0c;但和字典不同&#xff0c;无论是键还是值&#xff0c;都要加上双引号。 json文件的读取与写入 import jsondata {"a":"1","b":&q…

JSON格式校验工具

经常用ApiPost 和 Postman 测试自己写的接口的话&#xff0c;有时会不小心写错传进去的json对象&#xff0c;我们可以用json格式检验工具去检查一下自己json格式。 在线工具&#xff1a;在线 JSON 工具&#xff0c;JSON 校验/格式化/压缩/工具 - 在线工具-wetools.com微工具

python文件处理——JSON格式文件

python文件处理——JSON格式文件 hello&#xff01;我是wakeyo_J&#xff0c;每天一个konwledge point&#xff0c;一起学python&#xff0c;让技术无限发散。 JSON格式文件 python文件处理——JSON格式文件1. JSON格式1.1 JSON常用的两种结构数据类型1.2 JSON数据与python数据…

json文件格式详解

json文件格式详解 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式&a…

json文件格式转换为png文件格式

话不多少&#xff0c;直接上代码&#xff0c;更换源目录和目标目录即可 1、导入库 import cv2 import numpy as np import os import shutil import matplotlib.pyplot as plt 2、设置源目录/输出目录 json_dir G:/json_filedir/ #json文件所在文件夹(注&#xff1a;文…