【C++笔记】AVL树的深度剖析

【C++笔记】AVL树的深度剖析

、

🔥个人主页大白的编程日记

🔥专栏C++笔记


文章目录

  • 【C++笔记】AVL树的深度剖析
    • 前言
    • 一. AVL树的概念
    • 二.AVL树的实现
      • 2.1 AVL树的结构
      • 2.2 AVL树的插入
      • 2.3 平衡因子更新
    • 三.旋转
      • 3.1旋转的原则
      • 3.2右单旋
      • 3.3左单旋
      • 3.4左右双旋
      • 3.5右左双旋
      • 3.6AVL树的插入
      • 3.7AVL树的查找
    • 四.AVL树的检测
      • 4.1AVL树检测
      • 4.2 AVL树的验证
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了map和set的深度剖析。今天我们来讲一下AVL树的深度剖析。话不多说,我们进入正题!向大厂冲锋
在这里插入图片描述

一. AVL树的概念

  • 定义
    AVL树是最先发明的自平衡⼆叉查找树,AVL是一颗空树,或者具备下列性质的⼆叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是⼀颗高度平衡搜索⼆叉树,通过控制高度差去控制平衡。
    注意是所有的子树都满足高度差绝对值不超过1。
  • 发明者
    AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis是两个前苏联的科学家,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
  • 平衡因子
    AVL树实现这里我们引入⼀个平衡因子(balance factor)的概念,每个结点都有⼀个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡,就像⼀个风向标⼀样。

  • 高度差
    思考⼀下为什么AVL树是高度平衡搜索⼆叉树,要求⾼度差不超过1,而不是高度差是0呢?0不是更好的平衡吗?画画图分析我们发现,不是不想这样设计,而是有些情况是做不到高度差是0的。比如⼀棵树是2个结点,4个结点等情况下,高度差最好就是1,无法做到高度差是0
  • 对比二叉搜索树
    AVL树整体结点数量和分布和完全⼆叉树类似,高度可以控制在 ,那么增删查改的效率也可以控制在logn ,相比⼆叉搜索树有了本质的提升。

二.AVL树的实现

2.1 AVL树的结构

这里我们为了旋转时需要用到父亲节点,所以我们就是一个三叉链的结构。
同时我们引入了平衡因子,方便我们控制高度差。

template<class k, class v>
struct AVLNode
{using node = AVLNode<k, v>;k _key;v _value;node* left;//左节点node* right;//右节点node* parent;//父亲节点int bf;//平衡因子AVLNode(const k& key, const v& value):_key(key), _value(value), left(nullptr), right(nullptr),parent(nullptr),bf(0){}
};
template<class k, class v>
class AVLTree
{using node = AVLNode<k, v>;private:node* _root = nullptr;
};

2.2 AVL树的插入

二叉搜索树还是按照二叉搜索树的规则插入。
但是插入后要更新平衡因子,如果高度差超过1那么就旋转。

  • 插入
    插入一个值按二叉搜索树规则进行插入。
    插入的节点,左右为空所以平衡因子为0.

  • 更新平衡因子
    新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析。

  • 结束
    更新平衡因子过程中没有出现问题,则插入结束。

  • 旋转
    更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,本质降低了子树的高度,保持原来的高度,不会再影响上一层,所以插入结束。

2.3 平衡因子更新

更新原则:

  • 平衡因子 = 右子树高度-左子树高度

  • 只有子树高度变化才会影响当前结点平衡因子。

  • 插入结点,会增加高度,所以新增结点在parent的右子树,parent的平衡因子++,新增结点在parent的左子树,parent平衡因子–。

  • parent所在子树的高度是否变化决定了是否会继续往上更新
    如果当前子树高度没有变化,那当前子树往上的祖先节点的平衡因子也不会变化
    更新结束。

