当前位置: 首页 > news >正文

数据结构系列之AVL树


前言

AVL树是为了解决搜索二叉树退化成链的问题,叫AVL树是因为发明者G. M. Adelson-Velsky和E. M. Landis 取首字母来的。

AVL树重要吗?其实很重要,因为他的旋转在红黑树中仍然会出现,但他仍然有缺陷,不然C++的map和set底层就不会是红黑树了。


一、什么是AVL树

AVL树仍然是一颗搜索二叉树,但他和普通的搜索二叉树的区别就是有一条规则:任意结点的左右子树的高度不会超过一,通过这一条规则来维护这颗树相对比较满。这样如果有N个结点,他的高度可维护在logN,插入的复杂度也在O(logN)

二、模拟

AVL实现的方法比较多,但是比较好理解的是利用平衡因子来控制,同时每一个结点维护三叉链(左右孩子和父亲),平衡因子怎么算?右子树的高度减去左子树的高度,既然满足这个条件,他的平衡因子只有三种可能:1/ - 1 / 0,插入会有变化就要去改。

这里模拟只模拟插入的过程,因为删除过于麻烦,也只是为了了解AVL。

节点定义:

前面已经讲了是三叉链结构,直接写成KV结构了

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

这里的bf就是平衡因子,balance factor,节点没啥讲的。

插入

插入可以分为两步:1.像搜索树一样插入。
2.维护平衡因子,根据平衡因子的状态进行旋转

非旋转

第一步:

bool insert(const pair<K, V>& kv)
{if (_root == nullptr){Node* newnode = new Node(kv);_root = newnode;return true;}else{Node* cur = _root;Node* parent = nullptr;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 (parent->_left == cur){parent->bf--;}else{parent->bf++;}if (parent->bf == 0){//更新结束break;}else if (parent->bf == 1 || parent->bf == -1){//继续更新cur = parent;parent = cur->_parent;}else if(parent->bf == 2 || parent->bf == -2){//旋转break;}

旋转:

一共几种情况?四种,2 * 2,父亲是2 或者 -2,孩子是1 或者 - 1
画图先看比较简单的两种情况,右单旋和左单旋,因为这两种情况对称,只分析一种:
在这里插入图片描述
这么旋转之后变得平衡了,那代码好不好写呢?要考虑的因素有很多,特别是父子关系,最重要的是考虑图里的8有没有父亲,如果没有,让10变成根,如果有,还需要相连。

if (parent->bf == 2 && cur->bf == 1)
{RotateLeft(parent);//左单旋,右边高
}
void RotateLeft(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;//核心1if (curleft != nullptr){curleft->_parent = parent;}cur->_left = parent;//核心2 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;}cur->bf = parent->bf = 0;
}

对称的情况

else if (parent->bf == -2 && cur->bf == -1)
{RotateRight(parent);//左边高,右单旋
}
void RotateRight(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}cur->bf = parent->bf = 0;
}

双旋的情况:
在这里插入图片描述

else if (parent->bf == 2 && cur->bf == -1)
{//折线形状//进行右左双旋RotateRightLeft(parent);
}
else if (parent->bf == -2 && cur->bf == 1)
{RotateLeftRight(parent);
}
void RotateLeftRight(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->bf;RotateLeft(parent->_left);RotateRight(parent);if (bf == 0){return;}else if (bf == 1){cur->bf = -1;}else if (bf == -1){parent->bf = 1;}else{assert(false);}
}
void RotateRightLeft(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->bf;RotateRight(parent->_right);RotateLeft(parent);if (bf == 0){return ;}else if (bf == 1){parent->bf = -1;}else if(bf == -1){cur->bf = 1;}else{assert(false);}
}

这就是AVL树的插入了。

三、验证正确性

1.验证他是搜索树,可以走一个中序遍历

void _Inorder(Node* node)
{if (node == nullptr){return;}_Inorder(node->_left);cout << node->_kv.first << ' ' << node->_kv.second << ' ' << endl;_Inorder(node->_right);}

2.验证他是AVL树,可以层序遍历大致看看对不对

void _LevelOrder(Node* node)
{queue<Node*> dq;if (_root != nullptr)dq.push(_root);size_t LevelSize = 1;while (!dq.empty()){for (size_t i = 0; i < LevelSize; ++i){Node* front = dq.front();cout << front->_kv.first << ' ';dq.pop();if (front->_left){dq.push(front->_left);}if (front->_right){dq.push(front->_right);}}LevelSize = dq.size();cout << endl;}
}

最严谨的验证方式还是计算每一个节点的bf和他存储的bf相不相等。

bool IsBalance()
{return _IsBalance(_root);
}
bool _IsBalance(Node* root)
{//判断是否平衡if (root == nullptr)return true;int leftH = Height(root->_left);int RightH = Height(root->_right);if (RightH - leftH !=  root -> bf){cout << "平衡因子异常" << endl;cout << root->_left << endl;return false;}return abs(RightH - leftH) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
}
int Height(Node* node)
{if (node == nullptr) return 0;int leftH = Height(node->_left);int rightH = Height(node->_right);return leftH > rightH ? leftH + 1 : rightH + 1;
}

四、AVL的缺陷

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即logN但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数
据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合,整体还是不太常用的。

正因为AVL树过于严格,所以搞出来一颗红黑树来适当放宽限度,他也是map和set的底层。

总结

AVL的旋转是个难点,需要把握好边界,红黑树的旋转和AVL树的旋转类似,所以需要掌握。

http://www.lryc.cn/news/597173.html

相关文章:

  • Java冒泡排序的不同实现
  • Excel自动分列开票工具推荐
  • 耐达讯自动化EtherCAT转RS232:示波器连接的“开挂秘籍”
  • IDEA如何管理多个Java版本。
  • 图机器学习(16)——图数据与自然语言处理
  • 使用idea 将一个git分支的部分记录合并到git另一个分支
  • idea部署新项目时,用自定义的maven出现的问题解决
  • 【网络编程】二、socket编程
  • Excel 将数据导入到SQLServer数据库
  • Linux文件——Ext2文件系统(3)_软硬链接
  • Encore.ts:下一代高性能 TypeScript 后端框架的崛起
  • Qt(基本组件和基本窗口类)
  • 开源深度学习新宠:Burn框架助您无忧高效建模
  • Django实战:Python代码规范指南
  • 开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端
  • Neo4j如何修改用户密码?
  • Android14 锁屏密码修改为至少6位
  • ESP32-CAM实战:DIY基于OpenAI的AI视觉识别相机
  • DeepSeek Janus Pro本地部署与调用
  • Object Sense (OSE):一款从编辑器脚本发展起来的编程语言
  • 【markdown】 VSCode 使用 Markdown Preview Enhanced 插件转PDF
  • 【前端】ikun-pptx编辑器前瞻问题三: pptx的图片如何提取,并在前端渲染。
  • Android埋点实现方案深度分析
  • 模拟实现消息队列项目
  • 音视频学习(四十三):H264无损压缩
  • 《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——3. QML入门:像搭积木一样构建UI
  • ESP32-S3学习笔记<4>:I2C的应用
  • DeepSeek 助力 Vue3 开发:打造丝滑的日历(Calendar),日历_家庭维护示例(CalendarView01_31)
  • WebGIS 中常用空间数据格式
  • 2025暑期—06神经网络-常见网络3