完整教程:【高阶数据结构】AVL树

news/2025/12/3 21:27:04/文章来源:https://www.cnblogs.com/yangykaifa/p/19304025

目录

  • 1. 什么是 AVL 树?
  • 2. 为什么需要 AVL 树?
  • 3. AVL树的模拟实现
    • 3.1 AVL树的定义
    • 3.2 AVL树的插入
      • 3.2.1 情况 1:右右情况(RR)(2,1) -> 左单旋
      • 3.2.2 情况 2:左左情况(LL)(-2,-1) -> 右单旋
      • 3.2.3 情况 3:右左情况(RL)(2,-1) -> 右左双旋(先右旋再左旋)
      • 3.2.4 情况 4:左右情况(LR)(-2,1) -> 左右双旋(先左旋再右旋)
      • 3.2.5 旋转的性质
    • 3.3 AVL树的求高以及平衡的判断
  • 4. 源代码

1. 什么是 AVL 树?

简单来说,AVL 树是一种自平衡的二叉搜索树(BST)。AVL 树又叫做平衡二叉树。

它是由两位苏联数学家 G. M. Adelson-Velsky 和 E. M. Landis 在 1962 年发明的,AVL 这个名字就来源于他们姓氏的首字母。

核心思想:在二叉搜索树的基础上,增加了一个平衡条件——对于树中的任意一个节点,其左子树和右子树的高度差不能超过 1。

这个“高度差”有一个专门的名字,叫做 平衡因子。

平衡因子 = 右子树高度 - 左子树高度
平衡因子的值只能是 -1, 0, 1 中的一个。如果出现 2 或 -2,就意味着树不平衡了,需要通过“旋转”来修复。

2. 为什么需要 AVL 树?

普通的二叉搜索树(BST)有一个致命缺点:它的性能严重依赖于插入的顺序。

最佳情况:插入顺序得当,树是完全平衡的,搜索、插入、删除的时间复杂度都是 O(log n)。

最坏情况:如果插入的数据是有序的(例如 1, 2, 3, 4, 5…),BST 会退化成一条链表,时间复杂度变为 O(n)。

AVL 树就是为了解决这个问题而生的。它通过强制维持树的平衡,保证了在最坏情况下,所有操作的时间复杂度仍然是 O(log n)。这个“维持平衡”的过程,就是我们下面要讲的 旋转。

3. AVL树的模拟实现

3.1 AVL树的定义

#pragma once
#include <iostream>#include <assert.h>using namespace std;template<typename K, typename 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<typename K, typename V>class AVLTree{typedef AVLTreeNode<K, V> Node;public:AVLTree():_root(nullptr){}private:Node* _root;};

3.2 AVL树的插入

关键步骤:

  1. 找到节点的位置
  2. 生成一个节点,并把节点连接好
  3. 以此节点的位置开始更新
      1. 把当前的parent节点平衡因子更新一下(+1或-1)
      1. 若parent节点平衡因子为0,则该树已经平衡,插入已经完成
      1. 若parent节点平衡因子为-1或者1,则继续更迭parent,cur,向上更新
      1. 若parent节点平衡因子为-2或者2,需要进行旋转,一次旋转(无论哪个旋转),都可以让parent的平衡因子直接变成0,即完成了插入。
bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}else{Node* parent = nullptr;Node* cur = _root;//1. 找到节点的位置while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}//2. 生成一个节点,并把节点连接好cur = new Node(kv);cur->_parent = parent;//三叉链需要额外链接cur的父亲节点if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}//3. 以此节点的位置开始更新while (parent)//当parent == nullptr的时候,无法继续向上更新,退出即可{//更新parent的平衡因子if (cur->_kv.first > parent->_kv.first){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 0)//此时父亲节点的平衡因子为0,退出即可{break;}else if (parent->_bf == 1 || parent->_bf == -1)//迭代cur和parent,继续{											   //向上更新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);}else//如果cur的平衡因子不符合为1或-1的情况,说明在插入前树已经不是AVL树了{   //直接断言错误即可assert(false);}break;}else//如果parent的平衡因子不符合为0,1或-1,2或-2的情况{   //说明在插入前树已经不是AVL树了,直接断言错误即可assert(false);}}}return true;}

接下来,我们讲旋转,讲旋转前,要知道的事情。
当 parent 和 cur 的平衡因子确定时,他们的位置也就确定了。
我们讲的左单璇,parent->_bf == 2 && cur->_bf == 1,即(2,1)的情况。那么简洁操作图解一定是:

A (平衡因子=2) parent
\
B (平衡因子=1) cur
\
C (新插入)

不可能是:

A (平衡因子=2) parent
/
B (平衡因子=1)  cur
/
C (新插入)

因为parent平衡因子为2,那么右子树一定出现了问题,那么cur一定出现在右边,因为cur是从插入点一步步更新过来的。

3.2.1 情况 1:右右情况(RR)(2,1) -> 左单旋

parent的平衡因子为 2 ,cur的平衡因子为 1 时,是需要使用左单旋的情况。
左单璇后,parent、cur 的平衡因子都变成 0
在这里插入图片描述
代码解析:

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

3.2.2 情况 2:左左情况(LL)(-2,-1) -> 右单旋

parent的平衡因子为 -2 ,cur的平衡因子为 -1 时,是需要使用右单旋的情况。
右单璇后,parent、cur 的平衡因子都变成 0
在这里插入图片描述
代码解析:

void RotateR(Node* parent)
{
Node* ppnode = parent->_parent;
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = curright;
if (curright)
{
curright->_parent = parent;
}
cur->_right = parent;
parent->_parent = cur;
if (ppnode == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
cur->_parent = ppnode;
}
else
{
ppnode->_right = cur;
cur->_parent = ppnode;
}
}
parent->_bf = cur->_bf = 0;
}

3.2.3 情况 3:右左情况(RL)(2,-1) -> 右左双旋(先右旋再左旋)

parent的平衡因子为 2 ,cur的平衡因子为 -1 时,是需要使用右左单旋的情况。
右左单璇后,parent、cur 的平衡因子不像左旋和右璇一样都变成0。而是有不同情况。

在这里插入图片描述
总结:

旋转前的curleft平衡因子旋转后的parent平衡因子旋转后的cur平衡因子旋转后的curleft平衡因子
第一种情况1-100
第二种情况-1100
第三种情况0000

代码如下:

void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
int bf = curleft->_bf;
RotateR(cur);
RotateL(parent);
if (bf == 0)//此时对应h == 0的情况,尽管我们调用的左单旋和右单旋中已经对parent和cur
{           //和curleft的平衡因子进行了无脑置0,但是这里为了解耦,即降低耦合性,所以
parent->_bf = 0;//我们需要手动置0,降低耦合性
cur->_bf = 0;
curleft->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
curleft->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
curleft->_bf = 0;
}
else//如果bf出现其它情况,那么插入前该树已经不是AVL树,所以直接断言false即可
{
assert(false);
}
}

3.2.4 情况 4:左右情况(LR)(-2,1) -> 左右双旋(先左旋再右旋)

parent的平衡因子为 -2 ,cur的平衡因子为 1 时,是需要使用左右单旋的情况。
左右单璇后,parent、cur 的平衡因子不像左旋和右璇一样都变成0。而是有不同情况。
其实就是和右左双旋同理。
在这里插入图片描述

总结:

旋转前的curright平衡因子旋转后的parent平衡因子旋转后的cur平衡因子旋转后的curright平衡因子
第一种情况10-10
第二种情况-1100
第三种情况0000

代码如下:

void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
int bf = curright->_bf;
RotateL(cur);
RotateR(parent);
if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
curright->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
cur->_bf = 0;
curright->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
cur->_bf = -1;
curright->_bf = 0;
}
else
{
assert(false);
}
}

3.2.5 旋转的性质

无论是左单旋、右单旋,还是左右双旋、右左双旋,旋转后,都是二叉搜索树。
左右双旋、右左双旋其实就是2个单旋的组合。因为左单旋、右单旋不会改变树为二叉搜索树。所以,双旋也不会改变

性质:二叉搜索树前序为升序。
即无论怎么旋转,都没有改变其前序的位置。

不信可以看看左旋、右旋的例子:

在这里插入图片描述
前序一直是:a 30 b 60 c
在这里插入图片描述
前序一直是:a 30 b 60 c

3.3 AVL树的求高以及平衡的判断

代码如下:

