C++中高阶数据结构(AVL树的原理讲解)

AVL树

AVL树的定义

avl本质是搜索树,是高度平衡二叉搜索树.特点是:任何树的左右子树的高度差不超过1.最大的高度差值最大也只能是1,也称之为平衡因子,

平衡因子就是右子树减去左子树的值,这个值的绝对值的最大值只能是1.这个平衡因子不是必须的,只是一种控制方式,方便我们更便捷的控制树.

节点的定义

struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorpair<K, V> _kv;AVLTreeNode(const pair<K,V> kv): _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}
};

向AVL树插入节点

  • 先按着搜索树的规则插入节点
  • 接着利用平衡因子来观测该树是否还是AVL树
    • 新插入的节点可能会影响该节点的部分祖先的平衡因子的值.
    • 更新规则:在c的左边新增,那么p->bf–,在c的右边新增,那么p->bf++,那么是否还会继续影响祖先呢?取决于p的高度是否变化.
    • 更新之后:父亲的 平衡因子如果是0的话,那么p所在的子树的高度不变不会影响爷爷.(如果p的平衡因子更新之后是0,就说明过更新之前是1或者是-1,说明是在矮的那一边插入了节点,p的高度不变,不会影响爷爷.),此时更新结束
    • 更新之后p的平衡因子是1或者是-1,那么p所在的子树的高度变了.会影响爷爷,说明更新之前p->bf是0,在p的有一边插入之后,p的高度变化了,就会影响爷爷.
    • 更新之后p的平衡因子成了2或者是-2,此时p所在子树就不是AVL树了,那么就需要进行旋转处理了.
    • 最后c成了根节点之后,那么就是更新条件结束了
  • 三种结束条件:
    • 更新到root结束,p->bf==0结束,旋转让parent所在子树的高度回到了插入之前,不会对上层的bf有影响,结束.

代码实现:

bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent){if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = cur->_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){RotateLR(parent);}else{RotateRL(parent);}break;}else{// 插入之前AVL树就有问题assert(false);}}return true;}

AVL中的旋转处理

根据插入数据的不同情况可以分为四种情况的

左单旋

当碰到如下图的情况:(红色的是插入节点之后节点对应的平衡因子的值,红色的方块代表插入了一个节点)

当c的右边增加了节点导致了p的平衡因子变成了2之后,此时将subR的左子树连接到parent的右子树上,紧接着将整个parent为根,a为左子树,b为右子树的整棵树连接到subR的左边.此时再计算平衡因子,发现都成了0,整棵树就满足了avl树的规则.
在这里插入图片描述

这里subR在插入节点之前,b子树和c子树的高度一定是相同的(高度也可以是0),只有如此,subR的节点的平衡因子才是0,假如b子树和c子树的高度不同,那么subR的平衡因子是值可能是1或者是-1,此时在subR的右边插入节点,subR节点的平衡因子可能会变成0或者是2,若变成0,avl树的更新就结束了,根本就不需要调整,若变成了2,那么需要调整的树就是subR这颗树,而不是parent这个树.

同理,a子树的节点的高度也一定是h

  • 如果a子树的高度是h+1,那么parent的平衡因子就是0了,此时无论是在parent的左边还是右边插入元素,都无法使parent的平衡因子更新成2或者是-2
  • 如果a子树的高度使h-1,那么此时这颗树根本就不是AVL树了,说明在新节点插入之前就不是AVL树

代码实现:

void RotateL(Node* parent)
{Node* subR = parent->_left;Node* subRL = subR->_right;Node* ppnode = parent->_parent;parent->_right = subRL;if(subRL) // subRL的高度可能是0subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;// 假如插入节点之前parent就是整棵树的根if(ppnode == nullptr){_root = subR;subR->_parent = nullptr;}else{if(ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf =0;subR-> _bf = 0;
}

右单旋

当遇到如下图的情况时:(红色是更新之后的平衡因子的值):

新节点插入到较高左子树的左侧,就使用右单旋.因为此时的树看上去就是整个左侧高,右侧低的形式

右单旋的方法:将subL的b这个右子树连接到parent的左边,紧接着,以parent为根,b为左子树,c为右子树的整棵树连接到subL的右边.

在这里插入图片描述

代码实现:

void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* grandParent = parent->_parent;parent->_left = subLR;// 同理,subLR的高度可能是0 if(subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;// 假如插入节点之前parent就是整棵树的根if(_root == parent){_root = subL;subL->_parent = nullptr;}else{if(grandParent->_left ==  parent){grandParent->_left = subL;}else{grandParent->_right = subL;}subL->_parent = grandParent;}parent->_bf =0;subL->_bf = 0;
}

