AVL树的原理及其实现

文章目录

  • 前言
  • 了解AVL树
  • AVL树的特点
  • AVL树的节点
  • 调整方案
    • 右单旋
      • 为什么要右单旋呢?
      • 右单旋代码
    • 左单旋
      • 为什么要左单旋?
      • 左单旋代码
    • 左右双旋
      • 左右双旋之后平衡因子的情况
      • 左右双旋代码实现
    • 右左双旋
      • 右左双旋代码:
  • 简单测试

前言

回顾我们对于二叉搜索树的了解,二叉搜索树的效率跟树的高度成反比。而且,数据插入的随机性导致普通的二叉搜索树甚至可能退化成一条“单链表”,这样的结构查找起来时间复杂度直奔O(N),这无疑是我们不想看到的。我们希望,无论数据怎么插入,二叉搜索树的结构都应该保持平衡,即看起来更加接近于完全二叉树或者满二叉树·。AVL树因此诞生。

了解AVL树

AVL树又叫平衡二叉搜索树,得名于它的发明者Georgy Adelson-Velsky和Evgenii Landis。它的出现很好的解决了二叉搜索树的效率退化问题。AVL树的特点就是任一一个节点的左右子树的高度差不超过1。这个特点可以使二叉搜索树的高度始终保持在logn的级别,从而保证了查找效率稳定在O(logn)级别。具体是怎么实现这一特点的呢?下面结合代码给您讲解。

