数据结构:探秘AVL树

本节重点

  • 理解AVL树的概念
  • 掌握AVL树正确的插入方法
  • 利用_parent指针正确更新平衡因子
  • 掌握并理解四种旋转方式:左单旋,右单旋,左右双旋,右左双旋

一、AVL树的概念

AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。

AVL树最先发明自平衡二叉搜索树,AVL是一颗空树,或者具备下列性质的二叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树,通过控制高度差去控制平衡。

AVL树的实现引入一个平衡因子的概念(balance factor)的概念,每个节点都有一个平衡因子,任何节点的平衡因子等于右子树高度减去左子树高度,也就是说任何节点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡,就像一个风向标一样。

AVL树整体的节点数量和分布和完全二叉树类似,但是AVL树高度可以控制在logN,所以增删查改的效率也可以控制在O(logN),相比二叉搜索树有了本质的提升。

如图,每个节点上方的小数字表示该节点的平衡因子,平衡因子只能为-1/1/0当为2/-2时我们要通过旋转将左右子树重新达到平衡状态。 

二、AVL树的实现

2.1 AVL树的结构

AVL树我们分成两部分来实现,一部分是单个节点的定义,一部分是AVL树。并且用两个类进行封装:

template<class K>
struct AVLTNode//AVL树节点的定义
{K _key;struct AVLTNode<K>* _left;struct AVLTNode<K>* _right;struct AVLTNode<K>* _parent;//引入parent方便我们快速向上更新平衡因子int _bf;//平衡因子(balance factor)//构造函数:AVLTNode(K key):_key(key), _left(nullptr), _right(nullptr), _parent(nullptr),_bf(0){}
};//AVL树的定义
template<class K>
class AVLTree
{typedef struct AVLTNode<K> Node;
public:
private:Node* root = nullptr;
};

2.2 AVL树的插入

AVL树插入的步骤:

  1. 插入一个值按照二叉搜索树的规则插入
  2. 新增节点后,只会影响祖先节点的高度,也就是可能会影响部分祖先节点的平衡因子,所以更新从新增节点->根节点路径上的平衡因子,实际最坏情况下更新到根,有些情况更新到中间就停止了。
  3. 更新平衡因子过程中没有出现问题,则插入结束
  4. 更新平衡因子的过程中出现不平衡,对不平衡子树旋转,旋转后降低了子树的高度,不会再影响上一层,所以插入结束

2.2.1 先按照搜索二叉树规则插入

在插入之前,我们需要判断该AVL树是否为空,若为空直接在_root 新增节点并返回,若不为空说明AVL树中已经存在节点,这时我们需要从根节点开始依次按照搜索树的规则寻找插入位置,找到之后创建新节点并链入到AVL树中。

代码示例:

