AVL树:高效平衡的二叉搜索树

server/2025/2/14 5:37:00/

🌟 快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。🌟

在这里插入图片描述

引言🤔

数据结构的奇妙世界里,二叉搜索树(BST)原本是查找数据的好帮手。想象一下,它就像一本有序的字典,能快速帮我们找到想要的信息。可要是数据排得太整齐,像排队一样有序,二叉搜索树就会“变形”成单支树,这时候查找数据就像在长长的队伍里一个个找,效率低得让人抓狂😫。别担心,1962年,两位聪明的俄罗斯数学家G.M.Adelson - Velskii和E.M.Landis发明了AVL树,给这个问题找到了完美的解决方案👏。

AVL树的概念🧐

1.1 平衡的定义

AVL树就像是一个超级严格的“平衡大师”😏,它不仅是二叉搜索树,还得满足每个节点的左右子树高度差(也就是平衡因子)的绝对值不能超过1。这就好比走钢丝的杂技演员,两边的平衡杆长度差得控制在一定范围内,才能稳稳地走在钢丝上。
在这里插入图片描述

1.2 高度与复杂度

因为AVL树这么会“平衡”,所以哪怕它有好多好多节点((n)个节点),它的高度也能保持在(O(log_2 n))这个不错的水平。这就意味着,我们查找数据的时候,时间复杂度也是(O(log_2 n)),快得飞起🛫!

AVL树节点的定义📋

要实现AVL树,先得把它的“小砖头”——节点定义好。每个节点都像一个小盒子,装着数据,还有指向左右“小伙伴”(子节点)和“家长”(父节点)的指针,另外还有个记录平衡因子的小标签。下面是用C++ 写的节点定义模板哦👇:

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;   // 该节点的左孩子AVLTreeNode<T>* _pRight;  // 该节点的右孩子AVLTreeNode<T>* _pParent; // 该节点的双亲T _data;int _bf;         // 该节点的平衡因子
};

AVL树的插入🎯

AVL树插入新数据就像往一个有序的书架上放新书,分两步走:

3.1 二叉搜索树插入

先按照二叉搜索树的规矩,从根节点开始,把新书(新数据)和每个节点的数据比较。要是新书比当前节点的数据小,就往左走;要是大,就往右走,直到找到合适的空位,把新书放进去📚。

3.2 平衡因子调整

新书放进去后,可能会把书架弄“歪”,也就是破坏了AVL树的平衡。这时候就得从新书的“家长”开始,沿着书架往上检查,调整每个节点的平衡因子。在调整过程中,“家长”节点的平衡因子会根据新书插在左边还是右边做相应变化(左边就减1,右边就加1)。这时候“家长”的平衡因子可能出现三种情况:

  • 平衡因子为0:这说明放新书之前,“家长”的平衡因子是正负1,放进去后刚刚好变成0,书架又稳了,插入成功🎉。
  • 平衡因子为正负1:意味着放新书前“家长”的平衡因子是0,放进去后变成正负1,以“家长”为根的这部分书架变高了,还得继续往上检查调整🧐。
  • 平衡因子为正负2:糟糕,这说明“家长”的平衡因子违反规定啦,得赶紧用旋转操作来把书架扶正😰。

AVL树的旋转🔄

当AVL树因为插入新书变得不平衡时,根据新书插的位置不同,有四种旋转“魔法”来恢复平衡:

4.1 左左(LL):右单旋

要是新书插在较高左子树的左侧,就来个右单旋。想象一下,失衡的节点(叫它(pParent))像个小塔,它的左子节点((pSubL))来帮忙把塔扶正。具体做法就是:

  • 把(pSubL)的右子节点((pSubLR))放到(pParent)的左边当左子节点。
  • 如果(pSubLR)存在,记得告诉它新“家长”是(pParent)。
  • 再把(pParent)放到(pSubL)的右边当右子节点。
  • 接着更新(pParent)和(pSubL)的“家长”指针,要是原来的塔尖(根节点)变了,也要更新根节点指针。
  • 最后,把(pParent)和(pSubL)的平衡因子都设为0,塔就稳稳当当啦🧱。
    在这里插入图片描述

4.2 右右(RR):左单旋

和左左情况相反,新书插在较高右子树的右侧时,就来个左单旋,跟右单旋类似,只是方向反过来。
在这里插入图片描述

4.3 左右(LR):先左单旋再右单旋

新书插在较高左子树的右侧时,先对失衡节点的左子节点来个左单旋,再对失衡节点来个右单旋。这个过程中,要小心保存和更新每个节点的平衡因子,保证书架重新平衡。
在这里插入图片描述

