Java 实现 AVL树
在二叉平衡树中,我们进行插入和删除操作时都需要遍历树,可见树的结构是很影响操作效率的。在最坏的情况下,树成了一个单支树,查找的时间复杂度成了O(N),建树跟没建树一样。那么是不是有什么办法可以建一个树避免这种情况?
一.概念
AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,其又叫高度平衡树。进行插入和删除操作后对树进行一次或多次旋转,保证每个结点的左右子树高度之差的绝对值不超过1,以达到高度平衡的目的。
1.AVL树本质上还是二叉平衡树,这是必须保证的一点;
2.AVL树在二叉平衡树的基础上加入了一个平衡的条件,即:每个结点的左右子树高度之差的绝对值不超过1。
二叉平衡树:Java Map和Set-CSDN博客
二.定义节点
节点与二叉平衡树的节点差不多,多了一个平衡因子,一个父节点。
static class TreeNode {public int val;public int bf;//平衡因子public TreeNode left;public TreeNode right;public TreeNode parent;public TreeNode(int val) {this.val = val;}
}
三.插入操作
因为AVL树也是二叉平衡树,所以插入操作是一样的,只需在后面加一个调整平衡因子的操作。
//找到要插入的位置
TreeNode node = new TreeNode(val);
if(root == null) {root = node;return true;
}TreeNode parent = null;
TreeNode cur = root;
while (cur != null) {if(cur.val < val) {parent = cur;cur = cur.right;}else if(cur.val == val) {return false;}else {parent = cur;cur = cur.left;}
}//插入节点
node.parent = parent;
cur = node;
if(parent.val < val) {parent.right = node;
}else {parent.left = node;
}
上述代码就是插入节点的操作。插入完后我们要对平衡因子进行调整。
1.调整平衡因子
平衡因子可分为三种情况:、
、
。
1.1 等于,说明该节点的左右子树高度相同,高度相同也就意味着该节点平衡了,也就是说新插入的节点没有使树的高度发生变化,那么整个树都是平衡的。
1.2 等于,说明该节点的左右子树高度相差1,如果左子树高那么就是-1,如果右子树高,那么就是1。如果是这种情况,还要继续往上找,因为这说明我们插入的节点影响了树的高度,这是要看一下是不是不平衡了。
1.3 等于,说明该节点左右子树高度相差2,不平衡了,要进行调整。这里又要分情况讨论了。
当平衡因子等于 2 时,说明右子树高。这里又要分为两种情况:

为什么要分为这两种呢?这与加下来的旋转操作有关。
前面说了,AVL树就是靠旋转来进行调整以达到平衡。如左图右子树高,我们可以通过左旋来降低右子树的高度。这里大家可以去下面看一下左旋的具体操作。
但对于右图来说,左旋就不好用了,转了之后还是不平衡的。对于右图我们要用先右旋在左旋的操作。
为什么会这样?左旋转的本质就是将bf为的左子树接到parent节点的右子树,如果说其这个左子树本身就是更深的子树,那么接上就和原来没有什么区别。
当平衡因子等于 -2 时,也是一样,都是一个原理这里不过多赘述,直接上代码。
public boolean insert(int val) {//找到要插入的位置TreeNode node = new TreeNode(val);if(root == null) {root = node;return true;}TreeNode parent = null;TreeNode cur = root;while (cur != null) {if(cur.val < val) {parent = cur;cur = cur.right;}else if(cur.val == val) {return false;}else {parent = cur;cur = cur.left;}}//插入节点node.parent = parent;cur = node;if(parent.val < val) {parent.right = node;}else {parent.left = node;}//调整平衡因子while (parent != null) {//更新平衡因子if(cur == parent.right) {parent.bf++;}else {parent.bf--;}if(parent.bf == 1 || parent.bf == -1){//继续循环cur = parent;parent = cur.parent;}else if(parent.bf == 2){if(cur.bf == 1) {rotateLeft(parent);}else {rotateRL(parent);}break;}else if(parent.bf == -2){if(cur.bf == -1) {rotateRight(parent);}else {rotateLR(parent);}break;}else{//已经平衡了break;}}return true;
}
2.左旋
将子树向左旋转:

