C++_进阶:AVL树

文章目录

    • 1. AVL树的概念
    • 2. AVL树节点的定义
    • 3. AVL树的插入
    • 4. AVL树的旋转
      • 4.1 右单旋
      • 4.2 左单旋
      • 4.3 左右双旋
      • 4.4 右左双旋
    • 5.AVL树的验证
    • 6. AVL树模拟实现

在这里插入图片描述

1. AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下

🔅因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:📋当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,这样的树就是AVL树

📋一棵AVL树是具有以下性质的二叉搜索树

  • 它的左右子树都是AVL树
  • 左右子树高度之差 (简称平衡因子) 的绝对值不超过1(-1/0/1)

在这里插入图片描述

🔅如果一棵二叉搜索树是高度平衡(满足以上两个条件)的,它就是AVL树。如果它有n个结点,其高度可保持在 O(logn) , 搜索时间复杂度O(logn)

2. AVL树节点的定义

AVL的节点在原来二叉搜索树的节点上,增加了两个成员:父节点指针变量,以及平衡因子变量

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

3. AVL树的插入

🔅AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树
那么AVL树的插入过程可以分为两步:

  • 按照二叉搜索树的方式插入新节点
  • 调整节点的平衡因子

🔅更新平衡因子规则:
1.📝 插入的是左子树的节点,平衡因子- -,插入的是右子树的节点,平衡因子++
在这里插入图片描述
2. 📝调节后的平衡因子为1/-1,0,2/-2 往下需要做的是不同的。调节后的平衡因子为0时,停止向上更新平衡因子

在这里插入图片描述
调节后的平衡因子为1/-1时,继续向上调整平衡因子

在这里插入图片描述
调节后的平衡因子为2/-2时,就要调整树了,并且不再向上调整平衡因子
在这里插入图片描述
在这里插入图片描述
代码示例:

//接收一个 键值对pair类型 作为插入的值
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--;elseparent->_bf++;// 更新后检测双亲的平衡因子//如果平衡因子为0,停止向上更新平衡因子if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){//调节后的平衡因子为1/-1时,继续向上调整平衡因子// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树// 的高度增加了一层,因此需要继续向上调整cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 调节后的平衡因子为2/-2时,就要调整树了,并且不再向上调整平衡因子// 双亲的平衡因子为2/-2,违反了AVL树的平衡性,需要对以pParent// 为根的树进行旋转处理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 {RotateLR(parent);}break;}else{assert(false);}}return true;
}

4. AVL树的旋转

🔅AVL树之所以能保持平衡,原因在于AVL树会 旋转,上面Insert代码中如果平衡因子== 2/-2时,RotateL,RotateR,RotateRL,RotateLR就是调整该树的函数。

🔅如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化根据节点插入位置的不同,AVL树的旋转分为四种

4.1 右单旋

1️⃣ 📝新节点插入较高左子树的左侧—左左:右单旋在这里插入图片描述
🔅最终根据情况重新设定平衡因子:cur的平衡因子变为0,parent的平衡因子变为0。

void  RotateR(Node* parent)
{// subL: parent的左孩子// subLR: Parent左孩子的右孩子Node* subL = parent->_left;Node* subLR = subL->_right;// 旋转完成之后,10的右孩子作为父亲的左孩子parent->_left = subLR;// 如果10的左孩子的右孩子存在,更新亲双亲if (subLR)subLR->_parent = parent;// 因为30可能是棵子树,因此在更新其双亲前必须先保存30的双亲Node* parentParent = parent->_parent;// 30作为 10的右孩子subL->_right = parent;// 更新30的双亲parent->_parent = subL;// 如果30是根节点,根新指向根节点的指针if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{	// 如果30是子树,可能是其双亲的左子树,也可能是右子树if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}// 根据调整后的结构更新部分节点的平衡因子parent->_bf = subL->_bf = 0;
}

4.2 左单旋

2️⃣ 📝新节点插入较高右子树的右侧—右右:左单旋
在这里插入图片描述
🔅最终根据情况重新设定平衡因子:cur的平衡因子变为0,parent的平衡因子变为0。

//与右旋逻辑大体一致
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;
}

