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,一经查实,立即删除!

相关文章

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;以其独特的优势…

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

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

【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; 命名…

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广泛应…

【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…

docker-compose管理jenkins

1.安装docker和compose 1.docker 更新系统&#xff1a;yum update 安装依赖项&#xff1a;yum install -y yum-utils device-mapper-persistent-data lvm2 配置镜像源&#xff1a;yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce…

5.12母亲节营销攻略:TikTok助力出海品牌赢得用户心

母亲节&#xff0c;作为一个全球性的节日&#xff0c;不仅是表达对母亲的感激之情的时刻&#xff0c;也是品牌们展示创意、赢得用户心的黄金机会。2024母亲节将至&#xff0c;如何利用TikTok在母亲节这一特殊时刻进行营销&#xff0c;赢得用户的心&#xff0c;成为出海品牌必须…

AWS-TGW同区域多vpc打通

同区域vpc打通方案配置 跨区域参考另一个博客 点我跳转 vpc110.30.0.0/16实例110.30.4.178 vpc2172.31.0.0/16实例2172.31.43.180 1. 新建tgw 默认配置即可 2.创建挂载&#xff0c;两个vpc打通就需要创建2个挂载 3. 观察tgw的路由规则 等待挂载完全创建完成后&#xff0c;会…

校友录系统的设计与开发

**中文摘要&#xff1a;**随着互联网技术的不断发展和普及&#xff0c;人们对于信息化、数字化的需求也越来越高。在此背景下&#xff0c;校友录系统的设计与开发显得尤为重要。本文旨在设计和开发一款方便实用的校友录系统&#xff0c;为学校提供一个联系和管理校友的平台&…

超分辨率专题 | 3 种方法、4 个教程、10 个数据集,一文 Get 核心知识点

2010 年 12 月&#xff0c;清华大学电子工程系教授苏光大接到一通不寻常的电话&#xff0c;内蒙古自治区准格尔刑警队的警员拿着一张模糊不清的犯罪嫌疑人人脸图像&#xff0c;向苏光大寻求帮助。 「这张图像是由路边的监控摄像头拍摄的&#xff0c;像素非常低&#xff0c;肉眼…

2024数维杯C题24页完整解题思路+1-4问代码解题+运行高清结果图

C题天然水合物资源量评价 点击链接加入群聊【2024数维杯数学建模ABC题资料汇总】&#xff1a; 2024数维杯C题完整思路24页配套代码1-4问后续参考论文https://www.jdmm.cc/file/2710638 下面内容是持续更新的 根据勘探数据确定天然气水合物资源的分布范围。 假设勘探区域内的…

Mura CMS processAsyncObject SQL注入漏洞复现(CVE-2024-32640)

0x01 产品简介 Mura CMS(Content Management System)是一款用于创建和管理网站内容的开源内容管理系统。它具有许多功能和灵活性,使其成为许多网站开发者和内容创作者的首选工具。是一个强大的企业网站解决方案,可用于创建和管理公司网站、产品目录、新闻发布、客户支持和…

异步时序电路的分析方法

异步时序电路的分析方法 在异步时序电路中&#xff0c;只有部分触发器由时钟脉冲 CP触发&#xff0c;其它触发器由电路内部信号触发。分析异步时序电路时需写出时钟方程&#xff0c;并特别注意各触发器的时钟条件在何时满足&#xff0c;其状态方程才能使用 Tips&#xff1a;在…