Java HashMap红黑树学习

Java HashMap红黑树学习

    • 一、红黑树介绍
    • 二、红黑树的基本操作
      • 2.1 旋转
        • 2.1.1 左旋
        • 2.1.2 右旋
      • 2.2 添加
      • 2.3 删除

一、红黑树介绍

在这里插入图片描述

1)红黑树(Red-Black Tree,简称R-B Tree),是一种特殊的平衡二叉查找树。
(2)节点非黑即红
(3)根节点是黑的
(4)叶子节点也是黑的 [注意:这里叶子节点,是指为空的叶子节点!]5)如果一个节点是红色的,则它的子节点必须是黑色的。
(6)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。(确保没有一条路径   	会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。)
(7)时间复杂度:O(log n)

二、红黑树的基本操作

2.1 旋转

旋转的目的是让树保持红黑树的特性:

  • 添加或删除红黑树中的节点之后,红黑树就发生变化,可能不满足红黑树的5条性质,就不再是红黑树,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。
  • 旋转包括两种:左旋 和 右旋。
2.1.1 左旋

在这里插入图片描述
变为
在这里插入图片描述
把主节点E左旋变成左节点,右节点S左旋变成主节点

 static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                          TreeNode<K,V> p) {
    TreeNode<K,V> y, pp, yl;
   	// 1. 先检查 P 是不是空,并且 P的右边有东西
    if (p != null && (y = p.right) != null) {
    	// 2. P 的右指针指向了 Y 的左节点
        if ((yl = p.right = y.left) != null)
        	// 3.原先 Y 的左节点换父节点 
            yl.parent = p;
        // 4. Y 的父节点换成了 P 的父节点。
        if ((pp = y.parent = p.parent) == null)
        	// 假如 “原先 root 指向 P”,进入此代码
            (root = y).red = false;
        // 5.  顶部父节点换孩子节点(原先孩子节点是 P, 现在要变成 Y)
        // 先判断原先孩子节点P到底是左孩子节点,还是右孩子节点,然后再替换
        else if (pp.left == p)
        	// 原先是左孩子节点
            pp.left = y;
        else
 			// 原先是右孩子节点
            pp.right = y;
        // 6. Y 的左孩子节点变成了 P
        y.left = p;
        // 7. P  的父节点变成了 Y
        p.parent = y;
    }
    return root;
}
2.1.2 右旋

在这里插入图片描述
变成
在这里插入图片描述同理:

  static <K,V> HashMap.TreeNode<K,V> rotateRight(HashMap.TreeNode<K,V> root,
                                                   HashMap.TreeNode<K,V> p) {
        HashMap.TreeNode<K,V> x, pp, lr;
        // 1. 先检查 P 是否为空,并且检查 P 是否有左孩子节点
        if (p != null && (x = p.left) != null) {
        	// 2. P 的左指针指向了 X 的右孩子节点
            if ((lr = p.left = x.right) != null)
            	// 3. 原先 X 的右节点换父节点 
                lr.parent = p;
            // 4. X 的父节点换成了 P 的父节点
            if ((pp = x.parent = p.parent) == null)
            	 // 假如 “原先 root 指向 P”,进入此代码
                (root = x).red = false;
            else if (pp.right == p)
                // 原先是右孩子节点
                pp.right = x;
            else
                // 原先是左孩子节点
                pp.left = x;
            // 6. X 的右孩子节点变成 P
            x.right = p;
            // 7. P 的父节点变成了 X
            p.parent = x;
        }
        return root;
    }

2.2 添加

  • 将红黑树当作一颗二叉查找树,将节点插入。
  • 将插入的节点着色为"红色"。
  • 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
/* 
 * 将结点插入到红黑树中
 *
 * 参数说明:
 *     node 插入的结点
 */