int Height()
{
return Height(_root);
}
int Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
int heightL = Height(root->_left);
int heightR = Height(root->_right);
return  heightL > heightR ? heightL + 1 : heightR + 1;
}
bool Isbalance()
{
return Isbalance(_root);
}
bool Isbalance(Node* root)
{
if (root == nullptr)
{
return true;
}
Node* left = root->_left;
Node* right = root->_right;
int heightL = Height(left);
int heightR = Height(right);
int bf = heightR - heightL;
if (bf != root->_bf)
{
cout << "平衡因子异常" << root->_kv.first << ':' << root->_bf << endl;return false;}return abs(bf) <= 1 && Isbalance(left) && Isbalance(right);}

4. 源代码

AVLTree.h:

#pragma once
#include <iostream>#include <assert.h>using namespace std;template<typename K, typename 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<typename K, typename V>class AVLTree{typedef AVLTreeNode<K, V> Node;public:AVLTree():_root(nullptr){}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}else{Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}while (parent){if (cur->_kv.first > parent->_kv.first){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);}else{assert(false);}break;}else{assert(false);}}}return true;}int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr){return 0;}int heightL = Height(root->_left);int heightR = Height(root->_right);return  heightL > heightR ? heightL + 1 : heightR + 1;}bool Isbalance(){return Isbalance(_root);}bool Isbalance(Node* root){if (root == nullptr){return true;}Node* left = root->_left;Node* right = root->_right;int heightL = Height(left);int heightR = Height(right);int bf = heightR - heightL;if (bf != root->_bf){cout << "平衡因子异常" << root->_kv.first << ':' << root->_bf << endl;return false;}return abs(bf) <= 1 && Isbalance(left) && Isbalance(right);}private:void RotateL(Node* parent){Node* ppnode = parent->_parent;Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}parent->_bf = cur->_bf = 0;}void RotateR(Node* parent){Node* ppnode = parent->_parent;Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}parent->_bf = cur->_bf = 0;}void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(cur);RotateL(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(cur);RotateR(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else{assert(false);}}private:Node* _root;};

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

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

相关文章

20251203周三日记

20251203周三日记今日: 1.炉石新版本大更。一早来实验室,边玩边调研,真挖到宝了。找到一个无训练的仓库,给师兄推过去,非常不错。 2.中午和同门吃饭,吃完回来继续跑vggt代码,手动下载调研的文章。好像3D genera…

了解NFSv4中的nfsidmap

nfsidmap 实用程序是 NFSv4 中处理 ID 映射 的关键组件,它在本地系统的UID/GID与 NFSv4 使用的user@domain字符串之间进行转换。这确保了在可能具有不同 UID/GID 映射的系统之间正确处理所有权和权限。 主要特性和使用…

你的学习思路有实践导向的优势,但需调整顺序和手段才能更高效成体系!核心结论:先搭建,边做边补原理,再集中突破面试考点,比 “先堆方案再回头学” 更高效。

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Scrum 冲刺博客_5

昨天已完成工作:完成基础数据的分类整理。 新增测试覆盖率阈值检查,确保代码质量。 完成基本代码框架并上传。今天计划完成的工作:开发组件内具体内容。 模块核心接口。 解决数据渲染延迟问题,优化页面加载速度。工…

2025年11月聚乙烯瓶厂家综合评估与选购指南:十大知名供应商深度解析

摘要 随着2025年化工包装行业的快速发展,聚乙烯瓶作为农药、医药、食品等领域的重要包装容器,其市场需求持续增长。本文基于行业调研数据和专家评估,为您呈现2025年11月聚乙烯瓶厂家的综合排名与分析,为采购决策提…

2025年12月广东顺德短视频代运营团队优势解读

本文深入解读2025年顺德短视频代运营团队的核心优势,聚焦广州云视信息服务有限公司的一站式短视频营销服务。通过分析本地化定制、技术赋能和全链路覆盖,结合客户案例和数据,为中小企业提供高效短视频运营解决方案,…

为什么需要多路召回

目录背景和价值一、向量检索的局限性1. 语义相似 ≠ 精确匹配2. 关键词匹配能力弱3. 多条件组合查询困难二、具体问题对比场景 1:精确数值查询场景 2:多条件组合查询场景 3:专业术语/品牌名查询三、向量检索 vs ES …

2025年11月聚乙烯瓶厂家综合排行榜:权威推荐与选购指南

摘要 随着2025年塑料包装行业的快速发展,聚乙烯瓶作为农药、化工、医药等领域的重要包装容器,其市场需求持续增长。本文基于行业数据、技术实力、客户口碑等多维度评估,为您呈现2025年11月聚乙烯瓶厂家综合排行榜,…