左右双旋

当新的节点插入在较高左树的右边时,需要进行左右双旋.

假定是如下图的情况:
在这里插入图片描述

此时在h这个子树插入数据时,整棵树的左边是较高的,此时我们假如采用右旋转,则会:
在这里插入图片描述

会发现,右旋之后,值为30的节点的平衡因子还是-2,所以我们不能采用单旋了,使用双旋.

  • 假设h是大于0的情况:

    先将b子树拆分:
    在这里插入图片描述

    将b拆分为值为40(这里40只是例子,只要满足时大于30小于60即可)的节点和e子树和f子树,那么此时由e和f两个子树了,新的节点就会有两种选择了.

    • 当在e子树插入数据时:以30为断点进行左单旋,接着对整棵树进行右单旋
      在这里插入图片描述

      通过图可知,最后形成的树是符合要求的,并且各自的平衡因子经过计算都是满足要求的.

    • 当在f子树插入节点时:
      对仍然对局部进行左单旋,接着对整棵树进行右单旋.

      在这里插入图片描述

      并且经过计算之后的平衡因子经过计算之后也是符合要求的.

    当h==0时:
    此时e和f就不存在了,

    • 当在30的右边插入节点时,就先对30为根的树进行做单旋,对整棵树进行右单旋.如下图所示,最终计算出的平衡因子都是符合要求的.

    在这里插入图片描述

    • 当在30的左边插入节点时:可以直接对整棵树进行右单旋即可.

    最终形成的树的关键节点的平衡因子的如何确定:由上图可以看出规律

    • 当h!=0时
      • 当新节点在e子树插入时:40所在节点的平衡因子是0,30所在节点的平衡因子是0,60所在节点的平衡因子是1.
      • 当新节点在f子树插入时:40所在节点的平衡因子是0,30所在节点的平衡因子是-1,60所在节点的平衡因子是0.
    • 当h==0时,这个三个关键节点的平衡因子都是0

    如何确定规律呢?

    因为e和f这两颗子树的高度是相同的.所以在e树插入数据时,e的parent的平衡因子就会更新为-1,
    在这里插入图片描述

    当是在f树插入节点时,f的parent的平衡因子就会更新成1.
    在这里插入图片描述

    当e和f不存在时:
    在这里插入图片描述

    代码实现:

    可以就可以利用subL的右节点的平衡因子的值来确定关键节点的平衡因子的值

    这里可以复用前面的代码,但是前面的左旋和右旋都会将节点的平衡因子都改成0.所以需要提前保存这个值

    void RotateLR(Node* parent)
    {// 记录节点,为了保存节点里的_bf的值.Node* subL = parent->_left;Node* subLR = subL->_right;// 提前保存好平衡因子,防止被修改int bf = subLR->_bf;// 直接调用RotateL(parent->_left);RotateR(parent);if(bf == -1){subLR->bf = 0;subL->_bf = 0;parent->_bf = 1;}else if(bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if(bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{// 此时就不是AVL树. ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/3a4298086b6f436385586435bd9eaad7.png)assert(false);  }
    }
    

右左双旋

当新节点插入在较高右树的左侧时,需要右左双旋了.采用的是和左右双旋类似的思想

在这里插入图片描述

  • 当h!=0时

    • 当在e树插入时:
      在这里插入图片描述

    • 当在f树插入时:
      在这里插入图片描述

  • 当h==0时
    在这里插入图片描述

代码实现:

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

如何验证一个树是否是avl树

可以计算每一个子树的高度,观察高度差.

可以先写一个检查高度的函数