更新停止条件:

  • 更新后parent的平衡因子等于0,更新中parent的平衡因子变化为-1->0 或者 1->0,说明更新前parent子树⼀边高⼀边低,新增的结点插入在低的那边,插入后parent所在的子树高度不变,不会影响parent的⽗亲结点的平衡因子,更新结束。

  • 更新后parent的平衡因子等于1 或 -1,更新前更新中parent的平衡因子变化为0->1 或者 0->-1,说明更新前parent子树两边⼀样高,新增的插入结点后,parent所在的子树⼀边高⼀边低,parent所在的子树符合平衡要求,但是高度增加了1,会影响parent的⽗亲结点的平衡因子,所以要继续向上更新。

  • 更新后parent的平衡因子等于2 或 -2,更新前更新中parent的平衡因子变化为1->2 或者 -1->-2,说明更新前parent子树⼀边高⼀边低,新增的插入结点在高的那边,parent所在的子树高的那边更高了,破坏了平衡,parent所在的子树不符合平衡要求,需要旋转处理,旋转的目标有两个:1、把parent子树旋转平衡。2、降低parent子树的高度,恢复到插入结点以前的高度。所以旋转后也不需要继续往上更新,插入结束。

  • 不断更新,更新到根,跟的平衡因子是1或-1也停停止了。

三.旋转

3.1旋转的原则

旋转的原则如下:

  1. 保持搜索树的规则
  2. 让旋转的树从不满足变平衡,其次降低旋转树的高度
    旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。
    说明:下面的图中,有些结点我们给的是具体值,如10和5等结点,这里是为了方便讲解,实际中是什么值都可以,只要大小关系符合搜索树的性质即可。

在这里插入图片描述

3.2右单旋

旋转时我们需要注意保持二叉搜索树的性质。
同时注意父亲节点和平衡因子的更新。
注意旋转后子树的高度不变,平衡因子向上更新停止。
在这里插入图片描述

	void RoRateR( node* parent)//右单旋{node* subL = parent->left;node* subLR = subL->right;node* pparnet = parent->parent;parent->left = subLR;if (subLR){subLR->parent = parent;//修改父节点}subL->right = parent;parent->parent = subL;if (pparnet == nullptr)//parent就是根节点{_root = subL;subL->parent = nullptr;}else{if (pparnet->left == parent)//确定parent节点是左还是右{pparnet->left = subL;}else {pparnet->right = subL;}subL->parent = pparnet;//修改父节点}subL->bf = parent->bf = 0;//更新平衡因子}

3.3左单旋

左单旋和右单旋类似。

void RoRateL( node* parent)//左单旋
{node* subR = parent->right;node* subRL = subR->left;node* pparnet = parent->parent;parent->right = subRL;if (subRL)//防止subRL为空{subRL->parent = parent;//修改父节点}subR->left = parent;parent->parent = subR;if (pparnet==nullptr)//parent就是根节点{_root = subR;subR->parent = nullptr;}else{if (pparnet->left == parent)//确定parent节点是左还是右{pparnet->left = subR;}else {pparnet->right = subR;}subR->parent = pparnet;//修改父节点}subR->bf = parent->bf = 0;//更新平衡因子
}

3.4左右双旋

左右双旋的就是先左单旋再右单旋。
同时注意平衡因子的更新即可。


更新条件:parent->bf == -2 && cur->bf == 1

void RoRateLR( node* parent)//左右双旋
{node* subL = parent->left;node* subLR = subL->right;int bf = subLR->bf;//先记录插入后的平衡因子RoRateL(subL);RoRateR(parent);if (bf == 0)//分情况讨论{parent->bf = 0;subL->bf = 0;subLR->bf = 0;}else if (bf == 1){parent->bf = 0;subL->bf = -1;subLR->bf = 0;}else if (bf == -1){parent->bf = 1;subL->bf = 0;subLR->bf = 0;}else{assert(false);}
}

3.5右左双旋

右左双旋情况和左右双旋类似,这里就不过多赘述了。

更新条件:parent->bf == 2 && cur->bf == -1

3.6AVL树的插入

结合前面的知识我们就可以写出二叉搜索树的插入了。

