数据结构--AVL树

目录

前言

AVL树的特点

AVL树的插入

节点的定义

情况分析

AVL树的旋转

右单旋

左单旋

左右双旋

右左双旋

​编辑总结 

验证AVL树


前言

二叉搜索树可以帮助我们以极高的效率查找(理想情况下是logn),但是当在极端情况下,比如当树中的节点值是有序的时,二叉搜索树会变成一个单枝树,相当于一个链表,于是乎为了让树更接近与一个完全二叉树,想着当向二叉搜索树中插入新节点后,让每个节点的左右子树高度差的绝对值不超过1就可以降低树的高度从而提高效率,于是就有了AVL树。

AVL树的特点

  • 每个节点的左右子树都是AVL树
  • 左右子树高度差(平衡因子)的绝对值不超过1

AVL树的插入

节点的定义

static class TreeNode {public int val;public TreeNode left;public TreeNode right;public int bf;//平衡因子public TreeNode parent;public TreeNode(int val) {this.val = val;}}

 //平衡因子=右子树高度-左子树高度

情况分析

因为AVL树也是二叉搜索树,所以先按照二叉搜索树的方式插入一个节点cur,但是当cur被插入后,其父节点parent的平衡因子必定会发生变化(左右子树高度差必定发生变化),所以每次插入节点后都需要更新平衡因子,并且检查AVL树的结构是否被破坏。情况可以分为以下几种

cur插入之前parent的平衡因子会有三种可能'-1' ,'0' ,'1'

  • 当cur插入到parent的左边时parent的平衡因子-1
  • 当cur插入到parent的右边时parent的平衡因子+1 

cur插入之后parent的平衡因子可能会有五种可能‘-1’,‘+1’,‘0’,‘-2’,‘+2’

  •  如果cur插入后parent的平衡因子是0,说明插入之前它的平衡因子是正负1,所以插入后才可能被调整为0,此时cur成功插入
  • 如果cur插入后parent的平衡因子是正负1,说明插入之前它的平衡因子是0,所以插入后才可能被调整为正负1,此时以parent为根的树高度增加了,上面节点的平衡因子可能会被打破所以需要继续向上更新
  • 如果cur插入后parent的平衡因子是正负2,则说明以parent为根的树不符合AVL树的性质,需要对其进行调整,既旋转

插入节点

TreeNode node = new TreeNode(val);//如果根节点为空直接插入if (root == null) {root = node;return true;}TreeNode parent = null;TreeNode cur = root;while (cur != null) {if (val > cur.val) {parent = cur;cur = cur.right;} else if (val < cur.val) {parent = cur;cur = cur.left;} else {return false;}}//cur == nullif (val > parent.val) {parent.right = node;} else if (val < parent.val) {parent.left = node;}

调整parent的平衡因子

//调整cur = node;node.parent = parent;while (parent != null) {//如果cur是父节点的右孩子,父节点的平衡因子就加一,否则减一if (cur == parent.right) {parent.bf++;} else {parent.bf--;}//判断平衡因子if (parent.bf == 0) {//已经平衡break;} else if (parent.bf == 1 || parent.bf == -1) {//继续向上调整cur = parent;parent = cur.parent;} else {if (parent.bf == 2) {//右树高,需要降低右树高度(说明cur插到了parent的右边)if (cur.bf == 1) {//左单旋} else if (cur.bf == -1) {//右左双旋}//左树高,需要降低左树高度} else {//parent.bf == -2(说明cur插到了parent的左边)if (cur.bf == -1) {//右单旋} else if (cur.bf == 1) {//左右双旋}}break;}}

AVL树的旋转

右单旋

当新节点插入较高左子树的左侧

上图是一个简单的右旋,当新节点10插入到30的左子树时(这里是左子树),破坏了avl树的平衡节点50的平衡因子变成-2,表示左子树比右子树高2,所以我们需要降低左子树,提高右子树。既将50节点向右旋转到30节点的左边(因为50比三十大,只能在30的右边),此时这颗AVL树就平衡了(别忘了修改平衡因子bf)

但是实际插入中的AVL树的结构可能不会和上图这样简单比如:

  • 30的右边可能已经有节点了

0是新插入的节点插到了30的左子树的左侧,破坏了AVL树的平衡,使50节点的平衡因子变成-2,所以和刚刚一样我们需要降低左树,抬高右树,我们让50节点向右旋转,但是此时30节点的右边已经有了一个节点(也可能是30的右子树的根),不过这个节点一定是大于30小于50的,所以我们只需要将这个节点放到50节点的左边,然后再将50节点放到30节点的右边即可,最后修改平衡因子。

