如何在AVL树中高效插入并保持平衡:一步步掌握旋转与平衡因子 —— 平衡因子以及AVL结构篇

devtools/2025/3/16 23:33:32/

文章目录

  • AVL树的概念
  • AVL树的结构
  • AVL树的插入
  • 平衡因子更新终止条件
  • 插入以及平衡因子的保持
  • AVL树的查找

AVL树的概念

AVL树(Adelson-Velsky and Landis Tree)是一种自平衡二叉查找树,它的特点是每个节点的左子树和右子树的高度差不能超过1。这意味着AVL树是一种平衡的二叉树,通过保持树的平衡来保证查找操作的时间复杂度为O(log n),从而提高性能。

AVL树当中,重要的一个概念:

  • 平衡因子(Balance Factor, BF): 每个节点都有一个平衡因子,定义为节点的左子树高度减去右子树高度。(当然,右子树的高度减去左子树的高度也是可以的)平衡因子的值只能是-1、0或1。

  • 今天这篇文章,作者定义的平衡因子是 bf = rh(right height) - lh(left height);即选择右子树高度减去左子树高度

AVL树的结构

在这里插入图片描述

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:Node* _root;  // 根节点指针
};

AVL树的插入

重点关注如何在插入时保持树的平衡。

插入操作概述
AVL树插入操作的过程与普通的二叉搜索树插入过程相似,唯一不同的是,AVL树在插入节点后需要检查树的平衡性,并进行必要的旋转操作来保持平衡。我们可以将插入操作分为几个步骤:

  1. 二叉搜索树的插入:按照二叉搜索树的规则插入节点。
  2. 更新节点高度:插入节点后,需要更新沿路径的所有节点的高度。
  3. 检查平衡因子:检查每个节点的平衡因子,确定是否需要进行旋转。
  4. 旋转操作:如果某个节点的平衡因子不在-1, 0, 1范围内,进行旋转以恢复平衡。

平衡因子更新终止条件

  • 更新后parent的平衡因⼦等于0,更新中parent的平衡因⼦变化为-1->0或者1->0,说明更新前parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会影响parent的⽗亲结点的平衡因⼦,更新结束。
  • 更新后parent的平衡因⼦等于1或 -1,更新前更新中parent的平衡因⼦变化为0->1或者0->-1,说明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所在的⼦树符合平衡要求,但是⾼度增加了1,会影响arent的⽗亲结点的平衡因⼦,所以要继续向上更新。
  • 更新后parent的平衡因⼦等于2 或 -2,更新前更新中parent的平衡因⼦变化为1->2 或者 -1->-2,说明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼了,破坏了平衡,parent所在的⼦树不符合平衡要求,需要旋转处理,旋转的⽬标有两个:1、把parent⼦树旋转平衡。2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不需要继续往上更新,插⼊结束。

平衡因子更新的3种情况。

更新到10结点,平衡因⼦为2,10所在的⼦树已经不平衡,需要旋转处理
在这里插入图片描述
更新到中间结点,3为根的⼦树⾼度不变,不会影响上⼀层,更新结束
在这里插入图片描述
最坏更新到根停⽌
在这里插入图片描述