4.4 右左(RL):先右单旋再左单旋

新书插在较高右子树的左侧时,先对失衡节点的右子节点来个右单旋,再对失衡节点来个左单旋。同样,别忘了平衡因子的更新。
在这里插入图片描述

AVL树的验证✅

要看看AVL树是不是真的“合格”,可以从两方面检查:

5.1 验证为二叉搜索树

像给书架上的书排序一样,对AVL树进行中序遍历。要是遍历出来的顺序是有序的,那就说明它是个合格的二叉搜索树📖。

5.2 验证为平衡树

一个个检查每个节点的平衡因子,看看它们左右子树高度差的绝对值是不是都没超过1。同时,还要检查平衡因子算得对不对🧮。
在这里插入图片描述

AVL树的删除🗑️

AVL树删除节点就像从书架上拿走一本书,和二叉搜索树的删除方法差不多。但拿走书后,书架可能又歪了,得重新调整平衡。有时候运气不好,可能得一直调整到最上面的根节点。具体怎么做,可以参考《算法导论》或者《数据结构 - 用面向对象方法与C++描述》殷人昆版📚。

AVL树的性能📈

AVL树在查找数据方面就像个超级跑车,时间复杂度是(O(log_2 n)),快得没话说。可要是经常对它进行插入和删除这些“大动作”,就像经常给跑车改装,为了保持平衡,得频繁调整,性能就会受影响,特别是删除的时候,可能要一直调整到根节点,很费时间。所以呢,要是数据基本不怎么变,查询又多,AVL树就是个好选择;要是数据经常变来变去,它可能就不太合适啦😕。

AVL树实现代码示例📄

#pragma once
#include <iostream>
#include <utility>
#include <cassert>template<class K, class V>
class AVLTreeNode
{
public:AVLTreeNode(const std::pair<K, V>& kv) : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0) {}AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // 平衡因子std::pair<K, V> _kv;
};template<class K, class V>
class AVLTree
{
public:typedef AVLTreeNode<K, V> Node;~AVLTree(){destroy(_root);}void destroy(Node* node){if (node){destroy(node->_left);destroy(node->_right);delete node;}}bool insert(const std::pair<K, V>& key){try{if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){parent = cur;if ((cur->_kv).first < key.first){cur = cur->_right;}else if ((cur->_kv).first > key.first){cur = cur->_left;}else{return false;}}cur = new Node(key);cur->_parent = parent;if ((parent->_kv).first < (cur->_kv).first){parent->_right = cur;}else{parent->_left = cur;}// 考虑平衡因子while (cur){if (parent == nullptr){break; // 如果 parent 为 nullptr,说明已经到根节点,跳出循环}if (cur == parent->_right){parent->_bf++;}else{parent->_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 if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{assert(false);}}return true;}catch (const std::bad_alloc&){return false;}}void RotateL(Node* parent) // 左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}// 修改_bfparent->_bf = subR->_bf = 0;}void RotateR(Node* parent) // 右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subL->_right;if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* parentParent = parent->_parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}// 修改_bfparent->_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){// 新增的就是subRLparent->_bf = subR->_bf = subRL->_bf = 0;}else if (bf == 1){// subRL的右结点就是新增的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);}}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 1){subL->_bf = -1;parent->_bf = 0;subLR->_bf = 0;}else if (bf == -1){subL->_bf = 0;parent->_bf = 1;subLR->_bf = 0;}else{subL->_bf = 0;parent->_bf = 0;subLR->_bf = 0;}}// 中序打印方法void inorderPrint() const{inorderPrintHelper(_root);std::cout << std::endl;}// 判断是否为平衡二叉树bool isBalanced() const{int height = 0;return isBalancedHelper(_root, height);}private:Node* _root = nullptr;// 中序遍历辅助函数void inorderPrintHelper(Node* node) const{if (node){inorderPrintHelper(node->_left);std::cout << "Key: " << node->_kv.first << ", Value: " << node->_kv.second << std::endl;inorderPrintHelper(node->_right);}}// 判断是否为平衡二叉树的辅助函数bool isBalancedHelper(Node* node, int& height) const{if (node == nullptr){height = 0;return true;}int leftHeight = 0;int rightHeight = 0;// 递归判断左子树是否平衡if (!isBalancedHelper(node->_left, leftHeight)){return false;}// 递归判断右子树是否平衡if (!isBalancedHelper(node->_right, rightHeight)){return false;}// 计算当前节点的高度height = std::max(leftHeight, rightHeight) + 1;// 检查当前节点的左右子树高度差是否不超过 1if (std::abs(leftHeight - rightHeight) > 1){return false;}return true;}
};

总结🎉

AVL树就像一个聪明又有点小脾气的数据结构小伙伴😜。它用巧妙的平衡方法,解决了二叉搜索树可能失衡的问题,让查找数据又快又稳。它的插入、旋转和验证等操作一环扣一环,保证了自己时刻保持高效。虽然在结构经常变化的时候有点“小傲娇”,性能不太好,但在适合的场景下,绝对是个得力助手💪。通过深入了解AVL树,希望大家在编程时能更明智地选择数据结构,让程序跑得又快又好🚀。
在这里插入图片描述

投票环节🎊

你觉得AVL树在以下哪种场景最有用呢🧐?