private void insert(RBTNode<T> node) {
	// 1. 设置节点的颜色为红色
    node.color = RED;
    
    int cmp; // 节点的比较结果 0 1 -1
    RBTNode<T> y = null; // 缓冲使用,记录上一个节点
    RBTNode<T> x = this.mRoot;

    // 2. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
    while (x != null) { // 遍历到最后,没有左孩子或者右孩子了,退出
        y = x;// 缓冲使用,记录上一个节点
        cmp = node.key.compareTo(x.key);
        if (cmp < 0)
            x = x.left; // node 比 X 小,那么往左边遍历
        else
            x = x.right;// node 比 X 大,那么往右边遍历
    }
	
	//已经找到并且退出,此时认爸爸,指向了上一个节点 y
    node.parent = y;
    if (y!=null) { // 为 null 表明是最顶部了
        cmp = node.key.compareTo(y.key); // 看下插入的节点是y的左孩子还是右孩子
        if (cmp < 0)
            y.left = node;
        else
            y.right = node;
    } else {
        this.mRoot = node;
    }

    // 3. 将它重新修正为一颗二叉查找树
    insertFixUp(node);
}

2.3 删除

删除节点的情形:

1)被删除节点没有孩子节点,即为叶节点。【直接将该节点删除即可】
(2)被删除节点只有一个孩子节点。【直接删除该节点,并用该节点的唯一子节点顶替它的位置】
(3)被删除节点有两个孩子节点。【先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。】
/**
 *  @param map 用于树化或者链化
 *  @param tab 桶数组,该 node 位于某一个桶内
 */
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
	    // ====== section 1:通过prev和next删除当前节点 ======

	    int n;
	    if (tab == null || (n = tab.length) == 0)
	        return;
	    // 获取桶 index 
	    int index = (n - 1) & hash; // 用与字符取余,看我之前的文章
	    // first 指向了桶的第一个元素,可能是链表头,也可能是树的 root节点 ,此处是树的根节点
	    TreeNode<K,V> first = (TreeNode<K,V>)tab[index],  rl;
	    TreeNode<K,V> root = first; // root 指向了 first
	    TreeNode<K,V> rl; // 
	    // succ 指向了 Node 节点的后继节点 next,对于二叉查找树来说,就是右子树的最左节点 。
	    TreeNode<K,V> succ = (TreeNode<K,V>)next;
	    // pred 指向了 this节点的前驱节点
	    TreeNode<K,V> pred = prev;
	    // 如果前驱结点是 null,那么他肯定是root节点
	    if (pred == null)
	    	// 首先 first先指向 succ,也就是this节点的后继节点next
	    	// 然后 tab[index] 指向了 first指向的位置,也就是指向了 this节点的 next 
	        tab[index] = first = succ; // 此处说明一下,假如 succ为空,那么代表这棵树啥都没了
	    else
	    	// 不是root节点,那么前驱结点的next后继指针指向了this的后继节点,意思就是断开了this节点
	        pred.next = succ;
	    // 看上面 succ 原本指向了 this节点的后继节点 next,如果不为空,说明 succ 是一个节点
	    if (succ != null)
	    	// this的后继节点的 prev前驱指针指向了 this节点的前驱节点,照样是断开了 this节点
	        succ.prev = pred;
	    // 这棵树啥都不剩下了。直接 return
	    if (first == null)
	    	// ************ 此处有 return ************
	        return;

	    // ====== section 2:当节点数量小于7时转换成链栈的形式存储  ====== 

	    // root的爸爸不是null,那证明不是根节点,那么继续往上面找上去,直到找到真正的 root
	    if (root.parent != null)
	        root = root.root();
	    // 树是空
	    if (root == null
	    		// 可以移动 并且
                || (movable
                	// root节点的右子树是空,
                    && (root.right == null
                    	// 或者root节点的左子树是空 ,rl指向了左子树
                        || (rl = root.left) == null
                        // 或者左子树的左子树是空
                        || rl.left == null))) {
                tab[index] = first.untreeify(map);  // too small ,此轮条件不判断树种节点的总数量,只判断根的左右子树是否符合链化的条件
                // ************ 此处有 return ************
                return;
            }

	    //  ======  section 3:判断当前树节点情况  ====== 
	    // p 指向当前节点, pl 当前Node的左子树,pr当前Node的右子树
	    TreeNode<K,V> p = this, pl = left, pr = right, replacement;
	    // 有俩孩子,属于情形三,那么就必须找到右子树的最左节点,也就是转化为情形1或者情形2
	    if (pl != null && pr != null) {
	    	// s 指向thisNode 的右子树
	        TreeNode<K,V> s = pr, sl;
	        // s 是为了找到最左节点
	        while ((sl = s.left) != null) // find successor
	            s = sl;
	        boolean c = s.red; s.red = p.red; p.red = c; // swap colors todo
	        // sr 指向了最左节点的右子树,因为 s已经是最左节点了,所以s肯定没有左子树
	        TreeNode<K,V> sr = s.right;
	        // pp 指向了 p的父亲(p在上面指向了this节点)
	        TreeNode<K,V> pp = p.parent;
	        // s是当前节点右子树的最左节点,pr是当前节点的右子树,s == pr 说明右子树的最左节点就是当前节点的右子树
	        // demo:看上图的红黑树,假如要删除“18”,那么“18”的s 右子树的最左节点是“19”,pr是当前节点的右子树也是“19”,那么他们俩互相换位置
	        if (s == pr) { // p was s's direct parent
	        	// 那么互换位置
	            p.parent = s;
	            s.right = p;
	        }
	        // 如果不是上面那个情况,比如此时要删除的是节点“18”,那么	
	        else {
	        	// s是当前节点右子树的最左节点,此时s是节点“15”
	        	// sp是节点“15”的爸爸节点“16”,那么此时要把 节点“18” 和s 节点“15”换个位置
	            TreeNode<K,V> sp = s.parent;
	            // 当前节点与右子树的最左节点换位置
	            // 当前节点换爸爸
	            if ((p.parent = sp) != null) {
	            	// 替换元素换儿子
	            	// s 是 sp的左孩子,
	                if (s == sp.left)
	                    sp.left = p;
	                // s 是 sp的右孩子    
	                else
	                    sp.right = p;
	            }
	            // s的右指针指向了原先节点的右子树,也就是接手他的右子树
	            if ((s.right = pr) != null)
	            	// 原本的右子树指针换个爸爸
	                pr.parent = s;
	        }
	        // 当前这个要删除的节点已经换到右子树的最左节点了,那么他肯定没有左孩子
	        p.left = null;
	        // 假如删除的节点还有右孩子,那么就接住
	        // 比如要删除的节点是“4”,那么把“4”和“5”换位置之后,原先“4”的右子树还是得接住
	        if ((p.right = sr) != null)
	            sr.parent = p;
	        // s目前已经是替换位置完毕的了,已经上位的了,如果s有左子树,那么接住
	        if ((s.left = pl) != null)
	            pl.parent = s;
	        // s目前已经是替换位置完毕的了,如果他的爸爸是null,那么s就是根节点root
	        if ((s.parent = pp) == null)
	            root = s;
	        // 如果一开始要删除的节点p位于他父亲pp的 左边,那么接住
	        else if (p == pp.left)
	            pp.left = s;
	        // 如果一开始要删除的节点p位于他父亲pp的 右边,那么也接住
	        else 
	            pp.right = s;
	        // 如果“替换节点”有右孩子,那么替换他的右边孩子。
	        // 比如上图,你要删除的是p节点“14”,那么找到右子树的最左节点s是“11”,那么此时要把他的右孩子“13”给换上去
	        if (sr != null)
	            replacement = sr;
	        else
	        	// 如果“替换节点”没有右孩子,那么替换节点就是她自己
	        	// 比如上图,你要删除的p节点是“6”,由于他没有右边的孩子,所以把“5”直接换了即可
	            replacement = p;
	    }
	    // 情形1,只有一个孩子,那么直接替换即可
	    else if (pl != null)
	        replacement = pl;
	    // 情形1,只有一个孩子,那么直接替换即可
	    else if (pr != null)
	        replacement = pr;
	    // 没有左子树 并且没有右子树 ,那么属于情形1,直接删除该节点即可
	    else
	        replacement = p;

	    //  ======  section 4:实现删除树节点逻辑 ======  

	    if (replacement != p) {
	        TreeNode<K,V> pp = replacement.parent = p.parent;
	        if (pp == null)
	            root = replacement;
	        else if (p == pp.left)
	            pp.left = replacement;
	        else
	            pp.right = replacement;
	        p.left = p.right = p.parent = null;
	    }

	    TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

	    if (replacement == p) {  // detach
	        TreeNode<K,V> pp = p.parent;
	        p.parent = null;
	        if (pp != null) {
	            if (p == pp.left)
	                pp.left = null;
	            else if (p == pp.right)
	                pp.right = null;
	        }
	    }
	    if (movable)
	        moveRootToFront(tab, r);
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/798381.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于正点原子imx6ull串口实验,打开串口软件后无反应