  •  50可能是其他节点的左右子树或者根节点

 如果50是根节点那么当旋转完成后要更新根节点,如果50是其父节点的左孩子,那么要让30节点变成50原父节点的左孩子,右孩子则相反

代码

public void rotateRight(TreeNode parent) {TreeNode pParetn = parent.parent;//记录parent的父节点TreeNode subL = parent.left;//parent的左孩子TreeNode subLR = subL.right;//parent的左孩子的右孩子parent.left = subLR;if (subLR != null) {subLR.parent = parent;}subL.right = parent;parent.parent = subL;//检查当前parent是不是根节点//是根节点的话直接让subL为根节点if (parent == root) {root = subL;root.parent = null;} else {//不是根节点,就判断parent是其父节点的左孩子还是右孩子if (parent == pParetn.left) {//parent是左孩子,就让subL也是左孩子pParetn.left = subL;} else {pParetn.right = subL;}subL.parent = pParetn;}subL.bf = 0;parent.bf = 0;}

首先先标记右旋所需要的节点,然后开始右旋,过程和前文一样

  1. 将subLR作为parent的左孩子(就算subLR为空也无所谓)
  2. 判断subL是否有右孩子(subLR) ,有的话就令subLR的父节点位parent
  3. 让subL的右节点为parent
  4. 让parent的父节点为subLR
  5. 旋转完成后判断parent是否是根节点是的话,更新根节点,有父节点的话则让subL为parent父节点的孩子
  6. 最后修改平衡因子

右旋代码既图解 

左单旋

当新节点插入较高右子树的右侧

左单旋的方法和右单旋一样,只是方向不一样,具体过程参考右单旋

代码及图解

private void rotateLeft(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;parent.right = subRL;subR.left = parent;if (subRL != null) {subRL.parent = parent;}TreeNode pParent = parent.parent;parent.parent = subR;//检查当前parent是不是rootif (parent == root) {root = subR;root.parent = null;} else {//不是rootif (pParent.right == parent) {pParent.right = subR;} else {pParent.left = subR;}subR.parent = pParent;}parent.bf = 0;subR.bf = 0;}

 左旋

  1. 首先先标记左旋所需要的节点,然后开始左旋
  2. 将subRL作为parent的右孩子
  3. 判断subR的左孩子是否为空,如果不为空则令subR的左孩子(subRL)的父节点为parent
  4. 令subR的左孩子为parent
  5. 令parent的parent为subR
  6. 旋转完成后判断parent是否是根节点是的话,更新根节点,有父节点的话则让subR为parent父节点的孩子
  7. 最后修改平衡因子

左右双旋

新节点插入较高左子树右侧

这种情况无法通过直接左旋或者右旋使树平衡,需要先左旋再右旋,通过左旋把树变成左子树较高的情况然后再右旋

可以看到只是右旋达不到平衡的结果

先左旋再右旋

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;} else if (bf == -1) {subL.bf = -1;subLR.bf = 0;parent.bf = 0;}}

 以上面的例子说明当bf==1时说明新节点插到45左边,bf==-1时说明新节点插到45右边

bf==-1时旋转结果

右左双旋

新节点插到较高右子树左侧

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;} else if (bf == -1) {parent.bf = 0;subR.bf = 1;subRL.bf = 0;}}

总结 

当新节点插入后如果树不平衡,既parent的平衡因子为-2或者2

1.parent的平衡因子为2,说明右树高

  • 如果SubR(右子树)的平衡因子为1,说明新节点插入较高右子树的右侧,执行左单旋
  • 如果SubR的平衡因子为-1,说明新节点插入到较高右子树的左侧,执行右左双旋

2.parent的平衡因子为-2,说明左树高

  • 如果SubL的平衡因子为-1,说明新节点插入到了较高左子树的左边,执行有右选
  • 如果SubL的平衡因子为1,说明新节点插入到了较高左子树的右边,执行左右双旋 

验证AVL树

验证AVL树只需要判断每个节点的左右子树高度差的绝对值都小于一就行

