平衡二叉搜索树(AVL)——【C++实现插入、删除等操作】

在这里插入图片描述

本章完整代码gitee地址:平衡二叉搜索树

文章目录

  • 🌳0. 前言
  • 🌲1. AVL树概念
  • 🌴2. 实现AVL树
    • 🌿2.1 结构定义
    • 🌿2.2 插入
      • 💐左单旋
      • 💐右单旋
      • 💐左右双旋
      • 💐右左双旋
    • 🌿2.3 查找
    • 🌿2.4 删除
    • 🌿2.5 树的高度
    • 🌿2.6 是否为平衡树
    • 🌿2.7 遍历(中序)

🌳0. 前言

C++的mapset这两个容器的底层就搜索树,关于搜索树,之前此篇文章讲过:数据结构——二叉搜索树,但它可能会出现极端情况:退化为链表

image-20230906133632229

所以再次基础上,就要将这颗树变为平衡的二叉搜索树——ALV树红黑树,本章讲解的是AVL树

🌲1. AVL树概念

AVL树俄罗斯的两位数学家G.M.Adlson-VelskiiE.M.Landis在1962年发表的论文《An algorithm for the organization of information》公开了这种结构:向二叉树插入一个新节点后,保证每个左右子树的高度之差绝对值不超过1,即可降低树的高度,减少平均搜索的长度

将左子树减去右子树的深度的值,成为平衡因子BF(Balance Factor),由于绝对值不超过1,则平衡因子的范围是**[-1,1]**,如果超过,就说明目前这棵树不是平衡的,需要进行调整

image-20230906135941793

由于树的左右子树是平衡的,所以要对这棵树操作的时候,最多进行高度次操作:O(lonN)

满二叉树:2h-1 = N

AVL树:2h - X = N(X的范围为[1,2h-1-1]),属于O(lonN),这个量级

🌴2. 实现AVL树

🌿2.1 结构定义

//定义成kv结构
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;	//平衡因子AVLTreeNode(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://功能
private}

🌿2.2 插入

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->_left = cur;}else{parent->_right = 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 = parent;parent = parent->_parent;cur = cur->_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{//防止写的代码有bugassert(false);}}return true;
}

我们设平衡因子为:右子树-左子树

  1. 新增左节点,parent平衡因子减一

  2. 新增右节点,parent平衡因子加一

  3. 更新后的parent平衡因子 == 0 ,说明parent所在子树高度不变,无需继续更新祖先节点

    更新后的parent平衡因子 == 1 或- 1,说明parent所在子树高度发生变化,影响祖先,需要继续更新祖先节点

  4. 更新后的parent平衡因子 == 2 或 -2,说明parent所在子树高度发生变化,且不平衡,需要对parent对所在子树进行旋转,让其平衡

这里的平衡因子起到一个检测作用,查看这棵树是否“生病”,如果发现“生病”,立即“治疗”,所以不可能出现大于2的绝对值的平衡因子

💐左单旋

单纯右边高,采用左单旋进行调整,具体情况如图:

image-20230907101404348

核心操作:

  1. `parent->right = cur->left;``
  2. ``cur->left = parent;`

左单旋实现:

//左单旋
void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;
}

动态示例:

left

💐右单旋

单纯的左边高,采用右单旋进行调整,具体情况如图:

image-20230907105426442

核心操作:

  1. parent->left = cur->right;
  2. cur->right = parent;

右单旋实现:

//右单旋
void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curRight = cur->_right;parent->_left = cur->_right;//防止curRight为空if (curRight){curRight->_parent = parent;}cur->_right = parent;//保存父亲的父亲节点Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root)	{_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->parent = ppnode;}//更新平衡因子parent->_bf = cur->_bf = 0;
}

动图示例:

right

💐左右双旋

新插入节点在较高左子树的右侧,采用右左双旋,具体情况如图:

image-20230907191809620

平衡因子更新需要看cur->right的平衡因子情况:

image-20230907193212207

  1. curRight == 0,它就是插入节点,全部更新为0
  2. curRight == 1c插入,cur->_bf = -1parent->_bf = 0
  3. curRight == -1b插入,cur->_bf = 0,·parent->_bf = 1`

核心操作:

  1. parent->left为旋转点左旋
  2. parent为旋转点右旋
  3. 根据curRight->_bf调整平衡因子

💐右左双旋

新插入节点在较高右子树的左侧,采用右左双旋,具体情况如图:

image-20230907171534105

这里平衡因子的更新,不能和单旋一样直接更新为0,要分情况,我们这里主要是看cur->left的平衡因子

image-20230907190633396

  1. curLeft->_bf == 0,则它就是插入节点,平衡因子全部更新为0
  2. curLeft->_bf == 1c插入,cur->_bf = 0parent->_bf = -1
  3. curLeft->_bf == -1,b插入,cur->_bf = 1parent->_bf = 0

核心操作:

  1. 以为parent->right为旋转点右旋
  2. parent为旋转点左旋
  3. 根据curLeft->_bf更新平衡因子

右左双旋代码实现:

	//右左双旋void RotateRL(Node* parent){Node* cur = parent->_right;Node* curLeft = cur->_left;int bf = curLeft->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){//curLeft为新增节点cur->_bf = 0;curLeft->_bf = 0;parent->_bf = 0;}else if (bf == 1){//curLeft右子树插入cur->_bf = 0;curLeft->_bf = 0;parent->_bf = -1;}else if (bf == -1){//curLeft左子树插入cur->_bf = 1;curLeft->_bf = 0;parent->_bf = 0;}else{assert(false);}}

动图示例:

r_lRtate

🌿2.3 查找

//查找
Node* Find(const K& key)
{return _Find(_root, key);
}

因为_root是私有成员,外部无法使用,所以我们设置一个子函数

Node* _Find(Node* root, const K& key)
{if (root == nullptr || root->_kv.first == key)return root;if (root->_kv.first > key)return _Find(root->_left, key);elsereturn _Find(root->_right, key);
}

🌿2.4 删除

删除操作就不多说了,注释写的特别清楚

基本思路就是:

  1. 删除元素
  2. 更新平衡因子

与插入类似,但细节还是很多

//删除
bool Erase(const K& key)
{return _Erase(key);
}

子函数:

bool _Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;//查找元素while (cur){if (cur->_kv.first > key){parent = cur;cur = cur->_left;}else if (cur->_kv.first < key){parent = cur;cur = cur->_right;}elsebreak;}//该元素不存在if (cur == nullptr)return false;//删除元素//1.左右子树都为空if (cur->_left == nullptr && cur->_right == nullptr){//根节点直接删除退出if (cur == _root){delete _root;_root = nullptr;return true;}if (cur == parent->_left){//删除的是左孩子parent->_bf++;parent->_left = nullptr;}else{//删除的是右孩子parent->_bf--;parent->_right = nullptr;}delete cur;cur = parent;}else if (cur->_left == nullptr)	//左子树为空{if (cur == _root){_root = cur->_right;cur->_right->_parent = nullptr;delete cur;cur = nullptr;return true;}//因为是平衡二叉树,如果左子树为空,那么右子树至多只有一个节点//这里前面已经判断了双空的情况,那么肯定右子树只有一个节点,直接删除即可Node* curRight = cur->_right;//替换元素cur->_kv.first = curRight->_kv.first;cur->_kv.second = curRight->_kv.second;//删除节点cur->_right = nullptr;delete curRight;curRight = nullptr;//删右孩子,父节点平衡因子-1cur->_bf--;}else if (cur->_right == nullptr)	//右子树为空{if (cur == _root){_root = cur->_left;cur->_left->_parent = nullptr;delete cur;cur = nullptr;return true;}//与左子树为空同理Node* curLeft = cur->_left;//替换元素cur->_kv.first = curLeft->_kv.first;cur->_kv.second = curLeft->_kv.second;//删除节点cur->_left = nullptr;delete curLeft;curLeft = nullptr;//删除左孩子,父节点平衡因子+1cur->_bf++;}else{//左右子树都不为空//以左子树最大元素为例parent = cur;	//前驱Node* prev = cur->_left;	//找左子树最大元素while (prev->_right){parent = prev;prev = prev->_right;}//替换元素cur->_kv.first = prev->_kv.first;cur->_kv.second = prev->_kv.second;//右边肯定没有元素了,因为找的就是最大的元素:左子树里面的最右边if (parent->_left == prev){parent->_left = prev->_left;	parent->_bf++;}else{parent->_right = prev->_left;parent->_bf--;}delete prev;prev = nullptr;//指向删除节点父亲cur = parent;}parent = cur->_parent;//重新找调整位置if (cur->_bf == -2 || cur->_bf == 2){if (cur->_bf == -2){Node* curLeft = cur->_left;parent = cur;cur = curLeft;}else{Node* curRight = cur->_right;parent = cur;cur = curRight;}}//if (cur->_bf == 0 && parent != nullptr && abs(parent->_bf) == 2)//{//	if (cur = parent->_left)//		cur = parent->_right;//	else//		cur = parent->_left;//}while (parent){//更新父亲的平衡因子parent->_bf = UpdateBf(parent);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 if (parent->_bf == 2){//cur->_bf == 0时RotateL(parent);//再更新一次parent->_bf = UpdateBf(parent);}else if (parent->_bf == -2){RotateR(parent);parent->_bf = UpdateBf(parent);}else{assert(false);}}cur = parent;parent = cur->_parent;}return true;
}
int UpdateBf(Node* root)
{if (!root)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return rightH - leftH;
}

🌿2.5 树的高度

int _Height(Node* root)
{if (root == nullptr)return 0;//记录深度int leftH = _Height(root->_left);int rightH = _Height(root->_right);//记录更深的那一个return std::max(leftH, rightH) + 1;
}

🌿2.6 是否为平衡树

bool _IsAVLTree(Node* root)
{//空树符合AVL树if (root == nullptr)return true;//左子树的高度int leftH = _Height(root->_left);//右子树高度int rightH = _Height(root->_right);//看平衡因子是否符合int bf = rightH - leftH;if (bf != root->_bf)return false;//平衡因子绝对值不大于//在一次递归左右子树return abs(bf) < 2 && _IsAVLTree(root->_left) && _IsAVLTree(root->_right);	
}

🌿2.7 遍历(中序)

void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);
}

那本次分享就到这里咯,AVL树主要是重点了解其中是如何变平衡的(各种旋转),在实际中,我们只有使用C++里面的mapset(底层是红黑树)。

我们下期再见咯,如果还有下期的话。

Tips:
如果代码有bug,希望大家能指出来,看了网上好多的都有bug
不知道这个有没有bug,我测了一些数据,目前没发现bug

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

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

相关文章

MySql系列-常用命令

基础知识-常用命令 命令不区分大小写 1、mysql连接 mysql -u username -p 实例: mysql -u root -p 2、元数据查询 //服务器版本信息 SELECT VERSION( ) //当前数据库名 (或者返回空) SELECT DATABASE( ) //当前用户名 SELECT USER( ) //服务器状态 SHOW STATUS //服务…

C++项目实战——基于多设计模式下的同步异步日志系统-①-项目介绍

文章目录 专栏导读项目介绍开发环境核心技术环境搭建日志系统介绍1.为什么需要日志系统2.日志系统技术实现2.1同步写日志2.2异步写日志 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领域新星创作者&#xff0c;新星计划导师&a…

SpringMVC的文件上传文件下载多文件上传---详细介绍

目录 前言&#xff1a; 一&#xff0c;文件上传 1.1 添加依赖 1.2 配置文件上传解析器 1.3 表单设置 1.4 文件上传的实现 二&#xff0c;文件下载 controller层 前端jsp 三&#xff0c;多文件上传 Controller层 运行 前言&#xff1a; Spring MVC 是一个基于 Java …

ES-索引管理

前言 数据类型 ​ 搜索引擎是对数据的检索&#xff0c;所以我们先从生活中的数据说起。我们生活中的数据总体分为两种&#xff1a; 结构化数据非结构化数据 结构化数据&#xff1a; 也称作行数据&#xff0c;是由二维表结构来逻辑表达和实现的数据&#xff0c;严格地遵循数…

idea中mapper直接跳转到xml的插件

一.点击File | Settings | Plugins&#xff0c;下载插件 二、重启idea

分割等和子集【动态规划】

分割等和子集 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 class Solution {//testpublic boolean canPartition(int[] nums) {if(nums null || nums.length 0) return false;int n nums…

如何曲线斩获大厂校招SP Offer

大家好&#xff0c;我是洋子&#xff0c;时至9月&#xff0c;24届校招正式批笔试环节很多公司都开始了&#xff0c;近期也有同学逐渐开始正式批面试&#xff0c;今天分享一篇22届学长的上岸大厂测开经验指南&#xff0c;干货满满 原文链接 https://zhuanlan.zhihu.com/p/479687…

virtualbox虚拟机中安装FreeDOS系统和DJGPP编译环境

一、安装FreeDOS系统 1、从官网下载FreeDOS系统镜像&#xff0c;下载的压缩包中包含两个文件&#xff1a;后缀为.iso和.img的镜像 ​​​下载页面 http://www.freedos.org/download/ 直接下载链接 https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.…

TOWE雷达光敏感应开关,让生活更智能、更安全

现代生活中&#xff0c;智能家居成为人们追求品质生活的必备之选。其中&#xff0c;照明控制的智能化已然成为一种趋势&#xff0c;传统的灯光开关需要人们手动操作&#xff0c;既不方便&#xff0c;有时候也会造成资源的过度浪费&#xff0c;而雷达光敏感应开关的出现&#xf…

05 C/C++ 指针复杂类型说明 9月5日

目录 C语⾔ (1)数组 (2)指针 指针变量 空指针 (3)指针复杂类型 int a 0; int *p &a; int p[3];​​​​​​​ int *p[3]; int (*p)[3]; int **p; int p(int); int(*p)(int); C语⾔ (1)数组 当数据具有相同的数据类型&#xff1b;使用过程中需要保留原始…

纯前端读写文件?

事情是这样的我发现vscode在线版居然可以打开文件目录和文件&#xff0c;还能保存文件。 兼容性一般 目前 谷歌 edge Opera 支持 其他均不支持 https://vscode.dev/ 查了一下MDN 发现增加新的API 了 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/showDirectoryP…

2023-09-07 LeetCode每日一题(修车的最少时间)

2023-09-07每日一题 一、题目编号 2594. 修车的最少时间二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数数组 ranks &#xff0c;表示一些机械工的 能力值 。ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n2 分钟内修好 n 辆车。 同时给你…

Nginx 配置错误导致漏洞

文章目录 Nginx 配置错误导致漏洞1. 环境启动2. CRLF注入漏洞2.1 漏洞描述2.2 漏洞原理2.3 漏洞利用2.4 修复建议 3. 目录穿越漏洞3.1 漏洞描述3.2 漏洞原理3.3 漏洞利用3.4 修复建议 4. add_header被覆盖4.1 漏洞描述4.2 漏洞原理4.3 漏洞利用4.4 修复建议 Nginx 配置错误导致…

数据结构与算法:数据结构基础

目录 数组 定义 形式 顺序存储 基本操作 读取元素 更新元素 插入元素 删除元素 扩容 初始化 时机 步骤 优劣势 链表 定义 单向链表 特点 双向链表 随机存储 基本操作 查找节点 更新节点 插入节点 删除元素 数组VS链表 栈与队列 栈 定义 基本操作…

uniapp使用webview将页面转换成图片支持h5、app、小程序

uniapp使用webview将页面转换成图片支持h5、app、小程序 在uniapp项目中新建主页和webview页面 index.vue代码 <template><view><!-- 微信小程序要设置src为网络路径 --><web-view src"/hybrid/html/webview.html"></web-view><…

51单片机的智能台灯控制系统仿真( proteus仿真+程序+原理图+报告+讲解视频)

51单片机的红外光敏检测智能台灯控制系统仿真 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接 51单片机的红外光敏检测智能台灯控制系统仿真( proteus仿真程序原理图报告讲解视频&#xff09; 仿真图proteus7.8及以上 程…

SpringMVC应用

文章目录 一、常用注解二、参数传递2.1 基础类型String2.2 复杂类型2.3 RequestParam2.4.路径传参 PathVariable2.4 Json数据传参 RequestBody2.5 RequestHeader 三、方法返回值3.1 void3.2 Stringmodel3.3 ModelAndView 一、常用注解 SpringMVC是一个基于Java的Web框架&#…

深度学习在医疗保健领域的应用:从图像识别到疾病预测

文章目录 深度学习在医学影像识别中的应用1. 癌症检测2. 病理学图像分析3. 医学图像分割 深度学习在疾病预测中的应用1. 疾病风险预测2. 疾病诊断辅助3. 药物研发 深度学习在个性化治疗中的应用1. 基因组学分析2. 临床数据集成 深度学习在医疗保健中的挑战和未来数据隐私和安全…

【小吉送书—第二期】阿里后端开发:抽象建模经典案例

文章目录 0.引言1.抽象思维2.软件世界中的抽象2.1 命名抽象2.2 分层抽象2.3 原则抽象 3. 经典抽象案例3.1 方案一&#xff1a;战术抽象&#xff0c;多快好省&#xff0c;跑步前进3.2 方案二&#xff1a;深入分析&#xff0c;透过表象&#xff0c;探寻本质 5. 推荐一本书&#x…

给 Ubuntu 操作系统配置静态 IP

针对 Ubuntu 22.04.3 操作系统的静态 IP 配置 一、查看初始的网络信息 查看网卡名称 ifconfig查看网关信息 route -n二、编辑网络配置文件 编辑文件&#xff0c;配置文件的名称可能不一样&#xff0c;自己去 /etc/netplan/ 目录查看 sudo vim /etc/netplan/01-network-manager-…