我在某多多买了俩读卡器才1.3&#xff0c;不得不说真便宜。买的是2.0给我发的是3.0.具体是真假我也不太清楚。 反正连上之后发现烧写程序后一直串口没反应&#xff0c;但是串口显示的是绿标&#xff0c;也就代表硬件没问题。 然后我跟着按了几下依旧没啥反应&#xff0c;突然…

自学鸿蒙HarmonyOS的ArkTS语言<九>自定义弹窗组件CustomDialog及二次封装自定义弹窗

一、自定义弹窗 CustomDialog struct CustomDialogBuilder {controller: CustomDialogController new CustomDialogController({ // 注意写法builder: CustomDialogBuilder({})})// controller: CustomDialogController // 这种预览会报错cancel?: () > voidconfirm?: (…

用API实现商品sku抓取字段展示-淘宝sku区间价展示逻辑和规则分析

有卖家问我&#xff1a;我的链接里面有5个sku&#xff0c;都是不同的价格&#xff0c;为什么消费者看到的不是最低价呢&#xff1f; 这是因为淘宝平台商品价格的展示规则发生了变化&#xff0c;存在SKU区间价的产品&#xff0c;现在在搜索结果页面的曝光已经不是默认显示最低s…

51单片机学习——矩阵键盘控制led

前言介绍 按键控制LED亮灭 #include <REGX52.H> void main() {while(1){if(P3_40){P1_10;}else{P1_11;}}}按键控制led状态 #include <REGX52.H> void Delay(unsigned int xms) //11.0592MHz {unsigned char i, j;while(xms){i 2;j 199;do{while (--j);} while …

鸿蒙语言基础类库:【@system.battery (电量信息)】

电量信息 说明&#xff1a; 从API Version 6开始&#xff0c;该接口不再维护&#xff0c;推荐使用新接口[ohos.batteryInfo]。本模块首批接口从API version 3开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import battery from syste…

算法学习day12(动态规划)

一、不同的二叉搜索树 二叉搜索树的性质&#xff1a;父节点比左边的孩子节点都大&#xff1b;比右边的孩子节点都小&#xff1b; 由图片可知&#xff0c;dp[3]是可以由dp[2]和dp[1]得出来的。(二叉搜索树的种类和根节点的val有关) 当val为1时&#xff0c;左边是一定没有节点的…

获奖案例回顾|基于卫星遥感和无人机的水稻全流程风险减量项目

引言 在现代农业保险领域&#xff0c;技术创新是推动行业进步的关键。珈和科技与太平财险的合作&#xff0c;旨在利用先进的卫星遥感和无人机技术&#xff0c;解决传统农业保险面临的诸多挑战&#xff0c;从而提升保险效率和服务质量。本次分享的项目案例获得了《金融电子化》…

leetcode日记(37)旋转图像

方法是看评论区想出来的&#xff1a;先将矩阵转置&#xff0c;再将每一行逆转 class Solution { public: int n,m,l,k; struct bian{int u;int v;int d; }; void digui(int loc,int c[],vector<bian> bi,int now,int q,bool colour[],int& maxx,bool jg[]){if(q>…

利用宝塔安装一套linux开发环境

更新yum&#xff0c;并且更换阿里镜像源 删除yum文件 cd /etc/yum.repos.d/ 进入yum核心目录 ls sun.repo rm -rf * 删除之前配置的本地源 ls 配置阿里镜像源 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo 配置扩展包 wge…

8款值得收藏的App推荐!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 值得一试的大众APP&#xff0c;它可能会给你的生活带来小小的改变。把下面的内容看完&#xff0c;我确信你一定会收获不少。 一、Todo清单——重…

Java之Stream流的笔记--手写版

Stream流通过讲集合或数组转换成链状流式的结构&#xff0c;简化了集合和数组进行排序、筛选、遍历、去重、统计等操作。主要包括创建流、中间操作、终结操作。若流中无终结操作&#xff0c;则中间操作不会执行&#xff1b;流是一次性的&#xff0c;使用完就会失效&#xff0c;…

【 香橙派 AIpro评测】烧系统运行部署LLMS大模型体验Jupyter Lab AI 应用样例(新手入门)

文章目录 一、引言⭐1.1下载镜像烧系统⭐1.2开发板初始化系统配置远程登陆&#x1f496; 远程ssh&#x1f496;查看ubuntu桌面&#x1f496; 远程向日葵 二、部署LLMS大模型2.1 快速启动&#x1f496;拉取代码&#x1f496;下载mode数据&#x1f496;启动模型对话 三、体验 内置…

SpringCloud02_consul概述、功能及下载、服务注册与发现、配置与刷新

文章目录 ①. Euraka为什么被废弃②. consul简介、如何下载③. consul功能及下载④. 服务注册与发现 - 8001改造⑤. 服务注册与发现 - 80改造⑥. 服务配置与刷新Refresh ①. Euraka为什么被废弃 ①. Eureka停更进维 ②. Eureka对初学者不友好,下图为自我保护机制 ③. 阿里巴巴…

多个版本JAVA切换(学习笔记)

多个版本JAVA切换 很多时候&#xff0c;我们电脑上会安装多个版本的java版本&#xff0c;java8&#xff0c;java11&#xff0c;java17等等&#xff0c;这时候如果想要切换java的版本&#xff0c;可以按照以下方式进行 1.检查当前版本的JAVA 同时按下 win r 可以调出运行工具…

Kafka基础入门-代码实操

Kafka是基于发布/订阅模式的消息队列&#xff0c;消息的生产和消费都需要指定主题&#xff0c;因此&#xff0c;我们想要实现消息的传递&#xff0c;第一步必选是创建一个主题&#xff08;Topic&#xff09;。下面我们看下在命令行和代码中都是如何创建主题和实现消息的传递的。…

MySql性能调优04-[MySql事务与锁机制原理]

MySql事务与锁机制原理 从undo与redo日志&#xff0c;理解事务底层ACID底层原理事务四大隔离级别事务底层锁机制和MVCC并发优化机制串行化底层实现机制读已提交和可重复读底层实现MVCC机制详解脏写问题(重要)读已提交&#xff1f;实现机制 BufferPool缓存与redo日志是如何提升事…

【AI大模型】李彦宏从“卷模型”到“卷应用”的深度解析:卷用户场景卷能给用户解决什么问题

文章目录 一、理解李彦宏的发言1.1 李彦宏的核心观点1.2 背景分析 二、技术发展&#xff1a;从辨别式到生成式2.1 辨别式AI技术2.2 生成式AI技术2.3 技术发展的挑战 三、“卷应用”&#xff1a;聚焦实际应用与价值3.1 应用为王3.2 技术落地的关键 四、“卷场景”&#xff1a;多…

007-端口隔离

端口隔离配置 端口隔离简介 为了实现报文之间的二层隔离&#xff0c;可以将不同的端口加入不同的VLAN&#xff0c;但会浪费有限的VLAN资源。采用端口隔离特性&#xff0c;可以实现同一VLAN内端口之间的隔离。 设备支持以下方式进行端口隔离&#xff1a; 基于隔离组的端口隔…

idea中打开静态网页端口是63342而不是8080

问题&#xff1a; 安装了tomcat 并且也配置了环境&#xff0c;但是在tomcat下运行&#xff0c;总是在63342下面显示。这也就意味着&#xff0c;并没有运行到tomcat环境下。 找了好几个教程&#xff08;中间还去学习了maven&#xff0c;因为跟的教程里面&#xff0c;没有maven,但…

LabVIEW心电信号自动测试系统

开发了一种基于LabVIEW的心电信号自动测试系统&#xff0c;通过LabVIEW开发的上位机软件&#xff0c;实现对心电信号的实时采集、分析和自动化测试。系统包括心电信号采集模块、信号处理模块和自动化测试模块&#xff0c;能够高效、准确地完成心电信号的测量与分析。 硬件系统…