4.3 左右双旋

3.📝 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
在这里插入图片描述

在这里插入图片描述
🔅但是实际情况中,插入的节点在son节点的左边还是右边我们是不知道的,也就是说,插入后的b树与c树的高度其实不确定的,只能确定不是h就是h-1,这会影响最终平衡因子的分布。根据bc树高度的不同可以分出三种情况:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

//左右双旋逻辑:
void RotateLR(Node* parent)
{//parent的左节点 -> curNode* subL = parent->_left;//parent的左节点的右节点 -> sonNode* subLR = subL->_right;// 记下 son 的平衡因子// 用son的平衡因子判断bc树不同的高度情况int bf = subLR->_bf;//复用左旋逻辑 , 对cur左旋RotateL(parent->_left);//复用右旋逻辑 , 对parent右旋RotateR(parent);//当b = h,c = hif (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}//当b = h-1,c = helse if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf =0;}//b = h,c = h-1else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else{assert(false);}
}

4.4 右左双旋

4️⃣新节点插入较高右子树的左侧—右左:先右单旋再左单旋
在这里插入图片描述

在这里插入图片描述

//右左双旋逻辑:与左右双旋逻辑大体一致
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf =0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}

5.AVL树的验证

🔅AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步

  1. 验证其为二叉搜索树。如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。
  2. 验证其为平衡树每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)节点的平衡因子是否计算正确。
bool _IsAVLTree(Node* pRoot)
{if (pRoot == nullptr){return true;}if (_Height(pRoot->_right) - _Height(pRoot->_left) != pRoot->_bf || abs(_Height(pRoot->_right) - _Height(pRoot->_left)>1)){cout << pRoot->_kv.first << ":" << pRoot->_kv.second << " bf=" << pRoot->_bf << endl;cout << "_Height(pRoot->_right) - _Height(pRoot->_left)=" << _Height(pRoot->_right) - _Height(pRoot->_left);return false;}return _IsAVLTree(pRoot->_left) && _IsAVLTree(pRoot->_right);
}//计算树高度函数
int _Height(Node* pRoot)
{if (pRoot == nullptr){return 0;}size_t leftH = _Height(pRoot->_left);size_t rightH = _Height(pRoot->_right);return leftH > rightH ? leftH + 1 : rightH + 1;
}

6. AVL树模拟实现

#pragma once
#include<iostream>
#include <assert.h>
using namespace std;//AVL采用键值对数结构
template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://默认生成默认构造函数AVLTree() = default;//拷贝构造AVLTree(const AVLTree<K, V>& t){_root = Copy(t._root);}//赋值重载AVLTree<K, V>& operator=(AVLTree<K, V> t){swap(_root, t._root);return *this;}//析构函数~AVLTree(){Destroy(_root);_root = nullptr;}//插入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--;elseparent->_bf++;if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){// 继续往上更新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){RotateRL(parent);}else {RotateLR(parent);}break;}else{assert(false);}}return true;}//查找函数Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}//打印树,中序void InOrder(){inOrder(_root);cout << endl;}//验证函数bool IsAVLTree(){return _IsAVLTree(_root);}private:bool _IsAVLTree(Node* pRoot){if (pRoot == nullptr){return true;}if (_Height(pRoot->_right) - _Height(pRoot->_left) != pRoot->_bf || abs(_Height(pRoot->_right) - _Height(pRoot->_left)>1)){cout << pRoot->_kv.first << ":" << pRoot->_kv.second << " bf=" << pRoot->_bf << endl;cout << "_Height(pRoot->_right) - _Height(pRoot->_left)=" << _Height(pRoot->_right) - _Height(pRoot->_left);return false;}return _IsAVLTree(pRoot->_left) && _IsAVLTree(pRoot->_right);}int _Height(Node* pRoot){if (pRoot == nullptr){return 0;}size_t leftH = _Height(pRoot->_left);size_t rightH = _Height(pRoot->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}void inOrder(Node* root){if (root == nullptr){return;}inOrder(root->_left);cout << root->_kv.first<<":"<<root->_kv.second<<" ";inOrder(root->_right);}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}//右旋void  RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}//右左旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf =0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//左右旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf =0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else{assert(false);}}//摧毁树函数void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}//拷贝函数Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_value);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}private:Node* _root = nullptr;
};