  1. 数据基本不变,查询频繁:就像图书馆的藏书目录,很少新增或删除书籍,但经常有人查询。📚
  2. 数据频繁插入和删除:比如一个实时更新的新闻网站,不断有新新闻发布,旧新闻过期删除。📰
  3. 其他场景(请留言说明):说不定你有独特的想法哦,快来分享!💡

快来投出你宝贵的一票吧👇,让我们看看大家对AVL树的看法!🎯


http://www.ppmy.cn/server/167535.html

相关文章

人工智能丨Deepseek vs 传统测试工具:谁将主导软件质量保障?

如今软件质量保障已成为企业竞争力的核心命脉。传统的测试工具&#xff08;如Selenium、JMeter、JIRA等&#xff09;曾长期占据主导地位&#xff0c;但随着AI技术的突破&#xff0c;以Deepseek为代表的智能化测试平台正以颠覆性姿态冲击行业格局。这场新旧工具的较量&#xff0…

反向代理块sjbe

1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;将从服务器上得到的结果返回给客户端&#xff0c;此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说&#xff0c;反向代理就相当于…

ollama实践笔记

目录 一、linux安装文件命令&#xff1a; 二、启动ollama 三、linux 如何把ollama serve做为服务方式启动 四、安装deepseek-r1 五、如何在网页中使用ollama&#xff1f; ‌5.1 安装Open WebUI【不推荐】 5.2 安装ollama-webui-lite 六、Ubuntu安装docker、只需要一句话…

Qt QComboBox 下拉列表偏移问题探究:多屏幕与高 DPI 环境下的 bug

一、问题背景与重现步骤 现象描述&#xff1a; 在 Qt 应用程序中&#xff0c;主界面包含 QComboBox 控件&#xff0c;并且启用了高 DPI 支持&#xff08;例如在 main() 中调用 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling)&#xff09;。 当用户将窗口拖动到…

用大模型学大模型03-数学基础 概率论 随机变量 概率分布

deepseek.com:什么是概率&#xff0c;什么是随机变量&#xff1f;深度学习中常用概率的分布有哪些&#xff1f; 1. 什么是概率&#xff1f; 概率是描述事件发生的可能性的数值&#xff0c;范围在 0 到 1 之间&#xff1a; 0&#xff1a;事件不可能发生。1&#xff1a;事件必…

掌握 PHP 单例模式:构建更高效的应用

在 PHP 应用开发中&#xff0c;资源的高效管理至关重要。单例模式是一种能够帮助我们实现这一目标的设计模式。本文将深入探讨单例模式的概念、工作原理以及在 PHP 项目中何时应该&#xff08;或不应该&#xff09;使用它。 什么是单例模式&#xff1f; 单例模式是一种设计模…

荣耀手机Magic3系列、Magic4系列、Magic5系列、Magic6系列、Magic7系列详情对比以及最新二手价格预测

目录 荣耀Magic系列手机详细对比 最新二手价格预测 性价比分析 总结 以下是荣耀Magic系列手机的详细对比以及最新二手价格预测&#xff1a; 荣耀Magic系列手机详细对比 特性荣耀Magic3系列荣耀Magic4系列荣耀Magic5系列荣耀Magic6系列荣耀Magic7系列处理器骁龙888&#x…

mongoTemplate获取某列最大值

首先&#xff0c;MongoDB中获取某列的最大值通常是通过聚合框架中的$group和$max操作符来完成的。那在Spring Data中&#xff0c;应该怎么构建这个聚合查询呢&#xff1f; 首先&#xff0c;可能需要创建一个Aggregation对象&#xff0c;里面包含分组和求最大值的步骤。比如&…