2025年11月农药瓶供应商排行榜:安徽金汇龙包装位居榜首

文章摘要 2025年农药包装行业迎来技术升级浪潮,随着环保政策趋严和智能制造的普及,农药瓶供应商面临新的机遇与挑战。本文基于市场调研数据和行业专家意见,为您呈现2025年11月农药瓶供应商综合排名榜单,安徽金汇龙…

2025年11月农药瓶供应商排行榜:安徽金汇龙包装领跑行业

摘要 2025年农药包装行业迎来技术升级浪潮,随着环保政策收紧和智能制造的推进,农药瓶供应商面临新的机遇与挑战。本文基于市场调研数据和行业专家评价,为您呈现2025年11月农药瓶供应商综合排名,并提供详细的对比分…

(论文阅读)An Image is Worth 32 Tokens for Reconstruction and Generation

(论文阅读)An Image is Worth 32 Tokens for Reconstruction and Generation1. 论文Yu et al. An Image is Worth 32 Tokens for Reconstruction and Generation. NeurIPS, 2024.摘要: 在视觉中,尤其是高分辨率设置…

2025年11月农药瓶供应商排行TOP10:安徽金汇龙包装领跑行业

摘要 2025年农药包装行业迎来技术升级浪潮,随着环保政策收紧和智能制造的推进,农药瓶供应商面临新的机遇与挑战。本文基于市场调研数据和技术参数分析,为您呈现2025年11月最具竞争力的农药瓶供应商排行榜,为农药生…

某中心与南加州大学联合遴选三位机器学习研究员

某机构与南加州大学的联合研究中心宣布,三位博士研究生荣获2022-23年度机器学习研究员称号,他们将获得资助并在某机构科学家的指导下,从事与机器学习隐私、安全和可信度相关的前沿研究。某中心与南加州大学联合遴选…

酷我音乐APP(手机音乐播放器) v12.0.0.2 去广告破解版

一、简介 📱酷我音乐是一款手机音乐播放器。酷我音乐app是一款拥有海量正版音乐库的免费听歌软件。软件提供在线听歌、电台、MV、歌词和个性化推荐等多种功能,下载安装后,用户可以在这里收听并下载高质量vip音乐,…

Alpha冲刺 - Day 7

Alpha冲刺 - Day 7(最终日) 团队名称: 书海拾贝队 冲刺日期: 2025年11月30日 冲刺阶段: Alpha阶段 Day 7/7 - 最终日一、站立式会议 1.1 会议信息会议时间: 2025年11月30日 10:00-10:30 会议地点: 图书馆三楼讨…

Alpha冲刺 - Day 6

Alpha冲刺 - Day 6 团队名称: 书海拾贝队 冲刺日期: 2025年11月29日 冲刺阶段: Alpha阶段 Day 6/7一、站立式会议 1.1 会议信息会议时间: 2025年11月29日 10:00-10:15 会议地点: 图书馆三楼讨论区 会议时长: 15分…

Alpha冲刺 - Day 5

Alpha冲刺 - Day 5 团队名称: 书海拾贝队 冲刺日期: 2025年11月28日 冲刺阶段: Alpha阶段 Day 5/7一、站立式会议 1.1 会议信息会议时间: 2025年11月28日 14:00-14:20 会议地点: 图书馆三楼讨论区 会议时长: 20分…

写的都队-冲刺总结

写的都队-冲刺总结这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/202501SoftwareEngineering这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/202501SoftwareEngineering/homework/14586团队名称 …

Alpha冲刺 - Day 4

Alpha冲刺 - Day 4 团队名称: 书海拾贝队 冲刺日期: 2025年11月27日 冲刺阶段: Alpha阶段 Day 4/7一、站立式会议 1.1 会议信息会议时间: 2025年11月27日 14:00-14:15 会议地点: 图书馆三楼讨论区 会议时长: 15分…

Python自动化脚本实战:批量创建用户、文件备份、日志清理、服务监控

在日常工作中,我们经常需要重复执行批量创建用户、定期备份文件、清理过期日志、监控服务状态等机械性操作。手动处理不仅耗时耗力,还容易出错。Python作为一门简洁高效的脚本语言,能轻松实现这些操作的自动化,帮我…