上图左图是没有旋转时的状态,右图时左旋后的状态,我们可以通过节点变化来得到整个过程的变化:12的左子树连接到了10上,10变成了12的左子树。
可以拆成这么几步:
1.将bf=1的节点的左子树接到parent的右子树上;
2.将bf=1的节点连接到parent的parent;
3.将parent连接到bf=1的左子树上。
private void rotateLeft(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;//将bf=1的节点的左子树接到parent的右子树上parent.right = subRL;if(subRL != null) {subRL.parent = parent;}//将bf=1的节点连接到parent的parentTreeNode pParent = parent.parent;if(root == parent) {root = subR;root.parent = null;}else {if(pParent.left == parent) {pParent.left = subR;}else {pParent.right = subR;}subR.parent = pParent;}//将parent连接到bf=1的左子树上subR.left = parent;parent.parent = subR;//调整平衡因子subR.bf = parent.bf = 0;}
3.右旋
将子树向右旋:

思路跟向左旋一样,这里是将8的右子树连在10的左子树上,将10连在8的右子树上。
具体步骤:
1.将bf=-1的节点的右子树连在parent的左子树上;
2.将bf=-1的节点与parent的parent连接;
3.将parent连接到bf=-1节点的右子树上。
private void rotateRight(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;//将bf=-1的节点的右子树连在parent的左子树上parent.left = subLR;if(subLR != null) {subLR.parent = parent;}//将bf=-1的节点与parent的parent连接TreeNode pParent = parent.parent;if(parent == root) {root = subL;root.parent = null;}else {if(pParent.left == parent) {pParent.left = subL;}else {pParent.right = subL;}subL.parent = pParent;}//将parent连接到bf=-1的节点上subL.right = parent;parent.parent = subL;//调整平衡因子subL.bf = 0;parent.bf = 0;
}
4.先右旋后左旋
先旋转以bf=-1为父节点的树,再旋转parent的树:

表现在这张图里的是先旋转13节点的树,旋转完后再旋转10节点的树。
这里要特别说明以下平衡因子的调整:


上面两张图相当清晰表示出了平衡因子的变化。
private void rotateRL(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;int bf = subRL.bf;rotateRight(parent.right);rotateLeft(parent);if(bf == 1) {parent.bf = -1;subR.bf = 0;subRL.bf = 0;}if(bf == -1){parent.bf = 0;subR.bf = 1;subRL.bf = 0;}
}
5.先左旋后右旋
这个跟先右旋再左旋相似,都很像。

代码:
private void rotateLR(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;int bf = subLR.bf;rotateLeft(parent.left);rotateRight(parent);if(bf == -1) {subL.bf = 0;subLR.bf = 0;parent.bf = 1;}if(bf == 1){subL.bf = -1;subLR.bf = 0;parent.bf = 0;}
}
四.判断是不是AVL树
判断什么是不是什么这种问题一般是从性质出发。
判断是不是AVL树,首先这棵树是一颗二叉平衡树,其次这棵树的高度也要平衡。
public boolean isBalanced(TreeNode root) {if(root == null){return true;}int leftH = height(root.left);int rightH = height(root.right);if(rightH-leftH != root.bf) {return false;}return Math.abs(leftH-rightH) <= 1&& isBalanced(root.left)&& isBalanced(root.right);
}
五.总结
AVL树改善了原来二叉平衡树查找的问题,但也有新的问题。我们要在AVL树上插入或删除时,要不断的转转转,这个转转转也要时间的。所以说,如果我们要存储一个要频发插入删除的树,不适合用这个。
相关文章:
Java 实现 AVL树
在二叉平衡树中,我们进行插入和删除操作时都需要遍历树,可见树的结构是很影响操作效率的。在最坏的情况下,树成了一个单支树,查找的时间复杂度成了O(N),建树跟没建树一样。那么是不是有什么办法可以建一个树避免这种情…...
CNN卷积网络实现MNIST数据集手写数字识别
步骤一:加载MNIST数据集 train_data MNIST(root./data,trainTrue,downloadFalse,transformtransforms.ToTensor()) train_loader DataLoader(train_data,shuffleTrue,batch_size64) # 测试数据集 test_data MNIST(root./data,trainFalse,downloadFalse,transfor…...
深入理解Java中的时间处理与时区管理
在Java开发中,时间处理和时区管理是常见的需求,特别是在全球化应用中。Java 8引入了新的时间API(java.time包),使时间处理变得更加直观和高效。本文将详细介绍Java中的时间处理与时区管理,通过丰富的代码示…...
虚拟机windows server创建域
目录 准备工作 一、新建域控制器 二、提升为域控制器添加新林 三、新建组织单位(OU),用户 四、将计算机加域 五、在域控中管理计算机 六、在域控中配置组策略 七、域内计算机验证组策略配置 准备工作 安装域前,如果有DNS…...
Java 集合框架:Java 中的 Set 集合(HashSet LinkedHashSet TreeSet)特点与实现解析
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 017 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自…...
springboot智能健康管理平台-计算机毕业设计源码57256
摘要 在当今社会,人们越来越重视健康饮食和健康管理。借助SpringBoot框架和MySQL数据库的支持,开发智能健康管理平台成为可能。该平台结合了小程序技术的便利性和SpringBoot框架的快速开发能力,为用户提供了便捷的健康管理解决方案。 通过智能…...
LetterBox图像预处理方法
LetterBox图像预处理方法就是要将不同分辨率的图像转换成固定分辨率,比如v8输入网络的固定分辨率为6406403,因此这里分享一下默认情况下对训练集、验证集和测试图片做的letterBox的方法。 1.LetterBox-Train 对于训练集,默认输入网络的图像尺寸为640640,假设有一张7201280…...
C++第五篇 类和对象(下) 初始化列表
目录 1.再探构造函数 2.类型转换 3.static成员 4.友元 friiend 1.再探构造函数 (1).之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式是以一个冒…...
C#中的通信
上位机应用开发-串口通信1、基于C#的串口通信对象:SerialPort 2、字段属性 PortName:获取或设置通信端口 BaudRate:获取或设置串行波特率-DataBits:获取或设置每个字节的标准数据位长度 Parity:获取或设置奇偶校验检查协仪I-StopBits;获取或设置每个字节的标准停止位数 3、…...
CVE-2022-21663: WordPress <5.8.3 版本对象注入漏洞深入分析
引言 在网络安全领域,技术的研究与讨论是不断进步的动力。本文针对WordPress的一个对象注入漏洞进行分析,旨在分享技术细节并提醒安全的重要性。特别强调:本文内容仅限技术研究,严禁用于非法目的。 漏洞背景 继WordPress CVE-2…...
C语言笔试题(三)
本专栏通过整理各专业方向的面试资料并咨询业界相关人士,整合不同方向的面试资料,希望能为您的面试道路点亮一盏灯! 1 简单题 如何声明一个二维数组? 答案: int arr[3][4];解析: 二维数组可以看作数组的数组。 union和struct…...
minio笔记之windows下安装使用
minio安装使用 去官网下载安装包启动访问管理平台创建桶创建用户、资源授权访问访问策略创建创建用户创建accessKey,用于应用程序开发 去官网下载安装包 直接安装即可 启动 设置密码 set MINIO_ROOT_USERadmin set MINIO_ROOT_PASSWORD12345678 cd到安装目录 mi…...
代码随想录算法训练营day31 | 56. 合并区间、738.单调递增的数字
碎碎念:加油 参考:代码随想录 56. 合并区间 题目链接 56. 合并区间 思想 这道题的核心还是判断重叠区间,本题和之前做过的452. 用最少数量的箭引爆气球、435. 无重叠区间的区别在于判断出重叠区间之后的操作,本题需要做的是合…...
利用 Python 制作图片轮播应用
在这篇博客中,我将向大家展示如何使用 xPython 创建一个图片轮播应用。这个应用能够从指定文件夹中加载图片,定时轮播,并提供按钮来保存当前图片到收藏夹或仅轮播收藏夹中的图片。我们还将实现退出按钮和全屏显示的功能。 C:\pythoncode\new\…...
报表系统之Cube.js
Cube.js 是一个开源的分析框架,专为构建数据应用和分析工具而设计。它的主要目的是简化和加速构建复杂的分析和数据可视化应用。以下是对 Cube.js 的详细介绍: 核心功能和特点 1. 多数据源支持 Cube.js 支持从多个数据源中提取数据,包括 SQ…...
代码随想录算法训练营第45天
115.不同的子序列 但相对于刚讲过 392.判断子序列,本题 就有难度了 ,感受一下本题和 392.判断子序列 的区别。 代码随想录 class Solution {public int numDistinct(String s, String t) {int lenS s.length();int lenT t.length();int[][] dp new …...
solidity合约创建
合约可以通过使用new关键字来创建其他合约的实例。 这个过程会执行被创建合约的构造函数(如果存在的话),并返回一个指向新创建合约的地址的引用。 这种方式允许智能合约动态地在区块链上部署新合约,并与它们交互。 通过 new 创…...
队列---循环队列实现
循环队列详解 概述 循环队列是一种基于数组实现的队列数据结构,其中队列的队首和队尾是通过模运算连接起来形成一个逻辑上的环形结构。这样可以有效地利用数组的空间,避免出现“假溢出”的情况。 结构体定义 循环队列的结构体定义如下: …...
【视频讲解】后端增删改查接口有什么用?
B站视频地址 B站视频地址 前言 “后端增删改查接口有什么用”,其实这句话可以拆解为下面3个问题。 接口是什么意思?后端接口是什么意思?后端接口中的增删改查接口有什么用? 1、接口 概念:接口的概念在不同的领域中…...
双指针hard题
[LeetCode]4. Median of Two Sorted Arrays 中文 - YouTube 依赖merge sort和priorityqueue的废物 正式变身山景城一姐小迷妹✪ω✪ 寻找正序数组中位数 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int len1 nums1.length;int len2 …...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