void RoRateR( node* parent)//右单旋
{node* subL = parent->left;node* subLR = subL->right;node* pparnet = parent->parent;parent->left = subLR;if (subLR){subLR->parent = parent;//修改父节点}subL->right = parent;parent->parent = subL;if (pparnet == nullptr)//parent就是根节点{_root = subL;subL->parent = nullptr;}else{if (pparnet->left == parent)//确定parent节点是左还是右{pparnet->left = subL;}else {pparnet->right = subL;}subL->parent = pparnet;//修改父节点}subL->bf = parent->bf = 0;//更新平衡因子
}
void RoRateL( node* parent)//左单旋
{node* subR = parent->right;node* subRL = subR->left;node* pparnet = parent->parent;parent->right = subRL;if (subRL)//防止subRL为空{subRL->parent = parent;//修改父节点}subR->left = parent;parent->parent = subR;if (pparnet==nullptr)//parent就是根节点{_root = subR;subR->parent = nullptr;}else{if (pparnet->left == parent)//确定parent节点是左还是右{pparnet->left = subR;}else {pparnet->right = subR;}subR->parent = pparnet;//修改父节点}subR->bf = parent->bf = 0;//更新平衡因子
}
void RoRateLR( node* parent)//左右双旋
{node* subL = parent->left;node* subLR = subL->right;int bf = subLR->bf;//先记录插入后的平衡因子RoRateL(subL);RoRateR(parent);if (bf == 0)//分情况讨论{parent->bf = 0;subL->bf = 0;subLR->bf = 0;}else if (bf == 1){parent->bf = 0;subL->bf = -1;subLR->bf = 0;}else if (bf == -1){parent->bf = 1;subL->bf = 0;subLR->bf = 0;}else{assert(false);}
}
void RoRateRL( node* parent)//右左双旋
{node* subR = parent->right;node* subRL = subR->left;int bf = subRL->bf;//先记录插入后的平衡因子RoRateR(subR);RoRateL(parent);if (bf == 0)//分情况讨论{parent->bf = 0;subR->bf = 0;subRL->bf = 0;}else if (bf == 1){parent->bf = -1;subR->bf = 0;subRL->bf = 0;}else if (bf == -1){parent->bf = 0;subR->bf = 1;subRL->bf = 0;}else{assert(false);}
}
bool Insert(const k& x, const v& v)
{if (_root == nullptr)//插入根节点{_root = new node(x, v);return true;}node* cur = _root;node* parent = nullptr;//保留父亲节点while (cur){/*介质不冗余*/if (x < cur->_key){parent = cur;cur = cur->left;}else if (x > cur->_key){parent = cur;cur = cur->right;}else{return false;}//介质冗余//if (x <= cur->_key)//相等插入到左子树//{//	parent = cur;//	cur = cur->left;//}//else if (x > cur->_key)//{//	parent = cur;//	cur = cur->right;//}}cur = new node(x, v);if (x > parent->_key){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){RoRateR(parent);}else if (parent->bf == 2 && cur->bf == 1){RoRateL(parent);}else if (parent->bf == -2 && cur->bf == 1){RoRateLR(parent);}else if (parent->bf == 2 && cur->bf == -1){RoRateRL(parent);}else{assert(false);}break;}else{assert(false);}}return true;
}

3.7AVL树的查找

在避免了二叉搜索树退化为单叉树的情况。
AVL树的查找效率为O(logN).

四.AVL树的检测

4.1AVL树检测

AVL树我们可以递归检测每颗子树的左右高度差是否不差过1即可。

void Inorder()
{_Inorder(_root);cout << endl;
}
bool IsBalanceTree()
{return _IsBalanceTree(_root);
}
bool _IsBalanceTree(const node* root)
{// 空树也是AVL树if (nullptr == root)return true;// 计算pRoot结点的平衡因⼦:即pRoot左右⼦树的⾼度差int leftHeight = _Height(root->left);int rightHeight = _Height(root->right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因⼦与pRoot的平衡因⼦不相等,或者// pRoot平衡因子的绝对值超过1,则⼀定不是AVL树if (abs(diff) >= 2){cout << root->_value << "高度差异常" << endl;return false;}if (root->bf != diff){cout << root->_key << "平衡因子异常" << endl;return false;}// pRoot的左和右如果都是AVL树,则该树⼀定是AVL树return _IsBalanceTree(root->left) && _IsBalanceTree(root->right);
}
void _Inorder(const node* root)
{if (root == nullptr){return;}_Inorder(root->left);cout << root->_key << ":" << root->_value<<endl;_Inorder(root->right);
}
size_t Size()
{return _Size(_root);
}
size_t _Size(const node* parent)
{if (parent){return 1 + _Size(parent->left)+ _Size(parent->right);}else{return 0;}
}
size_t Height()
{return _Height(_root);
}
size_t _Height(const node* parent)
{if (parent){return 1 + max(_Height(parent->left), _Height(parent->right));}else{return 0;}
}

4.2 AVL树的验证

  • 检测一
void TestAVLTree1()
{AVL::AVLTree<int, int>t;// 常规的测试⽤例//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的带有双旋场景的测试⽤例int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert(e, e);}t.Inorder();cout << t.IsBalanceTree() << endl;
}

在这里插入图片描述
在这里插入图片描述

  • 检测二
void TestAVLTree2()
{const int N = 100000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();AVL::AVLTree<int, int> t;for (auto e : v){t.Insert(e, e);}size_t end2 = clock();cout << "Insert:" << end2 - begin2 << endl;cout << t.IsBalanceTree() << endl;cout << "Height:" << t.Height() << endl;cout << "Size:" << t.Size() << endl;size_t begin1 = clock();// 确定在的值/*for (auto e : v){t.Find(e);}*/// 随机值for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end1 = clock();cout << "Find:" << end1 - begin1 << endl;
}

后言

这就是AVL树的深度剖析。大家自己好好消化!今天就分享到这!感谢各位的耐心垂阅!咱们下期见!拜拜~

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

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

相关文章

支持向量机(SVM)在肝脏CT/MRI图像分类(肝癌检测)中的应用及实现

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…

DeepSeek扫雷游戏网页版HTML5(附源码)

用DeepSeek帮忙生成一个网页版的扫雷游戏&#xff0c;效果非常棒&#xff0c;基于HTML5实现&#xff0c;方便运行。 提示词prompt 帮我做一个网页版的 html5 扫雷游戏游戏功能说明 游戏难度&#xff1a; 1 简单&#xff1a;1010 格子&#xff0c;10个地雷 2 中等&#xff1a;16…

Day53GAN对抗生成网络思想

生成对抗网络&#xff08;GAN&#xff09;是深度学习领域的一种革命性模型&#xff0c;由Ian Goodfellow等人于2014年提出。其核心思想源于博弈论中的零和博弈&#xff0c;通过两个神经网络&#xff08;生成器和判别器&#xff09;的对抗性训练&#xff0c;实现数据的高质量生成…

meilisearch-轻量级搜索引擎

meilisearch是一款开源的轻量级搜索引擎&#xff0c;相比于elasticsearch等重量级搜索引擎&#xff0c;meilisearch注重数据搜索&#xff0c;从而而省去了其它不必要的功能&#xff08;如支持聚合分析、分布式搜索等特性&#xff09;&#xff0c;以便于快速上手开发和构建应用。…

51c大模型~合集150

我自己的原文哦~ https://blog.51cto.com/whaosoft/14034001 #原来Scaling Law还能被优化 Meta这招省token又提效 2017 年&#xff0c;一篇《Attention Is All You Need》论文成为 AI 发展的一个重要分水岭&#xff0c;其中提出的 Transformer 依然是现今主流语言模型…

每天一个前端小知识 Day 23 - PWA 渐进式 Web 应用开发

PWA 渐进式 Web 应用开发&#xff08;离线缓存、桌面安装等&#xff09; &#x1f9e0; 一、什么是 PWA&#xff1f; PWA&#xff08;Progressive Web App&#xff09;是一种让 Web 应用具有类似原生 App 用户体验的技术体系。 PWA 不是一个框架&#xff0c;而是由一组浏览器 A…

音视频会议服务搭建(设计方案-两种集成方案对比)-03

前言在开始计划之前&#xff0c;查阅了不少资料。一种方案是 Go层做信令业务&#xff0c;nodejs层来管理和mediasoup的底层交互&#xff0c;通过客户端去调用Go层&#xff1b;第二种方案是 客户端直接调用nodejs层来跟mediasoup去交互&#xff1b; 最终&#xff0c;当然不出意料…

【小白】linux安装ffmpeg | java转码 【超详细】

前言 最近在开发过程中&#xff0c;发现当我们上传除了mp4以外的其他少见的格式&#xff0c;如 .flv .rmvb 格式的视频时&#xff0c;在前端在线播放的时候会播放不出来画面&#xff0c;所以 接下来&#xff0c;将要进行一个非常完美的工程&#xff0c;将视频格式转为.mp4 1.安…

一个简单的脚本,让pdf开启夜间模式

因为平常我比较喜欢晚上看面试题。 市面上很多的面试题pdf都是白色的晚上看的话非常的刺眼。 所以我本能的去互联网搜索看看有没有pdf转换为夜间模式的。 搜索了一段时间后发现并没有这种东西。于是我自己做了一个转换的python脚本。 import os import fitz # PyMuPDF from P…

Flink OceanBase CDC 环境配置与验证

一、OceanBase 数据库核心配置 1. 环境准备与版本要求 版本要求&#xff1a;OceanBase CE 4.0 或 OceanBase EE 2.2组件依赖&#xff1a;需部署 LogProxy 服务&#xff08;社区版/企业版部署方式不同&#xff09;兼容模式&#xff1a;支持 MySQL 模式&#xff08;默认&#x…

c++对象池

【设计模式】其它经典模式-对象池模式&#xff08;Object Pool Pattern&#xff09;-CSDN博客 在C中&#xff0c;对象池&#xff08;Object Pool&#xff09;是一种管理对象生命周期的技术&#xff0c;旨在减少对象创建和销毁的开销&#xff0c;提高性能。对象池预先分配一定数…

JavaFX:Scene(场景)

简介 Scene对象是JavaFX场景图的根(root)。JavaFX 场景中包含所有可视的 JavaFX GUI 组件。JavaFX 场景由javafx.scene.Scene类表示。必须在 Stage(舞台)上设置 Scene 对象才能使其可见。在本 JavaFX Scene 教程中,将向您展示如何创建 Scene 对象并向其添加 GUI 组件。 创…

vue3.4中的v-model的用法~

1.首先以前我们针对父子组件传参是不是通过defineProps与defineEmits来实现的&#xff0c;但是这么比较繁琐&#xff0c;因为他是单向传参&#xff0c;而不是双向的&#xff0c;这里我们要介绍的是vue3.4的v-model来实现双向数据传递。 2、代码示例&#xff1a; //父组件 <…

nvm常用指令汇总

nvm是用来管理nodejs的&#xff0c;可以方便安装、切换、卸载当前环境的node版本。 以下是常用指令汇总&#xff1a;nvm list 查看本机已经安装的node版本。*表示当前系统正在使用的node版本nvm install xx.xx.x 后边加版本号&#xff0c;表示安装指定的版本nvm use xx.xx.x当前…

洛谷P5021 [NOIP 2018 提高组] 赛道修建【题解】【二分答案+树上贪心】

P5021 [NOIP 2018 提高组] 赛道修建 题意简述 给定一棵含 n n n 个点的无向带权树&#xff0c;求将其分裂为 m m m 条链后&#xff0c;最短的一条链的最大长度是多少&#xff1f; 点可以重复使用&#xff0c;边不可以重复使用。 思路 二分答案贪心判定貌似可以&#xff…

Portal认证过程杂谈

Portal认证模型简介 Portal认证模型通常由这四个设备组成 认证服务器即3A服务器&#xff0c;通常用radius服务器 接入设备通常就是NAC设备&#xff08;网络接入控制&#xff09; Portal服务器就是Portal认证的认证网站&#xff08;通常叫门户网站&#xff09; 认证过程简述…

ZSGuardian ---AI赋能,新一代研发管理守护平台 -即将上线

一场研发管理的革命 在数字化浪潮奔涌向前的今天&#xff0c;软件开发与产品研发的节奏不断加快&#xff0c;市场需求瞬息万变&#xff0c;技术迭代日新月异。对于研发团队而言&#xff0c;如何在复杂多变的环境中&#xff0c;高效地管理项目、保障产品质量、确保按时上线&…

小菜狗的云计算之旅,学习了解rsync+sersync实现数据实时同步(详细操作步骤)

Rsyncsersync实现数据实时同步 目录 Rsyncsersync实现数据实时同步 一、rsync概述 二、rsync运行原理 三、rsync部署 四、备份测试 五、使用非系统用户备份数据 5.1 rsync的配置文件介绍 5.2 配置备份目录 5.3 使用rsync用户备份测试 5.4 pull拉取数据 六、rsyncse…

牛客周赛Round 99(Go语言)

A题 (A.go) 思路总结: 这道题要求判断一个整数中是否包含连续的两个9。 核心思路是将输入的整数转换为字符串&#xff0c;然后遍历这个字符串&#xff0c;检查是否存在相邻的两个字符都是9。如果找到了&#xff0c;就立即停止遍历并输出"YES"&#xff1b;如果遍历完…

红外图像小目标检测热力图可视化系统

原创代码&#xff0c;可以工程修改含界面。