插入以及平衡因子的保持

	bool Insert(const pair<K, V>& kv) {if (_root == nullptr) {_root = new Node<K, V>(kv);return true;}Node<K, V>* parent = nullptr;Node<K, V>* cur = _root;// 寻找插入位置while (cur) {parent = cur;if (kv.first < cur->_kv.first) {cur = cur->_left;} else if (kv.first > cur->_kv.first) {cur = cur->_right;} else {return false;  // 键值已存在}}// 创建新的节点并连接到父节点cur = new Node<K, V>(kv);if (kv.first < parent->_kv.first) {parent->_left = cur;} else {parent->_right = cur;}cur->_parent = parent;// 更新平衡因子并处理不平衡while (parent) {if (cur == parent->_left) {parent->_bf--;  // 左子树高,平衡因子减1} else {parent->_bf++;  // 右子树高,平衡因子加1}//平衡因子 = 右子树 - 左子树// 如果平衡因子为0,结束更新if (parent->_bf == 0) {break;}// 如果平衡因子为±1,继续向上更新if (parent->_bf == 1 || parent->_bf == -1) {cur = parent;parent = parent->_parent;}// 如果平衡因子为±2,说明不平衡,进行旋转处理else if (parent->_bf == 2 || parent->_bf == -2) {// 旋转处理/**/   旋转的详细讲解在下一篇文章,有兴趣的读者可以前往下一篇进行查看**if (parent->_bf == -2 && cur->_bf == -1)//左高(在左子树)——右旋{RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1)//右高(在左子树)——左旋{RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1)//左高(在右子树)——左右旋{RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1)//右高(在左子树)——右左旋{RotateRL(parent);}else{assert(false);}break;} else {assert(false);  // 应该不会发生}}return true;}

AVL树的查找

⼆叉搜索树逻辑实现即可,搜索效率为 O(logN)

Node* Find(const K& key)
{// 从根节点开始查找Node* cur = _root;// 遍历树,直到找到目标节点或到达空节点while (cur){// 如果当前节点的键小于目标键,向右子树查找if (cur->_kv.first < key){cur = cur->_right;}// 如果当前节点的键大于目标键,向左子树查找else if (cur->_kv.first > key){cur = cur->_left;}// 如果当前节点的键等于目标键,返回当前节点else{return cur;}}// 如果遍历结束仍未找到目标节点,返回 nullptrreturn nullptr;
}

http://www.ppmy.cn/devtools/167669.html

相关文章

grunt构建工具:scss转css

Grunt 是一个基于 JavaScript 的任务运行工具&#xff0c;通常用于自动化重复性任务&#xff0c;例如代码编译、文件压缩、单元测试等。它通过配置文件 Gruntfile.js 来定义任务和插件。 完整项目地址&#xff1a;https://github.com/ylpxzx/grunt-scss-to-css 以下是 Grunt 的…

爬虫基础之爬取豆瓣同城信息(保存为csv excel 数据库)

网站:长沙最近一周戏剧活动_豆瓣 温馨提示: 本案例仅供学习交流使用 本案例所使用的模块 requests(发送HTTP请求)pandas(数据保存模块)lxml(用于解析数据模块)csv(用于保存为csv文件)pymysql(用于操作数据库)parsel(解析数据的模块) 确定爬取的信息内容&#xff1a; 戏剧的名称…

贪心算法(6)(java)优势洗牌

题目: 给定两个长度相等的数组nums1和nums2&#xff0c;nums1相对于nums2的优势可以满足nums1【1】>nums[2]的索引的数目来描述。 返回nums1的任意排列&#xff0c;使其相对于nums2的透视最大化呀。 原理&#xff08;贪心策略&#xff09;&#xff1a;田忌赛马 1.如果比不…

数据类设计_图片类设计之6_混合图形类设计(前端架构)

前言 学的东西多了,要想办法用出来.C和C是偏向底层的语言,直接与数据打交道.尝试做一些和数据方面相关的内容 引入 接续上一篇,讨论混合图形类设计 方法论-现在能做什么 这段属于聊天内容---有句话是这么说的&#xff1a;不要只埋头拉车&#xff0c;还要抬头看路。写代码也是…

蓝桥杯_LED模块

一 前言 还有四十多天将要进行蓝桥杯的比赛&#xff0c;接下来一个多月我将进行我的知识点的复习&#xff0c;争取在蓝桥杯提交一个满意的答卷 二 锁存器M74HC753M1R 在我这一年并没有进行在csdn上发布任何文章&#xff0c;这一年我学了stm32、51&#xff0c;还有部分理论知…

css梯形tab

效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Tab 示例</…

【go】函数类型的作用

Go 语言函数类型的巧妙应用 函数类型在 Go 语言中非常强大&#xff0c;允许将函数作为值进行传递和操作。下面详细介绍函数类型的各种妙用&#xff1a; 1. 回调函数 // 定义一个函数类型 type Callback func(int) int// 接受回调函数的函数 func processData(data []int, ca…

算力服务器主要是指什么?

随着科技的快速发展&#xff0c;人工智能也逐渐兴起&#xff0c;算力服务器也受到了各个企业的重视&#xff0c;本文就来为大家介绍一下算力服务器主要都是指什么吧&#xff01; 算力服务器对于人工智能领域来说&#xff0c;在深度学习模型的训练和推理过程中扮演着非常重要的角…