bool Insert(const K& key){if (_root == nullptr){//AVL是一颗空树_root=new Node(key)return 1;}else{Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{assert(false);}}//链接新节点:cur = new Node(key);if (cur->_key > parent->_key){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;}

2.2.2 判断并更新平衡因子

更新规则:

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

插入节点会增加子树高度,影响当前节点平衡因子因为一次只能插入一个节点所以平衡因子要么++要么--:

插入在右子树平衡因子++;插入在左子树平衡因子--。

平衡因子的三种情况:(0,-1/1,-2/2)

1、更新后parent节点平衡因子为0

说明更新之前parent节点平衡因子为1或-1也就是左右子树一边高一边低,节点插入在低的一边,插入后左右平衡不会影响上一级节点的平衡因子。

2、更新后parent节点平衡因子为1/-1

说明更新之前parent节点平衡因子为0,插入后左右子树一边高一边低会影响上一级节点的平衡因子,所以要继续向上更新。

3、更新后parent节点平衡因子为2/-2

说明更新之前parent节点平衡因子为1/-1也就是左右子树一边高一边低,节点插入在高的一边

代码示例: 

while (parent)
{if (parent->_right == cur){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 0){//说明新增节点在低的一边,插入后左右子树平衡//不会影响祖先节点的 _bf直接breakbreak;}else if(parent->_bf==1||parent->_bf==-1){//新增节点之后为1或-1说明之前为0(左右子树平衡)//此时需要更新依次更新祖先节点的 _bf直到更新到根节点或某一祖先节点_bf==0cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == 2 && cur->_bf == 1){//单纯右边高,左旋RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){//单纯左边高,右旋RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//先左后右,右左双旋RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//先右后左,左右双旋RotateLR(parent);}else{assert(false);}break;}else{assert(false);}
}

2.2.3 (不平衡)旋转子树

旋转的原则:

  • 保持搜索树的规则
  • 让旋转的树从不满足变为平衡,其次降低树的高度

首先我们需要明白的是旋转操作分为两部分,一是调整节点之间的链接关系,二是更新平衡因子     _bf

左单旋(RotateL)

当parent的平衡因子为2,且cur的平衡因子为1时AVL树会呈现右子树一边高的形式,这时我们需要进行左旋操作,需要注意的是满足 parent->_bf==2 && cur->_bf==1 条件的AVL树的形式可能有多种:

为了便于理解,我们可以选择一种简单的情况进行分析并写出相应代码:

代码示例:

void RotateL(Node* parent){Node* SubR = parent->_right;Node* SubRL = SubR->_left;Node* pparent = parent->_parent;if (SubRL){SubRL->_parent = parent;}parent->_right = SubRL;SubR->_left = parent;parent->_parent = SubR;if (parent == _root){_root = SubR;SubR->_parent = nullptr;}else{if (pparent->_right == parent){pparent->_right = SubR;}else{pparent->_left = SubR;}SubR->_parent = pparent;}//节点链接关系调整完成后更新平衡因子:SubR->_bf = 0;parent->_bf = 0;}
右单旋(RotateR)

类似的是,满足 parent->_bf==-2 && cur->_bf==-1 条件的AVL树的形式也可能有多种

我们也选择其中一种简单的情况进行分析和编写代码:

void RotateR(Node* parent){Node* SubL = parent->_left;Node* SubLR = SubL->_right;Node* pparent = parent->_parent;if (SubLR){SubLR->_parent = parent;}parent->_left = SubLR;SubL->_right = parent;parent->_parent = SubL;if (parent == _root){_root = SubL;SubL->_parent = nullptr;}else{if (pparent->_right == parent){pparent->_right = SubL;}else{pparent->_left = SubL;}SubR->_parent = pparent;}//节点链接关系调整完成后更新平衡因子:SubL->_bf = 0;parent->_bf = 0;}
左右双旋(RotateLR)

与单旋不同的是,双旋对应的AVL树的结构不再是单纯一边高,我们由条件判断很容易就可以看出来(parent->_bf == -2 && cur->_bf == 1 或 parent->_bf == 2 && cur->_bf == -1),此时我们发现AVL树节点类似“折线形”排列,这时单纯的单旋无法使二叉树再次平衡,我们需要进行两次单旋来解决。

类似的是满足双旋触发条件时,AVL树的结构要是拓展开来也有非常多种情况,我们可以选择其中一种较为简单的情况来分析并编写相应代码:

需要注意的是左右双旋的时候对SubLR的_bf也要进行考虑,目的是确定SubLR是否存在单个的子树,因为最终SubLR的子树会链入SubL或者parent影响两个节点的平衡因子

void RotateLR(Node* parent){Node* SubL = parent->_left;Node* SubLR = SubL->_right;int bf = SubLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 1){SubLR->_bf = 0;parent->_bf = 0;SubL->_bf = -1;}else if (bf == 0){parent->_bf = 0;SubLR->_bf = 0;SubL->_bf = 0;}else{SubLR->_bf = 0;parent->_bf = 1;SubL->_bf = 0;}}
右左双旋(RotateRL)

 

void RotateRL(Node* parent){Node* SubR = parent->_right;Node* SubRL = SubR->_left;int bf = SubRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){SubRL->_bf = 0;parent->_bf = -1;SubR->_bf = 0;}else if (bf == 0){parent->_bf = 0;SubLR->_bf = 0;SubL->_bf = 0;}else{SubRL->_bf = 0;parent->_bf = 0;SubR->_bf = 1;}}

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

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

相关文章

电源系统的热设计与热管理--以反激式充电器为例

前言 反激电源常用于各种电子设备中&#xff0c;比如充电器、适配器等&#xff0c;它们通过变压器进行能量转换。高温环境可能对电子元件造成影响&#xff0c;特别是像MOSFET、二极管、变压器这样的关键部件&#xff0c;导致效率变低&#xff0c;甚至可能导致功能失效。还有安…

linux课程学习二——缓存

一.文件io与标准io的一个区别 遇到死循环可以ctrl c结束进程 使用printf输出&#xff0c;输出没有问题 用wirte输出&#xff0c;参数1&#xff0c;可以理解为上面介绍的linux标准文件描述符的1&#xff08;STDOUT&#xff09;标准输出&#xff0c;我们加上一个死循环while&…

Kafka中的消息如何分配给不同的消费者?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka中的消息如何分配给不同的消费者&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka中的消息如何分配给不同的消费者&#xff1f; 在 Kafka 中&#xff0c;消息是通过 主题&#xff08;Topic&#xff09; 进行组织的&…

Android的安全问题 - 在 Android 源码的 system/sepolicy 目录中,区分 public、private 和 vendor的目的

参考&#xff1a;Google文档 在 Android 8.0 及更高版本中自定义 SEPolicy 在 Android 源码的 system/sepolicy 目录中&#xff0c;区分 public、private 和 vendor 是为了模块化 SELinux 策略&#xff0c;并明确不同部分的访问权限和接口边界。这种设计主要基于以下原因&…

Java NIO之FileChannel 详解

关键点说明 文件打开选项&#xff1a; StandardOpenOption.CREATE - 文件不存在时创建 StandardOpenOption.READ/WRITE - 读写权限 StandardOpenOption.APPEND - 追加模式 StandardOpenOption.TRUNCATE_EXISTING - 清空已存在文件 缓冲区操作&#xff1a; ByteBuffer.wrap…

stock-pandas,一个易用的talib的替代开源库。

原创内容第841篇&#xff0c;专注智能量化投资、个人成长与财富自由。 介绍一个ta-lib的平替——我们来实现一下&#xff0c;最高价突破布林带上轨&#xff0c;和最低价突破布林带下轨的可视化效果&#xff1a; cross_up_upper stock[high].copy()# cross_up_upper 最高价突破…

JVM 面经

1、什么是 JVM? JVM 就是 Java 虚拟机&#xff0c;它是 Java 实现跨平台的基石。程序运行之前&#xff0c;需要先通过编译器将 Java 源代码文件编译成 Java 字节码文件&#xff1b;程序运行时&#xff0c;JVM 会对字节码文件进行逐行解释&#xff0c;翻译成机器码指令&#x…

【JavaScript】合体期功法——DOM(一)

目录 DOMWeb API 基本概念作用和分类 什么是 DOMDOM 树DOM 对象 获取 DOM 元素根据 CSS 选择器来获取 DOM 元素选择匹配的第一个元素选择匹配的多个元素 其他获取 DOM 元素方法 修改元素的内容对象.innerText 属性对象.innerHTML 属性案例&#xff1a;年会抽奖 修改元素属性修改…

GAMMA数据处理(十)

今天向别人请教了一个问题&#xff0c;刚无意中搜索到了一模一样的问题 不知道这个怎么解决... ok 解决了 有一个GAMMA的命令可转换 但是很奇怪 完全对不上 转换出来的行列号 不知道为啥 再试试 是因为经纬度坐标的小数点位数 de as

Java入门知识总结——章节(二)

ps&#xff1a;本章主要讲数组、二维数组、变量 一、数组 数组是一个数据容器&#xff0c;可用来存储一批同类型的数据 &#x1f511;&#xff1a;注意 类也可以是一个类的数组 public class Main {public static class Student {String name;int age; // 移除 unsignedint…

动态IP:网络世界的“变色龙”如何改变你的在线体验?

你知道吗&#xff1f;有时候我觉得动态IP就像是网络世界里的“变色龙”。它不像静态IP那样一成不变&#xff0c;而是随时在变化&#xff0c;像是一个永远在换衣服的演员。你永远不知道它下一秒会变成什么样子&#xff0c;但正是这种不确定性&#xff0c;让它变得特别有趣。想象…

从24GHz到71GHz:Sivers半导体的广泛频率范围5G毫米波产品解析

在5G技术的浪潮中&#xff0c;Sivers半导体推出了创新的毫米波无线产品&#xff0c;为通信行业带来高效、可靠的解决方案。这些产品支持从24GHz到71GHz的频率&#xff0c;覆盖许可与非许可频段&#xff0c;适应高速、低延迟的通信场景。 5G通信频段的一点事儿及Sivers毫米波射频…

aocache:AOCache 新增功能深度解析:从性能监控到灵活配置的全方位升级

最近对aocache 进行了重要升级&#xff0c;最新版本0.6.0增加了几项新功能&#xff1a;性能分析日志&#xff0c;AOCache性能分析工具&#xff0c;切入点自定义配置&#xff0c;全局配置&#xff0c;本文详细说明这几项目新功能的作用和使用方式。 一、性能分析日志 需求背景…

Java EE 进阶:MyBatis-plus

MyBatis-plus的介绍 MyBatis-plus是MyBatis的增强工具&#xff0c;在MyBatis的基础上做出加强&#xff0c;只要MyBatis有的功能MyBatis-plus都有。 MyBatis-plus的上手 添加依赖 在我们创建项目的时候&#xff0c;我们需要添加MyBatis-plus和mysql的依赖 MyBatis-plus的依赖…

GitHub和Gitee上的一些AI项目

以下是GitHub和Gitee上的一些AI项目&#xff1a; GitHub上的AI项目 TensorFlow&#xff1a;一个端到端开源机器学习平台&#xff0c;包含大量工具和库&#xff0c;广泛应用于图像识别、自然语言处理等领域。PyTorch&#xff1a;由Facebook开发的开源深度学习框架&#xff0c;…

JavaScript网页设计高级案例:构建交互式图片画廊

JavaScript网页设计高级案例&#xff1a;构建交互式图片画廊 在现代Web开发中&#xff0c;交互式元素已成为提升用户体验的关键因素。本文将通过一个高级案例 - 构建交互式图片画廊&#xff0c;展示如何结合HTML和JavaScript创建引人入胜的网页应用。这个案例不仅涵盖了基础的…

Linux命令大全:从入门到高效运维

适合人群&#xff1a;Linux新手 | 运维工程师 | 开发者 目录 一、Linux常用命令&#xff08;每天必用&#xff09; 1. 文件与目录操作 2. 文件内容查看与编辑 二、次常用命令&#xff08;按需使用&#xff09; 1. 系统管理与监控 2. 网络与通信 3. 权限与用户管理 三、…

Windows 10/11 使用 VSCode + SSH 免密远程连接 Ubuntu 服务器(指定端口)

摘要&#xff1a; 本文详细介绍如何在 Windows 系统上通过 VSCode Remote-SSH 免密登录远程 Ubuntu 服务器&#xff08;SSH 端口 2202&#xff09;&#xff0c;避免每次输入密码的繁琐操作&#xff0c;提高开发效率。 1. 环境准备 本地系统&#xff1a;Windows 10/11远程服务…

一些需要学习的C++库:CGAL和Eysshot

写在前面&#xff1a; 从开始工作到现在&#xff0c;去过多家公司&#xff0c;多个行业&#xff0c; 虽然大部分时间在通信业&#xff0c;但也有其它的行业的工作没有做完&#xff0c;但也很感兴趣。每次想要研究一下时&#xff0c;总是想不起来。 这里写一些信息&#xff0c;…

蓝桥杯16天刷题计划一一Day01

蓝桥杯16天刷题计划一一Day01&#xff08;STL练习&#xff09; 作者&#xff1a;blue 时间&#xff1a;2025.3.26 文章目录 蓝桥杯16天刷题计划一一Day01&#xff08;STL练习&#xff09;[P1540 [NOIP 2010 提高组\] 机器翻译 - 洛谷 (luogu.com.cn)](https://www.luogu.com.…