本文就到这里,感谢你看到这里❤️❤️! 我知道一些人看文章喜欢静静看,不评论🤔,但是他会点赞😍,这样的人,帅气低调有内涵😎,美丽大方很优雅😊,明人不说暗话,要你手上的一个点赞😘!

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

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

相关文章

git 学习--GitHub Gitee码云 GitLab

1 集中式和分布式的区别 1.1 集中式 集中式VCS必须有一台电脑作为服务器&#xff0c;每台电脑都把代码提交到服务器上&#xff0c;再从服务器下载代码。如果网络出现问题或服务器宕机&#xff0c;系统就不能使用了。 1.2 分布式 分布式VCS没有中央服务器&#xff0c;每台电脑…

将 hugo 博客搬迁到服务器

1. 说明 在 Ubuntu 22.04 上使用 root 账号&#xff0c;创建普通账号&#xff0c;并赋予 root 权限。 演示站点&#xff1a;https://woniu336.github.io/ 魔改hugo主题: https://github.com/woniu336/hugo-magic 2. 服务器配置 建立 git 用户 adduser git安装 git sudo apt …

python | 图片转换为 pdf 实现方法

目录 一、PIL 库简介及安装使用方法 &#xff08;一&#xff09;python 不同版本下 PIL 的使用方法 二、图片转换为 pdf 的两种实现方法 &#xff08;一&#xff09;简易版——pdf 页面尺寸跟随图片大小 &#xff08;二&#xff09;常用版——pdf 每页尺寸统一为 A4 一、P…

shellcode汇编复习

shellcode汇编复习 一、 汇编代码复习1.1 基础寄存器1. EAX (Accumulator Register)2. EBX (Base Register)3. ECX (Count Register)4. EDX (Data Register)5. ESI (Source Index Register)6. EDI (Destination Index Register) 二、 基础指令1. mov - 数据传送2. add - 加法3.…

Ansys Zemax|如何有效地模拟散射

附件下载 联系工作人员获取附件 概要 OpticStudio中&#xff0c;有两个用来提升散射模拟效率的工具&#xff1a;Scatter To List以及Importance Sampling。在这篇文章中&#xff0c;我们详细讨论了这两个工具&#xff0c;并且以一个杂散光分析为例示范了如何使用Importance S…

简单的jar包重打包Failed to get nested archive for entry 报错处理

简单的jar包重打包Failed to get nested archive for entry 报错处理 1. 需求 公司有一个后端项目&#xff0c;项目已经打好了jar包&#xff0c;现在我们发现jar包依赖的子包有问题&#xff0c;其中的一个mybatis xml文件查询数据不正确&#xff0c;我们需要替换项目&#xf…

批量将labelme的json文件转为png图片查看

文章目录 前提修改 l a b e l m e labelme labelme然后你就可以在这个环境下用代码批量修改了 前提 安装anaconda或者miniconda安装labelme 修改 l a b e l m e labelme labelme 查看labelme所处环境的路径&#xff1a;conda info --envs 比如我的是在py39_torch里面 修改la…

秋招力扣Hot100刷题总结——链表

1. 反转链表题目连接 题目要求&#xff1a;给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 代码及思路 遍历所有节点&#xff0c;将所有节点的next指向前一个节点由于要改变节点的next指向&#xff0c;而链表是单向的&#xff0c;因此需要…

【Mac】植物大战僵尸杂交版 for Mac(经典策略塔防游戏)游戏介绍

游戏介绍 植物大战僵尸杂交版 for Mac是一款非常受欢迎的策略塔防游戏&#xff0c;植物大战僵尸游戏以其独特的主题、幽默的风格和富有挑战性的关卡设计而著称。玩家需要种植各种植物来防御入侵的僵尸&#xff0c;每种植物都有其特定的功能和攻击方式。植物大战僵尸杂交版&…

Android 上下滑隐藏显示状态栏