AVL树的特点

  • AVL树具有二叉搜索树的性质,即对于任意一个节点,左子树的值均小于根节点的值,右子树的值均大于根节点的值。
  • 每个节点都有一个平衡因子属性(可视为一个int变量),记录着左右子树的高度差(一般是右子树高度减去左子树高度
  • 查找、删除、插入的时间复杂度均为O(logn).

AVL树的节点

根据AVL树的特点,AVL树的每一个节点都得有一个记录左右子树高度差的平衡因子。除此之外,还需要有一个指向父节点的指针。具体如下:

		//节点信息template<class K, class V>class AVLNode {public:AVLNode(const pair<K, V>& kv):_bf(0), left(nullptr), right(nullptr),father(nullptr){}int _bf;//平衡因子pair<K, V> _kv;AVLNode<K, V>* left;AVLNode<K, V>* right;AVLNode<K, V>* father;};

调整方案

下面所述代码中,平衡二叉树的平衡因子均表示右子树高度减去左子树高度。
当我们向一颗平衡二叉树里插入一个元素,插入节点的祖先节点的平衡因子都有可能会因此改变。从当前新节点的父节点开始向上遍历,重复以下几个步骤:

  1. 用一个指针指cur向新节点表示当前节点,一个指针pa指向其父节点。
  2. 如果cur节点在pa的左子树中,那么这个pa节点的平衡因子减一,否则加一。
  3. 更新平衡因子之后,观察pa平衡因子大小。如果为0,则说明当前pa节点向上的所有父节点的平衡因子都不会改变。
  4. 如果·pa·平衡因子为-1或者1,则说明当前节点符合平衡二叉树节点特征,但是其父节点还不一定,于是继续向上遍历
  5. 如果pa平衡因子为2或者-2,则说明当前节点失衡了(左右子树的高度差大于1)!我们需要调整。

调整方案具体如下:

右单旋

如果cur的平衡因子为-1,且pa的平衡因子为-2。那么我们选择让pa节点右单旋
在这里插入图片描述

上图就是这种情况**。矩形表示一颗子树**。右旋过之后就变成了这样:
在这里插入图片描述

为什么要右单旋呢?

或者说为什么右单旋之后,pacur的平衡因子会为0呢?

假设在没有旋转的时候,pa的左子树高度为N,则右子树高度为N-2。即上图黄色矩形的表示的树的高度为N-2。
由于cur的平衡因子为-1,则cur左子树的高度为N-1,右子树的高度为N-2。即上图蓝色矩形表示的树的高度为N-2。

所以,我们完成这样一个右单旋之后,对于cur来说,左子树的高度依旧是N-1,但是右子树的高度变成了N-1,平衡因子也就变成了0。
对于pa来说,它的右子树高度依旧是N-2,但是左子树高度变成了N-2,平衡因子也变成了0。

右单旋之后还需不需要继续往上调整呢呢?答案是不用了,由于curpa的平衡因子都是0了,再往上的节点的平衡因子保持不变。

继续思考,平衡因子是没问题了,但是这样旋转会不会改变二叉搜索树的性质呢?答案也是不会的。所谓的右单旋,就是把pa变成cur的右孩子,原本cur是pa的左孩子,pa节点包括pa的右子树的值均大于以cur子树的值。旋转之后cur的左子树的值依旧小于cur节点的值,cur右子树的值依旧大于cur节点的值。对于pa来说也是如此。

右单旋代码

//右单旋
void RotateR(pNode pa) {pNode subL = pa->left;pNode subLR = subL->right;pa->left = subLR;if (subLR) subLR->father = pa;subL->right = pa;pNode ppa = pa->father;pa->father = subL;if (pa == _root) {_root = subL;subL->father = nullptr;}else {if (pa == ppa->left) {ppa->left = subL;}else {ppa->right = subL;}subL->father = ppa;}subL->_bf = pa->_bf = 0;
}

左单旋

如果cur的平衡因子为1,且pa的平衡因子为2。那么我们选择让pa节点左单旋
在理解右单旋的原理之后,对于左单旋也就容易理解了,因为两者的旋转方式都是一样的,只不过“方向”不同。
在这里插入图片描述
这种情况我们就需要对pa进行左单旋,调整之后就变成了下图所示:
在这里插入图片描述

为什么要左单旋?

参考右单旋。

假设在没有旋转的时候,pa的左子树高度为N,则右子树高度为N+2。即上图黄色矩形的表示的树的高度为N。
由于cur的平衡因子为1,则cur右子树的高度为N+1,左子树的高度为N。即上图蓝色矩形表示的树的高度为N。

所以,我们完成这样一个左单旋之后,对于cur来说,右子树的高度依旧是N+1,但是左子树的高度变成了N+1,平衡因子也就变成了0。
对于pa来说,它的左子树高度依旧是N,但是右子树高度变成了N,平衡因子也变成了0。

左单旋代码

//左单旋
void RotateL(pNode pa) {pNode subR = pa->right;pNode subRL = subR->left;pa->right = subRL;if (subRL) subRL->father = pa;subR->left = pa;pNode ppa = pa->father;pa->father = subR;if (pa == _root) {_root = subR;subR->father = nullptr;}else {if (pa == ppa->left) {ppa->left = subR;}else {ppa->right = subR;}subR->father = ppa;}subR->_bf = pa->_bf = 0;
}

左右双旋

如果cur的平衡因子为1,且pa的平衡因子为-2。那么我们选择先让pa节点的左孩子左单旋。然后再让pa右单旋

这种情况只对pa进行右单旋不能保证让所有节点平衡,我们自能
在这里插入图片描述
图中的h表示子树的高度
在这里插入图片描述

仔细观察上图AVL树左右双旋的过程,就能明白为什么要双旋能让每个节点再次平衡了。值得注意的是,在双旋之后,pa于subL节点的平衡因子并不是固定的,它们随着新节点插入到subLR的位置的而变化。
例如:
在这里插入图片描述

左右双旋之后平衡因子的情况

  • 情况(1)如果新节点插入到subLR的左子树中,在双旋之后,这个新节点会变成subL的右子树节点,此时subL的平衡因子为0,pa的平衡因子为1
  • 情况(2)如果新节点插入到subLR的右子树中,双旋之后,这个新结点则会变成pa的左子树节点,此时pa的平衡因子为0,subL的平衡因子为-1
  • 情况(3)如果新插入节点本身就是subLR节点,即此时subLR的平衡因子为0。也就意味着双旋之后,pa和subL的平衡因子为0.
  • 但无论是上面的哪种情况,双旋之后,subLR的平衡因子都是0.

那更新pa和subL的平衡因子时如何区分是以上哪种情况呢?看subLR的平衡因子。如果是-1,表示新节点在subLR的左子树中,即情况(1)。如果是1,说明新节点在subLR的右子树中,即情况(2)。如果是0,表示新节点就是subLR本身,即情况(3)。

左右双旋代码实现

有了上面左右单旋的代码,左右双旋就能通过使用封装它们的函数来实现了:

//左右双旋
void RotateLR(pNode pa) {pNode subL = pa->left;pNode subLR = subL->right;int subLR_bf = subLR->_bf;RotateL(subL);RotateR(pa);if (subLR_bf == -1) {pa->_bf = 1 ;subL->_bf = 0;}else if (subLR_bf == 1) {pa->_bf = 0;subL->_bf = -1;}else if (subLR_bf == 0) {pa->_bf = 0;subL->_bf = 0;}else {//perror("RotateLR");}return;
}

右左双旋

如果cur的平衡因子为-1,且pa的平衡因子为2。那么我们选择先让pa节点的右孩子右单旋。然后再让pa左单旋
原理和左右双旋一致,只不过旋转的方向相反:
在这里插入图片描述
右左双旋的平衡因子的情况参考左右双旋,我这里就不做过多赘述了。

同样右左双旋的代码可以使用单旋的接口来实现

右左双旋代码:

//右左双旋
void RotateRL(pNode pa) {pNode subR = pa->right;pNode subRL = subR->left;int subRL_bf =subRL->_bf;RotateR(subR);RotateL(pa);if (subRL_bf == -1) {pa->_bf = 0;subR->_bf = 1;}else if (subRL_bf == 1) {pa->_bf = -1;subR->_bf = 0;}else if (subRL_bf == 0) {pa->_bf = 0;subR->_bf = 0;}else {//perror("RotateRL");}}

简单测试

通过上面的学习,现在我们就能基本实现AVL树的插入操作了。为了检查代码的正确性,下面给出一组测试数据插入到AVL树中,并遍历输出观察结果:

#include<iostream>
#include"myAVLTree.h"
using namespace std;
using namespace k_val;int main() {int a[10] = { 1,7,8,3,4,9,10,2,6,5 };AVLTree<int, int> avl;for (int i = 0; i < 10; i++) {avl.Insert({ a[i],a[i] });}avl.InOrder();return 0;
}

在这里插入图片描述
如果想观察·内部结构,可以自行打开调试窗口一一查看。

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

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

相关文章

NLP经典入门论文

1.基础部分 Word2Vec Efficient Estimation of Word Representations in Vector Space https://arxiv.org/abs/1301.3781v3 Transformer attention is all you need https://arxiv.org/abs/1706.03762 BERT Pre-training of Deep Bidirectional Transformers for Langua…

Altman确认:神秘Chatbot非GPT-4.5,OpenAI搜索引擎即将上线

&#x1f680; Altman确认&#xff1a;神秘Chatbot非GPT-4.5&#xff0c;OpenAI搜索引擎即将上线 摘要&#xff1a;近日&#xff0c;Sam Altman在哈佛大学的演讲中确认&#xff0c;引发广泛猜测的gpt2-chatbot并非OpenAI即将发布的下一代模型GPT-4.5。与此同时&#xff0c;关于…

亚信安慧AntDB:解锁数智化的新时代

亚信安慧AntDB的融合实时的特性使得它在数据库领域独树一帜。传统的数据库系统往往只能追求数据的准确性和一致性&#xff0c;但在实际的业务场景中&#xff0c;这些特性并不能满足企业的需求。AntDB的出现打破了传统束缚&#xff0c;为企业带来了全新的数据处理方式&#xff0…

低代码审计作业平台:引领企业实现审计高效革命

随着信息化时代的深入发展&#xff0c;审计工作面临着前所未有的挑战与机遇。传统的审计方式往往繁琐复杂&#xff0c;效率低下&#xff0c;已无法满足现代企业对高效、准确、智能的审计需求。在这样的背景下&#xff0c;审计作业低代码平台应运而生&#xff0c;以其独特的优势…

【思考】使用Vue Router在Vue.js中配置题目库链接的实现

在开发一个包含题目库的Web应用时&#xff0c;我们通常会遇到一个需求&#xff1a;需要将每个题目和一个特定的链接相对应&#xff0c;以便用户可以直接访问或分享单个题目。在Vue.js中&#xff0c;我们可以通过Vue Router来实现这一功能。 步骤1&#xff1a;安装Vue Router …

B/S模式的web通信(高并发服务器)

这里写目录标题 目标实现的目标 服务器代码&#xff08;采用epoll实现服务器&#xff09;整体框架main函数init_listen_fd函数&#xff08;负责对lfd初始化的那一系列操作&#xff09;epoll_run函数do_accept函数do_read函数内容补充&#xff1a;http中的getline函数 详解do_re…

Vue 3:定义下一代前端开发标准

Vue.js一直以来都是前端开发者钟爱的框架之一&#xff0c;而随着Vue 3的正式发布&#xff0c;这一爱恋将进一步深化。Vue 3的到来不仅意味着更快、更轻量级的框架&#xff0c;更重要的是&#xff0c;它引入了一系列强大的新特性和改进&#xff0c;为前端开发带来了全新的体验和…

新通知!2024年安徽省大数据企业申报流程、范围及条件

2024年安徽省大数据企业申报流程、范围及条件等内容如下&#xff0c;安徽省的企业单位可以了解一下&#xff0c; 一、安徽省大数据企业申报范围 在安徽省内注册成立一年以上&#xff0c;主要从事大数据服务、应用、产品制造等有关数据处理活动且符合《实施细则》第五条和第六…

Mac 报错 Zsh: command not found :brew

Mac 安装其他命令时报错 Zsh: command not found :brew终于找到一个能行的&#xff0c;还能够配置国内下载源&#xff0c;记录一下 执行 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"选择一个开始继续执行即可

21、Flink 的 Checkpoints 使用介绍

Checkpoints 1.概述 Checkpoint 使 Flink 的状态具有良好的容错性&#xff0c;通过 checkpoint 机制&#xff0c;Flink 可以对作业的状态和计算位置进行恢复。 2.Checkpoint 存储 Flink 开箱即用地提供了两种 Checkpoint 存储类型&#xff1a; JobManagerCheckpointStorag…

【C++初阶】第十站:vector 中通用函数的模拟实现

目录 vector中的三个重要迭代器 默认成员函数 构造函数(无参构造) 构造函数(函数模板) 构造函数(带有默认参数) size_t int 拷贝构造函数 赋值重载 析构函数 迭代器相关函数 begin和end 容量和大小相关函数 size capacity resize 修改容器内容相关函数 reser…

不想让Win系统更新,那就让它暂停一万年

按照下图所示进行操作 winR 输入 regedit&#xff0c;进入注册表编辑器 随后依次点击 HKEY_LOCAL_MACHINE ⬇ SOFTWARE ⬇ Microsoft ⬇ WindowsUpdate ⬇ UX ⬇ Settings 最后在右侧空白处 文件类型 新建DWORD&#xff08;32位&#xff09;值&#xff08;D&#xff09; 命名…

Liunx计划任务

目录 一.计划任务概念解析 二.Liunx计划任务管理工具 1. at命令 基本语法 时间格式 常用选项 2. crontab命令 crontab 文件 /etc/cron.allow /etc/cron.deny 使用规则 crontab 参数 crontab 文件格式 特殊用法 使用技巧与思路扩展&#xff1a; 步骤 示例 一.…

PyQt5的布局管理

文章目录 1.垂直布局和水平布局垂直布局&#xff08;QVBoxLayout&#xff09;&#xff1a;水平布局&#xff08;QHBoxLayout&#xff09;&#xff1a; 2. 布局中的addStrech2.1 我们首先看只有一个Strech的情况&#xff0c;比较容易理解2.2 两个Strech2.3 多个Strech 3.栅格布局…

FPGA HDMI Sensor无线航模摄像头

FPGA方案&#xff0c;接收摄像头sensor 图像数据后&#xff0c;通过HDMI输出到后端 客户应用&#xff1a;无线航模摄像头 主要特性&#xff1a; 1.支持2K以下任意分辨率格式 2.支持多种型号sensor 3.支持自适应摄像头配置&#xff0c;并补齐输出时序 4.可定制功能&#xff…

OpenHarmony 实战开发(南向)-Docker编译环境搭建

Docker环境介绍 OpenHarmony为开发者提供了两种Docker环境&#xff0c;以帮助开发者快速完成复杂的开发环境准备工作。两种Docker环境及适用场景如下&#xff1a; 独立Docker环境&#xff1a;适用于直接基于Ubuntu、Windows操作系统平台进行版本编译的场景。 基于HPM的Docker…

【ArcGIS Pro微课1000例】0058:玩转NetCDF多维数据集

一、NetCDF介绍 NetCDF(network Common Data Form)网络通用数据格式是由美国大学大气研究协会(University Corporation for Atmospheric Research,UCAR)的Unidata项目科学家针对科学数据的特点开发的,是一种面向数组型并适于网络共享的数据的描述和编码标准。NetCDF广泛应…

【外币兑换,简单贪心】

小明刚从美国回来&#xff0c;发现手上还有一些未用完的美金&#xff0c;于是想去银行兑换成人民币。可是听说最近人民币将会升值&#xff0c;并从金融机构得到了接下来十二个月可能的美元对人民币汇率&#xff0c;现在&#xff0c;小明想要在接下来一年中把美金都兑换成人民币…

【Java】Java中栈溢出的常见情况及解决方法

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Linux实验二:文件IO操作

目录 一、实验目的二、实验内容三、实验环境四、参考代码五、实验步骤步骤1. 编辑程序源代码test2.c步骤2. 编译源代码test2.c步骤3. 编辑源文件alice.txt步骤4. 运行程序test2 六、实验结果七、实验总结 一、实验目的 1、掌握Linux中系统调用、文件描述符的基本概念&#xff…