【计算机基础——数据结构——AVL平衡二叉树】
1. BST二叉查找树
1.1 BST二叉查找树的特性
- 左子树上所有结点的值均小于或等于它的根结点的值。
- 右子树上所有结点的值均大于或等于它的根结点的值。
- 左、右子树也分别为二叉排序树。
1.2 BST二叉查找树的缺点
- 二叉查找树是有缺点的,在不断插入的时候,有可能出现这样一种情况:很容易“退化”成链表,
- 如果bst 树的节点正好从大到小的插入,此时树的结构也类似于链表结构,这时候的查询或写入耗时与链表相同。
为了避免这种特殊的情况发生,引入了平衡二叉树(AVL)和红黑树(red-black tree)
2. AVL平衡二叉树
平衡二叉树也叫AVL(发明者名字简写),也属于二叉搜索树的一种,与其不同的是AVL通过机制保证其自身的平衡。
AVL树是最先发明的自平衡二叉查找树。
在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。
增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
2.1 AVL树的特性
AVL树本质上还是一棵二叉搜索树,它有以下特性:
- 特性1: 对于任何一颗子树的root根结点而言,它的左子树任何节点的key一定比root小,而右子树任何节点的key 一定比root大;
- 特性2:对于AVL树而言,其中任何子树仍然是AVL树;
- 特性3:每个节点的左右子节点的高度之差的绝对值最多为1;
在插入、删除树节点的时候,如果破坏了以上的原则,AVL树会自动进行调整使得以上三条原则仍然成立。
2.2 AVL树的平衡功能
举个例子,下左图为AVL树最长的2节点与最短的8节点高度差为1;
当插入一个新的节点后,根据上面第一条原则,它会出现在2节点的左子树,但这样一来就违反了原则3。
此时AVL树会通过节点的旋转进行进行平衡,
AVL调整的过程称之为左旋和右旋,左旋就是逆时针转,右旋是顺时针转
2.3 AVL平衡的调整过程
调整的过程:
- 确定旋转支点(pivot):这个旋转支点就是失去平衡这部分树,在自平衡之后的根节点,平衡的调整过程,需要根据pivot它来进行旋转。我们只关注失衡子树的根结点 及它的子节点和孙子节点即可。
- 判断AVL失衡的类型:包含左左结构失衡(LL型失衡)、右右结构失衡(RR型失衡)、左右结构失衡(LR型失衡)、右左结构失衡(RL型失衡)
- 根据失衡的类型进行相应的旋转
2.3.1 场景1:LL失衡-左左结构失衡(右旋)
在结点Root的 左结点(L) 的 左子树(L) 上做了插入元素的操作,我们称这种情况为 左左型 ,我们应该进行右旋转。
2.3.2 场景2:RR型失衡:右右结构失衡(左旋)
在结点Root的 右结点(R) 的 右子树(R) 上做了插入元素的操作,我们称这种情况为右右型 ,我们应该进行左旋转。
2.3.3 场景3: LR型失衡:左右失衡(左旋+右旋):
在结点Root的 左结点(L) 的 右子树(R) 上做了插入元素的操作,我们称这种情况为左右型 ,我们应该进行左旋转+右旋转。
2.3.4 场景4:RL失衡: 右左结构 (右旋+左旋):
在结点Root的 右结点(R) 的 左子树(L) 上做了插入元素的操作,我们称这种情况为右左型 ,我们应该进行右旋转+左旋转。
3. 代码实现+详细注解
3.1 基本结构+操作
package mainimport "fmt"// AVL树的节点
type Node struct {Key intHeight intLeft *NodeRight *Node
}type AVLTree struct {Root *Node
}// 返回节点的高度
func height(n *Node) int {if n == nil {return 0}return n.Height
}func max(a, b int) int {if a > b {return a}return b
}// 返回当前节点的平衡因子
func getBalance(n *Node) int {if n == nil {return 0}return height(n.Left) - height(n.Right)
}// 右旋
func rightRotate(root *Node) *Node {//pivot是新的根节点,T2是要转移的子树的根pivot := root.LeftT2 := pivot.Rightpivot.Right = rootroot.Left = T2// 重新计算两个调整后的节点高度root.Height = max(height(root.Left), height(root.Right)) + 1pivot.Height = max(height(pivot.Left), height(pivot.Right)) + 1return pivot
}// 右旋
func leftRotate(root *Node) *Node {//pivot是新的根节点,T2是要转移的子树的根pivot := root.RightT2 := pivot.Leftpivot.Left = rootroot.Right = T2// 重新计算两个调整后的节点高度root.Height = max(height(root.Left), height(root.Right)) + 1pivot.Height = max(height(pivot.Left), height(pivot.Right)) + 1return pivot
}// 查找
func (t *AVLTree) Search(key int) *Node {return search(t.Root, key)
}func search(node *Node, key int) *Node {if node == nil {return nil}if key == node.Key {return node}if key < node.Key {return search(node.Left, key)}return search(node.Right, key)
}
3.2 插入操作
func (t *AVLTree) Insert(key int) {t.Root = insert(t.Root, key)
}func insert(node *Node, key int) *Node {// 1. 找到插入节点的位置if node == nil {return &Node{Key: key, Height: 1}}if key < node.Key {node.Left = insert(node.Left, key)} else if key > node.Key {node.Right = insert(node.Right, key)} else {//重复的key是不被允许的return node}// 2. 更新新插入节点的祖先节点的高度node.Height = max(height(node.Left), height(node.Right)) + 1// 3. 获得当前节点的平衡因子balance := getBalance(node)// 4. 平衡调整// 4.1说明新插入的节点插入到了当前节点的左节点的左孩子,需要进行右旋if balance > 1 && key < node.Left.Key {return rightRotate(node)}// 4.2说明新插入的节点插入到了当前节点的右节点的右孩子,需要进行左旋if balance < -1 && key > node.Right.Key {return leftRotate(node)}// 4.3说明新插入的节点插入到了当前节点的左节点的右孩子,需要进行左旋+右旋if balance > 1 && key > node.Right.Key {node.Left = leftRotate(node.Left)return rightRotate(node)}// 4.4说明新插入的节点插入到了当前节点的右节点的左孩子,需要进行右旋+左旋if balance < -1 && key < node.Left.Key {node.Right = rightRotate(node.Right)return leftRotate(node)}// 4.5 不要需要进行平衡调整return node
}
3.3 删除操作
func (t *AVLTree) Delete(key int) {t.Root = delete(t.Root, key)
}func delete(node *Node, key int) *Node {if node == nil {return nil}// 1. 找到要删除的节点if key < node.Key {node.Left = delete(node.Left, key)} else if key > node.Key {node.Right = delete(node.Right, key)} else {// 被删除的节点是叶子节点或者只有一个孩子节点if node.Left == nil {return node.Right} else if node.Right == nil {return node.Left}// 被删除的节点下面还有两个孩子节点// 先找到被删除节点下面最小的值temp := minValueNode(node.Right)// 将最小的值放到当前节点node.Key = temp.Key// 递归删除最小值node.Right = delete(node.Right, temp.Key)}if node == nil {return node}// 2. 更新新插入节点的祖先节点的高度node.Height = max(height(node.Left), height(node.Right)) + 1// 3. 获得当前节点的平衡因子balance := getBalance(node)// 4. 平衡调整// 4.1说明新删除的节点导致当前节点的平衡因子出了问题,// 判断是当前节点左节点的左孩子造成的,需要进行右旋if balance > 1 && getBalance(node.Left) >= 0 {return rightRotate(node)}// 4.2说明新删除的节点导致当前节点的平衡因子出了问题,// 判断是当前节点右节点的右孩子造成的,需要进行左旋if balance < -1 && getBalance(node.Right) <= 0 {return leftRotate(node)}// 4.3说明新删除的节点导致当前节点的平衡因子出了问题,// 判断是当前节点左节点的右孩子造成的,需要进行左旋+右旋if balance > 1 && getBalance(node.Left) < 0 {node.Left = leftRotate(node.Left)return rightRotate(node)}// 4.4说明新删除的节点导致当前节点的平衡因子出了问题,// 判断是当前节点右节点的左孩子造成的,需要进行右旋+左旋if balance < -1 && getBalance(node.Right) > 0 {node.Right = rightRotate(node.Right)return leftRotate(node)}// 4.5 不要需要进行平衡调整return node
}func minValueNode(node *Node) *Node {current := nodefor current.Left != nil {current = current.Left}return current
}
3.4 测试
func main() {tree := &AVLTree{}// 插入节点tree.Insert(10)tree.Insert(20)tree.Insert(30)tree.Insert(40)tree.Insert(50)tree.Insert(60)tree.Insert(70)// 层次遍历fmt.Println(LevelOrder(tree.Root))tree.Delete(10)tree.Delete(20)tree.Delete(30)fmt.Println(LevelOrder(tree.Root))
}// LevelOrder 返回层次遍历的结果(按层分组)
func LevelOrder(root *Node) [][]int {result := make([][]int, 0)if root == nil {return result}// 使用队列来存储节点queue := []*Node{root}for len(queue) > 0 {// 当前层的节点数levelSize := len(queue)// 存储当前层的值currentLevel := make([]int, 0, levelSize)// 遍历当前层的所有节点for i := 0; i < levelSize; i++ {// 获取队首节点node := queue[0]queue = queue[1:] // 出队// 将当前节点的值加入当前层的结果中currentLevel = append(currentLevel, node.Key)// 将子节点加入队列if node.Left != nil {queue = append(queue, node.Left)}if node.Right != nil {queue = append(queue, node.Right)}}// 将当前层的结果加入最终结果result = append(result, currentLevel)}return result
}
层次遍历的结果,符合预期
相关文章:
【计算机基础——数据结构——AVL平衡二叉树】
1. BST二叉查找树 1.1 BST二叉查找树的特性 左子树上所有结点的值均小于或等于它的根结点的值。右子树上所有结点的值均大于或等于它的根结点的值。左、右子树也分别为二叉排序树。 1.2 BST二叉查找树的缺点 二叉查找树是有缺点的,在不断插入的时候,…...
体育活动赛事报名马拉松微信小程序开发
功能描述 体育活动赛事报名马拉松微信小程序,该项目是一个体育活动报名小程序,主要功能有活动报名、扫码签到、签到积分、排行奖励、积分兑换等功能。 用户端🔶登录:◻️1.微信授权登录 ◻️2.手机号码授权 🔶首页&am…...
【C++】C++基础知识
一.函数重载 1.函数重载的概念 函数重载是函数的一种特殊情况,C允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表必须不同。函数重载常用来处理实现功能类似,而数据类型不同的问题。 #include <iostream> using…...
中间件安全
IIS IIS短文件漏洞 此漏洞实际是由HTTP请求中旧DOS 8.3名称约定(SFN)的代字符(~)波浪号引起的。它允许远程攻击者在Web根目录下公开文件和文件夹名称(不应该可被访问)。攻击者可以找到通常无法从外部直接访问的重要文件,并获取有关应用程序基础结构的信息。 利用工具 https…...
Zabbix中文监控指标数据乱码
1)点击主机,选择Zabbix server 中的 图形 一项,可以看到当前显示的为乱码 2) 下载字体文件: https://gitcode.com/open-source-toolkit/4a3db/blob/main/SimHei.zip 解压unzip -x SimHei.zip 3) 替换字体文…...
【AI】AI如何赋能软件开发流程
方向一:流程与模式介绍【传统软件开发 VS AI参与的软件开发】 传统软件开发流程 传统软件开发流程一般可以分为以下几个阶段: 1. 需求分析:在这个阶段,开发团队与客户沟通,明确软件的需求和目标。团队会收集、整理和分…...
恒创科技:什么是 RAID 3 ? RAID 3、4 和5之间有什么区别?
RAID 是一种存储数据以提高性能并减少数据丢失的特定技术。您可以根据自己的需求选择多种 RAID 类型。RAID 3 是列表中比较有效的类型之一。本文将重点介绍这种特定的 RAID 技术,并比较 RAID 3、4 和 5。 RAID 3 的定义 RAID 3 是一种特定的磁盘配置,用于…...
python获取iOS最近业务日志的两种方法
当iOS UI自动化用例执行失败的时候,需要获取当时的业务日志,供后续分析使用。 现在已经把iOS沙盒目录挂载到本地,剩下的事情就是从沙盒目录中捞取当前的日志,沙盒中的日志文件较大,整体导出来也可以,但是会…...
【如何获取股票数据43】Python、Java等多种主流语言实例演示获取股票行情api接口之沪深指数历史交易数据获取实例演示及接口API说明文档
最近一两年内,股票量化分析逐渐成为热门话题。而从事这一领域工作的第一步,就是获取全面且准确的股票数据。因为无论是实时交易数据、历史交易记录、财务数据还是基本面信息,这些数据都是我们进行量化分析时不可或缺的宝贵资源。我们的主要任…...
ESLint 使用教程(一):从零配置 ESLint
前言 在现代前端开发中,代码质量和风格一致性是团队合作和项目维护的重要因素。而 ESLint 作为一款强大的 JavaScript 静态代码分析工具,能够帮助开发者发现和修复代码中的潜在问题。本文将详细介绍 ESLint 的常用规则配置,并结合实际应用场…...
openssl对称加密代码讲解实战
文章目录 一、openssl对称加密和非对称加密算法对比1. 加密原理2. 常用算法3. 加密速度4. 安全性5. 应用场景6. 优缺点对比综合分析 二、代码实战代码说明:运行输出示例代码说明:注意事项 一、openssl对称加密和非对称加密算法对比 OpenSSL 是一个广泛使…...
web前端动画按钮(附源代码)
效果图 源代码 HTML部分 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> …...
go函数传值是值传递?还是引用传递?slice案例加图解
先说下结论 Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。 值语义类型:参数传递的时候,就是值拷贝,这样就在函数中就无法修改原内容数据。 基本类型:byte、int、bool…...
PostgreSQL数据库笔记
PostgreSQL 是什么 PostgreSQL(简称Postgres或PG)是一个功能强大、可靠性高、可扩展性好的开源对象-关系数据库服务器(ORDBMS),它以加州大学伯克利分校计算机系开发的POSTGRES版本4.2为基础。 发展历程 起源与发展&a…...
财务软件源码SaaS云财务
在如今的商业环境中,准确的财务管理是一家企业取得成功的关键。然而,传统的财务管理方法已经无法满足现代企业的需求,需要一个全新的解决方案。推出了全新的财务软件为您提供完美的解决方案。 选择财务软件源码,您将享受到以下优…...
Elasticsearch集群和Kibana部署流程
搭建Elasticsearch集群 1. 进入Elasticsearch官网下载页面,下载Elasticsearch 在如下页面选择Elasticsearch版本,点击download按钮,进入下载页面 右键选择自己操作系统对应的版本,复制下载链接 然后通过wget命令下载Elastics…...
丹摩征文活动 | 丹摩智算:大数据治理的智慧引擎与实践探索
丹摩DAMODEL|让AI开发更简单!算力租赁上丹摩! 目录 一、引言 二、大数据治理的挑战与重要性 (一)数据质量问题 (二)数据安全威胁 (三)数据管理复杂性 三、丹摩智算…...
【Django】Clickjacking点击劫持攻击实现和防御措施
Clickjacking点击劫持 1、clickjacking攻击2、clickjacking攻击场景 1、clickjacking攻击 clickjacking攻击又称为点击劫持攻击,是一种在网页中将恶意代码等隐藏在看似无害的内容(如按钮)之下,并诱使用户点击的手段。 2、clickj…...
Ansys Zemax | 手机镜头设计 - 第 4 部分:用LS-DYNA进行冲击性能分析
该系列文章将讨论智能手机镜头模组设计的挑战,从概念和设计到制造和结构变形分析。本文是四部分系列中的第四部分,它涵盖了相机镜头的显式动态模拟,以及对光学性能的影响。使用Ansys Mechanical和LS-DYNA对相机在地板上的一系列冲击和弹跳过程…...
工具收集 - java-decompiler / jd-gui
工具收集 - java-decompiler / jd-gui 参考资料 用法:拖进来就行了 参考资料 https://github.com/java-decompiler/jd-gui 脚本之家:java反编译工具jd-gui使用详解...
《无线重构世界》射频模组演进
射频前端四大金刚 射频前端由PA、LNA、滤波器、开关“四大金刚” 不同的模块有自己的工艺和性能特点 分层设计 射频前端虽然只由PA、LNA、开关、混频器4个模块构成,但不同模块之间相互连接且相互影响。如果将射频系统当成一个整体来理解,其中的细节和…...
渗透测试---docker容器
声明:学习素材来自b站up【泷羽Sec】,侵删,若阅读过程中有相关方面的不足,还请指正,本文只做相关技术分享,切莫从事违法等相关行为,本人一律不承担一切后果 目录 一、Docker的作用与优势 二、docker的核心…...
【go从零单排】Atomic Counters原子计数
🌈Don’t worry , just coding! 内耗与overthinking只会削弱你的精力,虚度你的光阴,每天迈出一小步,回头时发现已经走了很远。 📗概念 在 Go 语言中,原子计数器(Atomic Counters)是…...
VSCode中python插件安装后无法调试
问题 VSCode中python插件安装后无法调试,如下,点击调试,VScode中不报错,也没有调试 解决方法 1、查看配置 打开所在路径 2、拷贝 将整个文件夹拷贝到vscode默认路径下 3、问题解决 再次调试,可以正常使用了…...
用react实现radio同时关联proform组件
实现: <ProFormRadio.GroupradioType{button}name{[bodyConfig, format]}label"请求体格式"initialValue{json}options{createTabs}fieldProps{{buttonStyle: solid,wrapperMarginInlineEnd: 20,onChange: e > {let v e.target.value;databaseMod…...
Objective-C 1.0和2.0有什么区别?
Objective-C ObjC比较小众,在1980年左右由Stepstone公司的Brad Cox和Tom Love发明。后来NeXT公司获得ObjC语言使用权,再后来到1996年NeXT被苹果公司收购也变成苹果公司使用,Mac市场占有率本身就不高,ObjC没有太多程序员。在移动互…...
TCP连接如何保障数据传输安全
TCP连接保障数据传输安全主要通过以下几个机制实现: 1、建立连接:TCP通过三次握手过程建立连接,确保通信双方实体真实存在,为后续的可靠数据传输打下基础。 2、序号机制:TCP为每个传输的数据包分配一个序号ÿ…...
【论文复现】ChatGPT多模态命名实体识别
📝个人主页🌹:Eternity._ 🌹🌹期待您的关注 🌹🌹 ❀ChatGPT ChatGPT辅助细化知识增强!1. 研究背景2. 模型结构和代码3. 任务流程第一阶段:辅助精炼知识启发式生成第二阶段…...
管理 Elasticsearch 变得更容易了,非常容易!
作者:来自 Elastic Ken Exner Elasticsearch 用户,我们听到了你的心声。管理 Elasticsearch 有时会变得很复杂,面临的挑战包括性能调整、问题检测和资源优化。我们一直致力于简化你的体验。今天,我们宣布了自收购 Opster 以来的一…...
SynchronousQueueworkQueue.offer() 和 poll() 方法的超时控制及线程池的讨论
2024.11.12 11:26 问:workQueue.offer(command) 这里 workQueue.poll(keepAlive, TimeUnit.NANOSECONDS) : 这里的 workQueue.offer(command) 没有超时时间控制? Claude 3.5 Haiku 非常好的问题!我来详细解释 offer() 和 poll() 方法的区别…...
全国城建中心官网入口/百度seo关键词优化市场
Hbase支持的Hadoop版本http://hbase.apache.org/book/configuration.html#hadoop 解压文件后,配置conf目录下的3个文件: hbase-env.sh(设定JAVA_HOME) regionserver(将slaves相应的IP加入) hbase-site.xml …...
电商平台网站设计公司/北京seo公司华网白帽
Linux用户操作记录我们都可以通过命令history来查看历史记录,但是如果因为某人误操作了删除了重要的数据,那么Linux history命令就基本上不会有太大的作用了。我们怎么来查看Linux用户操作记录,有没有什么办法实现通过记录登陆后的IP地址和某…...
小学校园门户网站建设方案/百度竞价推广登录
1005:创建表失败1006:创建数据库失败1007:数据库已存在,创建数据库失败1008:数据库不存在,删除数据库失败1009:不能删除数据库文件导致删除数据库失败1010:不能删除数据目录导致删除…...
一个网页的制作流程/陕西优化疫情防控措施
java中集合的区别是什么?在java中集合主要分为:List,Set,Map三种,其中List与Set是继承自Collection,而Map不是。List与Set的区别:List中的元素有存放顺序,并且可以存放重复元素&…...
定制网站建设加盟代理/建站之星
LiulishuoPreview 项目地址:LiulishuoPreview简介:手摸手带你用 VideoView 实现英语流利说炫酷引导页Imitate Liulishuo guide view. Demo...
python+视频播放网站开发/宁波网站优化
mysql多实例部署软件安装//安装wget工具[rootlocalhost ~]# yum -y install wget//下载二进制格式的mysql软件包[rootlocalhost ~]# wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.31-linux-glibc2.12-x86_64.tar.gz使用xftp把包传进来配置用户和组并解压…...