一、DisplayPolicy类中监听滑动事件&#xff0c;然后发送广播事件 Android12类路径&#xff1a; frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.javamSystemGestures new SystemGesturesPointerEventListener(mUiContext, mHandler,new SystemGest…

SQL注入(head、报错、盲注)

目录 【学习目标、重难点知识】 【学习目标】 【重难点知识】 1. 报错注入 1.1 那么什么是报错注入呢&#xff1f; 1.2 报错注入原理 extractvalue函数 updatexml函数 1.3 靶场解析 靶场练习 2. HEAD注入 2.1 相关全局变量 2.2 靶场解析 burp暴力破解 靶场练习 3…

java常见面试题汇总

&#x1f30f;个人博客主页&#xff1a;意疏-CSDN博客 希望文章能够给到初学的你一些启发&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏支持一下笔者吧&#xff5e; 阅读指南&#xff1a; 开篇说明一、封装 继承 多态1.封装2.继承3.多态 二、什么是重载…

初始化列表 / 隐式转换 / 静态

目录 初始化列表隐式转换单参数的隐式类型转换多参数的隐式类型转换explicit关键字 static 初始化列表 大部分时候成员变量在对象实例化的时候调用构造函数就整体定义了&#xff0c;注意此时只有定义&#xff0c;不算初始化。而定义后的值的值是在构造函数里面给的。我们知道构…

<数据集>无人机航拍不同高度牧羊识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6065张 标注数量(xml文件个数)&#xff1a;6065 标注数量(txt文件个数)&#xff1a;6065 标注类别数&#xff1a;1 标注类别名称&#xff1a;[sheep] 序号类别名称图片数框数1sheep6065149785 使用标注工具&…

Macos M1 IDEA本地调试 HBase 2.2.2

# 1. 前提 执行 mvn clean package assembly:single -DskipTests没问题&#xff0c;并在hbase-assembly/target目录下生成hbase-2.2.2-bin.tar.gz 文件夹 证明Maven 下载依赖没问题 1.1 报错 1 这里应该是报错找不到 com.google.protobuf:protoc:exe:osx-aarch_64:3.5.1 …

入门STM32—外部中断

外部中断的存在使得微控制器能够及时响应外部事件&#xff0c;避免频繁的轮询操作&#xff0c;从而提高系统的实时性、效率和低功耗性能。 1.什么是外部中断&#xff1f; 外部中断是指微控制器接收到外部引脚的信号变化时触发的中断。STM32F103系列微控制器支持多个外部中断线…

鸿蒙(API 12 Beta3版)【DRM会话管理(C/C++)】数字版权保护开发

DRM会话管理&#xff08;MediaKeySession&#xff09;支持媒体密钥管理及媒体解密等&#xff0c;MediaKeySession实例由系统管理里的MediaKeySystem实例创建和销毁。 开发步骤 导入NDK接口&#xff0c;接口中提供了DRM相关的属性和方法&#xff0c;导入方法如下。 #include &…

学习嵌入式第二十九天

ipc进程间通信方式 PC&#xff0c;即进程间通信&#xff08;Inter-Process Communication&#xff09;&#xff0c;是操作系统中不同进程之间交换数据的一种机制。以下是一些常见的IPC方式&#xff1a; 管道&#xff1a;用于父子进程或兄弟进程之间的通信。消息队列&#xff…

selenium-java实现自动登录跳转页面

如果要一直刷新一个网页&#xff0c;总不能人工一直去点&#xff0c;所以想到大学时候学过selenium技术&#xff0c;写个脚本来一直刷新&#xff0c;因为经常写java语言&#xff0c;所以选用java语言来写 实验环境 注意&#xff0c;需要先准备好Google浏览器和Chrome-Driver驱…

ffmpeg6.1集成Plus-OpenGL-Patch滤镜

可参考上一篇文章。ffmpeg6.1集成ffmpeg-gl-transition滤镜-CSDN博客 安装思路大致相同&#xff0c; 因为 Plus-OpenGL-Patch也是基于 ffmpeg 4.x 进行开发的&#xff0c;所以在高版本上安装会有很多报错。 这是我安装后的示例&#xff0c;需要安装教程或者改代码可私信我。 …