int _Height(Node* root){if (root == nullptr){return 0;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}int Height(){return _Height(_root);}

在写一个Isbalance函数检查函数的左右高度是否符合要求

bool _IsBalance(Node* root){if (root == nullptr)return true;int balance = _Height(root->_right) - _Height(root->_left);if (abs(balance) <= 2){cout << "平衡" << endl;return true;}else{cout << "不平衡" << endl;return false;}if (balance != root->_bf){cout << " 平衡因子异常 ";return false;}return _IsBalance(root->_left) && _IsBalance(root->_right);}

但是这个函数使用的递归太多了,复杂度太高了.每次在计算高度差的时候,已经将高度给计算出来了,但是函数的最后还是要计算左右子树的高度.

balance的优化,使用一个引用来记录height的左右高度.

bool _IsBalance(Node* root,int& Height){if (root == nullptr){height = 0;return true;            }int balance = _Height(root->_right) - _Height(root->_left);int leftHeight = 0,rightHeight = 0;// 左右子树只要有一个不是平衡树,就返回falseif (!_IsBalance(root->_left, leftHeight) || !_IsBalance(root->_right, rightHeight)){return false;}if (abs(rightHeight - leftHeight) >= 2){cout <<root->_kv.first<<"不平衡" << endl;return false;}if (balance != root->_bf){cout << " 平衡因子异常 ";return false;}height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;return true;}

结束

关于AVL树的讲解就到这里啦,如有不足,请在评论区指正,下期见!

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

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

相关文章

每日OJ题_两个数组dp⑥_力扣97. 交错字符串

目录 力扣97. 交错字符串 解析代码 力扣97. 交错字符串 97. 交错字符串 难度 中等 给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子…

C++ 学习笔记

文章目录 【 字符串相关 】C 输入输出流strcpy_s() 字符串复制输出乱码 【 STL 】各个 STL 支持的常见方法 ? : 运算符switch case 运算符 switch(expression) {case constant-expression :statement(s);break; // 可选的case constant-expression :statement(s);break; //…

HarmonyOS实战开发-如何实现电话服务中SIM卡相关功能

介绍 本示例使用sim相关接口&#xff0c;展示了电话服务中SIM卡相关功能&#xff0c;包含SIM卡的服务提供商、ISO国家码、归属PLMN号信息&#xff0c;以及默认语音卡功能。 效果预览 使用说明&#xff1a; 1.若SIM卡槽1插入SIM卡则SIM卡1区域显示为蓝色&#xff0c;否则默认…

创建个人百度百科需要什么条件?

互联网时代&#xff0c;创建百度百科词条可以给个人带来更多的曝光和展现&#xff0c;相当于一个镀金的网络名片&#xff0c;人人都想上百度百科&#xff0c;但并不是人人都能创建上去的。 个人百度百科词条的创建需要满足一定的条件&#xff0c;今天伯乐网络传媒就来给大家聊聊…

P1123 取数游戏(dfs算法)

题目描述 一个 NM 的由非负整数构成的数字矩阵&#xff0c;你需要在其中取出若干个数字&#xff0c;使得取出的任意两个数字不相邻&#xff08;若一个数字在另外一个数字相邻 8个格子中的一个即认为这两个数字相邻&#xff09;&#xff0c;求取出数字和最大是多少。 输入格式 第…

关联式容器——map和set详解

troop主页 今日鸡汤&#xff1a;Never bend your head.Always hold it high.Look the world straight in the face. 加油&#xff01;成为最好的编程大师 前言 我们前几篇文章讲了搜索二叉树&#xff0c;我们提到了搜索二叉树的应用就是K结构和KV结构&#xff0c;今天我们要提…

Vue3中的computed,watch和watchEffect的特点

1.computed 1&#xff09;computed拥有缓存性&#xff0c;多次调用会直接从缓存中获取&#xff0c;而不会重新执行&#xff0c;只有相依赖的数据发生改变才会重新计算&#xff0c;所以说computed性能很高。 例&#xff1a;下面是同时调用三次计算属性firstTotal和三次函数first…

扬帆出海扩规模,仍是比亚迪未来的发展关键?

又到了新能源车企公布阶段性成果的时期。 日前&#xff0c;乘联会预估2024年3月全国新能源乘用车厂商批发销量82万辆&#xff0c;同比增长33%&#xff0c;环比增长84%。其中&#xff0c;比亚迪继续领跑&#xff0c;3月销量超30万辆&#xff0c;环比增长147.8%&#xff0c;而这…

简单用Nodejs + express 编写接口

文章目录 get接口示范post接口示范注意点 准备工作可以看上一篇文章&#xff1a;文章链接》》 get接口示范 app.get(/, (req, res) > {res.send("Hello World"); })因为是get接口&#xff0c;所以可以直接在浏览器上请求&#xff08;端口地址接口名&#xff09;…

测开面经(Git经典题目,Git入门)

1. GitHub是什么 a. Git是一个分布式版本控制系统&#xff0c;作用是跟踪、管理和协调软件开发项目中的代码更改。 b. 提供了一种有效的方式来管理代码的版本历史&#xff0c;以及多人协作开发的能力。 2. Git的作用有哪些 a. 版本控制&#xff1a;Git可以记录每次代码更改的…

基于51单片机轮胎胎压监测系统—数码管显示

基于51单片机轮胎胎压监测系统 &#xff08;仿真&#xff0b;程序&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.MPX4115压力传感器胎压检测&#xff1b; 2.ADC0832进行模数转换后&#xff0c;51单片机处理控制&#xff1b; 3.数码管显示胎压&#xff…

想要品牌传播有效,先清楚这三个本质问题

在互联网时代&#xff0c;企业想要提高市场竞争力就需要做好品牌传播。然而有许多企业在做品牌传播时都会踩坑&#xff0c;原因是因为忽视了这三点&#xff0c;接下来就让媒介盒子和大家分享&#xff1a; 一、 文案本质是“购买理由” 在文案技巧中经常会出现一些词&#xff…

uniapp 表单使用Uview校验 包括城市选择器

<view><!-- 注意&#xff0c;如果需要兼容微信小程序&#xff0c;最好通过setRules方法设置rules规则 --><u--form labelPosition"left" :model"model1" :rules"rules" ref"uForm" labelWidth"174"><u…

Python中csr_matrix的两种初始化方法

本文以csr_matrix为例来说明sparse矩阵的使用方法&#xff0c;其他类型的sparse矩阵可以参考https://docs.scipy.org/doc/scipy/reference/sparse.html csr_matrix是Compressed Sparse Row matrix的缩写组合&#xff0c;下面介绍其两种初始化方法 csr_matrix((data, (row_ind…

【操作系统】-寄存器-具有记忆功能的元器件

为什么要使用寄存器 现代电子计算机用二进制来表示数字&#xff0c;人类发明了触发器&#xff0c;每个触发器可以保存1比特&#xff0c;为了保存一个较大的二进制数&#xff0c;组合一起就是新的元器件&#xff0c;称为寄存器&#xff08;register&#xff09;&#xff0c;或者…

MySQL-7.mysql约束

约束用于确保数据库中的数据满足特定的商业规则。 MySQL约束包含五种&#xff1a;not null、unique、primary key、foreign key、check 7.1 primary key 主键 字段名 字段类型 primary key 用于唯一的标识表的行数据&#xff0c;当定义主键约束后&#xff0c;该列不能重复。 pr…

第十课 Excel

最上方标题栏&#xff1a; 显示共工作薄名称&#xff0c;如果显示兼容模式是没有办法使用高级功能的。分辨高版本和低版本可以通过后缀名进行分辨&#xff1b;显示xlsx就是高版本工作薄&#xff0c;如果显示xls的话就是低版本工作薄了。如果同事老板都使用的是低版本的话我们发…

VM-UNet: Vision Mamba UNet for Medical Image Segmentation

VM-UNet: Vision Mamba UNet for Medical Image Segmentation VM-UNet&#xff1a;基于视觉Mamba UNet架构的医学图像分割 论文链接&#xff1a;http://arxiv.org/abs/2402.02491 代码链接&#xff1a;https://github.com/JCruan519/VM-UNet 1、摘要 文中利用状态空间模型SS…

【攻防世界】Confusion1

php的标志是大象&#xff0c;Python的标志是蛇 。Python 的 Flask 框架( Flask 使用 Jinja2 作为模板引擎 ) 点进register.php 输入{{3*4}} 输入 {{config}} 也有回显&#xff0c;ssti 判断是否存在ssti注入&#xff1a; 1. {{8*8}} 2. {{config}} 过滤了关键字&#xff0…

人工智能前沿成科技竞争新高地

以下文章来源&#xff1a;经济参考报 近日&#xff0c;首届中国具身智能大会&#xff08;CEAI 2024&#xff09;在上海举行。作为人工智能领域的前沿热点&#xff0c;具身智能正逐步走进现实&#xff0c;成为当前全球科技竞争的新高地、未来产业的新赛道、经济发展的新引擎。 “…