public int height(TreeNode node) {if (node == null) {return 0;}int left = height(node.left);int right = height(node.right);return left > right ? left + 1 : right + 1;}public Boolean isbalanced(TreeNode root) {if (root == null) {return true;}//求左右子树高度int left = height(root.left);int right = height(root.right);if (root.bf != right - left) {return false;}return Math.abs(left - right) <= 1&& isbalanced(root.left)&& isbalanced(root.right);}public static void main(String[] args) {int[] array = {17, 4, 8, 6, 17, 25, 17, 19, 10};AVLTree avlTree = new AVLTree();for (int i=0;i<array.length;++i){avlTree.insert(array[i]);}boolean ret = avlTree.isbalanced(avlTree.root);System.out.println(ret);}

综上,AVL树其实就是一个相对平衡的二叉搜索树,每个节点的左右子树高度差的绝对值都小于一,这样可以使其查找的时间复杂度稳定为O(logN),但是也因为其在进行修改操作时比如插入删除,需要对树进行多次旋转,使其修改变得及其麻烦。所以,如果需要 一种查询高效且有序的数据结构,而且数据的个数为静态的(修改操作较少或不修改),可以考虑AVL树,但一个结构经常修 改,就不太适合

以上就是博主对AVL树知识的分享,在之后的博客中会陆续分享有关数据结构的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望可以多多支持博主!!🥰🥰

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

泰迪杯特等奖案例学习资料:基于多模态融合与边缘计算的智能温室环境调控系统

(第十二届泰迪杯数据挖掘挑战赛特等奖案例解析) 一、案例背景与核心挑战 1.1 应用场景与行业痛点 在现代设施农业中,温室环境调控直接影响作物产量与品质。传统温室管理存在以下问题: 环境参数耦合性高:温度、湿度、光照、CO₂浓度等参数相互影响,人工调控易顾此失彼。…

动手学深度学习12.1. 编译器和解释器-笔记练习(PyTorch)

以下内容为结合李沐老师的课程和教材补充的学习笔记&#xff0c;以及对课后练习的一些思考&#xff0c;自留回顾&#xff0c;也供同学之人交流参考。 本节课程地址&#xff1a;无 本节教材地址&#xff1a;12.1. 编译器和解释器 — 动手学深度学习 2.0.0 documentation 本节…

[java八股文][Java并发编程面试篇]并发安全

juc包下你常用的类&#xff1f; 线程池相关&#xff1a; ThreadPoolExecutor&#xff1a;最核心的线程池类&#xff0c;用于创建和管理线程池。通过它可以灵活地配置线程池的参数&#xff0c;如核心线程数、最大线程数、任务队列等&#xff0c;以满足不同的并发处理需求。Exe…

VMware搭建ubuntu保姆级教程

目录 VMware Ubuntu 虚拟机配置指南 创建虚拟机 下载 Ubuntu ISO 新建虚拟机 网络配置&#xff08;双网卡模式&#xff09; 共享文件夹设置 SSH 远程访问配置 VMware Ubuntu 虚拟机配置指南 创建虚拟机 下载 Ubuntu ISO 【可添加我获取】 官网&#xff1a;Get Ubunt…

冯诺依曼结构与哈佛架构深度解析

一、冯诺依曼结构&#xff08;Von Neumann Architecture&#xff09; 1.1 核心定义 由约翰冯诺依曼提出&#xff0c;程序指令与数据共享同一存储空间和总线&#xff0c;通过分时复用实现存取。 存储器总带宽 指令带宽 数据带宽 即&#xff1a;B_mem f_clk W_data f_…

C/C++工程中的Plugin机制设计与Python实现

C/C工程中的Plugin机制设计与Python实现 1. Plugin机制设计概述 在C/C工程中实现Plugin机制通常需要以下几个关键组件&#xff1a; Plugin接口定义&#xff1a;定义统一的接口规范动态加载机制&#xff1a;运行时加载动态库注册机制&#xff1a;Plugin向主程序注册自己通信机…

node-sass安装失败解决方案

1、python环境问题 Error: Cant find Python executable "python", you can set the PYTHON env variable. 提示找不到python2.7版本&#xff0c; 方法一&#xff1a;可安装一个python2.7或引用其他已安装的python2.7 通过设置环境变量可以解决&#xff1b; 方法二&…

Netty高并发物联网通信服务器实战:协议优化与性能调优指南

目录 1.总体设计 2.自定义协议设计(简单版) 3.消息类型(1字节) 4.项目结构 5.核心功能代码 (1)pom.xml(Maven依赖) (2)IotServer.java(服务器启动器) (3)IotServerInitializer.java(Pipeline初始化) (4)DeviceChannelManager.java(设备连接管理器)…

多模态大语言模型arxiv论文略读(六十)

Cantor: Inspiring Multimodal Chain-of-Thought of MLLM ➡️ 论文标题&#xff1a;Cantor: Inspiring Multimodal Chain-of-Thought of MLLM ➡️ 论文作者&#xff1a;Timin Gao, Peixian Chen, Mengdan Zhang, Chaoyou Fu, Yunhang Shen, Yan Zhang, Shengchuan Zhang, Xi…

面试常问系列(一)-神经网络参数初始化-之自注意力机制为什么除以根号d而不是2*根号d或者3*根号d

首先先罗列几个参考文章&#xff0c;大家之后可以去看看&#xff0c;加深理解&#xff1a; 面试常问系列(一)-神经网络参数初始化面试常问系列(一)-神经网络参数初始化之自注意力机制_注意力机制的参数初始化怎么做-CSDN博客面试常问系列(一)-神经网络参数初始化-之-softmax-C…

第5篇:EggJS中间件开发与实战应用

在Web开发中&#xff0c;中间件&#xff08;Middleware&#xff09;是处理HTTP请求和响应的核心机制之一。EggJS基于Koa的洋葱模型实现了高效的中间件机制&#xff0c;本文将深入探讨中间件的执行原理、开发实践以及常见问题解决方案。 一、中间件执行机制与洋葱模型 1. 洋葱模…

树状结构转换工具类

项目中使用了很多树状结构&#xff0c;为了方便使用开发一个通用的工具类。 使用工具类的时候写一个类基础BaseNode&#xff0c;如果有个性化字段添加到类里面&#xff0c;然后就可以套用工具类。 工具类会将id和pid做关联返回一个树状结构的集合。 使用了hutool的工具包判空…

【Python】--装饰器

装饰器&#xff08;Decorator&#xff09;本质上是一个返回函数的函数 主要作用是&#xff1a;在不修改原函数代码的前提下&#xff0c;给函数增加额外的功能 比如&#xff1a;增加业务&#xff0c;日志记录、权限验证、执行时间统计、缓存等场景 my_decorator def func():pas…

AI教你学VUE——Gemini版

前端开发学习路线图 (针对编程新手&#xff0c;主攻 Vue 框架) 总原则&#xff1a;先夯实基础&#xff0c;再深入框架。 想象一下建房子&#xff0c;地基不牢&#xff0c;上面的高楼&#xff08;框架&#xff09;是盖不起来的。HTML、CSS、JavaScript 就是前端的地基。 阶段一…

神经网络中之多类别分类:从基础到高级应用

神经网络中之多类别分类&#xff1a;从基础到高级应用 摘要 在机器学习领域&#xff0c;多类别分类是解决复杂问题的关键技术之一。本文深入探讨了神经网络在多类别分类中的应用&#xff0c;从基础的二元分类扩展到一对多和一对一分类方法。我们详细介绍了 softmax 函数的原理…

Go Web 后台管理系统项目详解

Go Web 后台管理系统项目详解 一、背景介绍 这是一个基于 Go 语言开发的 Web 后台管理系统&#xff0c;为笔者学习期间练手之作&#xff0c;较为粗糙 二、技术架构 后端 语言 &#xff1a;采用 Go 语言&#xff08;Golang&#xff09;编写&#xff0c;因其简洁高效、并发能…

【Python系列】Python 中的 HTTP 请求处理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

OS7.【Linux】基本指令入门(6)

目录 1.zip和unzip 配置指令 使用 两个名词:打包和压缩 打包 压缩 Linux下的操作演示 压缩和解压缩文件 压缩和解压缩目录 -d选项 2.tar Linux下的打包和压缩方案简介 czf选项 xzf选项 -C选项 tzf选项 3.bc 4.uname 不带选项的uname -a选项 -r选项 -v选项…

windows系统 压力测试技术

一、CPU压测模拟 工具&#xff1a;CpuStres v2.0 官网&#xff1a;https://learn.microsoft.com/en-us/sysinternals/downloads/cpustres 功能&#xff1a;是一个工具类&#xff0c;用来模拟在一个进程中启动最多64个线程&#xff0c;且可以独立控制任何一个线程的启动/暂停、…

64.搜索二维矩阵

给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示…