一篇搞定AVL树+旋转【附图详解旋转思想】

🎉个人名片:

🐼作者简介:一名乐于分享在学习道路上收获的大二在校生
🙈个人主页🎉:GOTXX
🐼个人WeChat:ILXOXVJE
🐼本文由GOTXX原创,首发CSDN🎉🎉🎉
🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路
🐓每日一句:如果没有特别幸运,那就请特别努力!🎉🎉🎉
————————————————

一.AVL 树

1.1 AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。(上章提过)
所以就有人发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

AVL树的条件:
一棵AVL树可以是空树,是具有以下性质的二叉搜索树:
1.它的左右子树都是AVL树
2.左右子树高度之差(简称平衡因子)的绝对值不超过1 (-1/0/1)(如下图)(这里讨论的该节点的平衡因子=右子树高度-左子树高度

注意: 下图的平衡因子=左子树高度-右子树高度
在这里插入图片描述
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。

1.2 AVL树节点的定义

                                //树节点的定义
template<class K,class V>
struct AVLNode
{ AVLNode<K, V>* _left;        //存储左节点AVLNode<K, V>* _right;       //存储右节点AVLNode<K, V>* _parent;      //存储父亲pair<K,V> _kv;               //存储数据int _bl;                     //平衡因子AVLNode(pair<K, V>& kv)      //构造函数:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bl(0)     {}
};

1.3 AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

平衡因子的处理方法:

Cur插入后,Parent的平衡因子一定需要调整,在插入之前,Parent 的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  1. 如果Cur插入到Parent的左侧,只需给Parent的平衡因子-1即可
  2. 如果Cur插入到Parent的右侧,只需给Parent的平衡因子+1即可

此时:Parent的平衡因子可能有三种情况:0,正负1, 正负2

  1. 如果Parent的平衡因子为0,说明插入之前Parent的平衡因子为正负1,插入后被调整成0,此时满足 AVL树的性质,插入成功。
  2. 如果Parent的平衡因子为正负1,说明插入前Parent的平衡因子一定为0,插入后被更新成正负1,此时以Parent为根的树的高度增加,需要继续向上更新。
  3. 如果Parent的平衡因子为正负2,则Parent的平衡因子违反平衡树的性质,需要对其进行旋转处理。

【旋转代码在下面讲解后】
实现代码:

bool insert(pair<K,V>& kv)
{if (_root == nullptr){_root = new Node(kv);}//找插入位置Node* parent = nullptr;   //记录插入位置的父亲,方便插入后链接Node* cur = _root;while (cur){if (cur->_kv > kv){parent = cur;cur = cur->_left;}else if (cur->_kv < kv){parent = cur;cur = cur->_right;}else{return false;}}//到这里就说明找到插入位置了,下面就开始插入了//判断插在父亲的左边还是右边if (cur == parent->left)             {cur = new Node(kv);      parent->_left = cur;cur->_parent = parent;cur->_parent->_bl--;       //如果是在左边插入,则--平衡因子}else if (cur == parent->right){cur = new Node(kv);parent->_right = cur;cur->_parent = parent;cur->_parent->_bl++;      //如果是在右边插入,则++平衡因子}//调节上面节点的平衡值while (parent){//情况1:插入节点后父亲的平衡因子改变if (parent->_bl == 1 || parent->_bl == -1){Node* grandfather = parent->_parent;if (parent = grandfather->_left){grandfather->_bl--;parent = grandfather;}else{grandfather->_bl++;parent = grandfather;}}//情况二:插入节点后父亲的平衡因子不改变else if (parent->_bl == 0){break;}//情况三:父亲的平衡因子已经不满足AVL树的条件//需要旋转处理else if (){/下面的旋转代码后面讲解后贴出//左单旋if (){}//右单旋else if (){}//左右双旋else if (){}//右左双旋else if (){}}}
}

1.4AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
单旋分为:左单旋与右单旋
双旋分为:左右双旋与右左双旋

单旋思想

如图
在这里插入图片描述
如上图,上面的左右单旋,要么是在parent的左子树高,并且左子树中左边高(左左高)
要么是parent的右子树高,并且右子树中右边高(右右高),这种及只需要旋转一边就可以解决不平衡的问题,哪边高,就往另一边旋转即可。

1.4.1左单旋

使用场景:
新节点插入较高右子树的右侧—右右:左单旋。

如图:长方形代表高度为h的子树。

在这里插入图片描述
具体例子:
在这里插入图片描述

解析:

含义解析:

pNodeR = parent->_right
pNodeRL = pNodeR->_left

思想解析:
如上图,右单旋是让pNodeRL节点成为parent的右孩子,然后parent自己变为pNodeR的左孩子,pNodeR变成这个子树的根。

平衡因子的调节:
单旋后pNodeR与parent的平衡因子都变为0;

注意:
在旋转过程中,有以下几种情况需要考虑:

  1. 50节点的左孩子可能存在,也可能不存在。
  2. 25可能是根节点,也可能是子树
    如果是根节点,旋转完成后,要更新根节点。
    如果是子树,可能是某个节点的左子树,也可能是右子树。

实现代码

//左单旋
void rotateL(Node* parent)
{Node* pparent = parent->_parent;  //记录所旋转根节点的父亲Node* pNodeR = parent->_right;   Node* pNodeRL = pNodeR->_left;if (pNodeRL)                //如果该旋转节点的右节点的左孩子存在parent->_right = pNodeRL;pNodeR->_left = parent;  //新的父节点的链接if (parent == _root)       //若parent是根节点{_root = pNodeR;pparent = nullptr;     }else                     //parent不是根节点{if (pparent->_left == parent){pparent->_left = pNodeR;}else{pparent->_right = pNodeR;}}pNodeR->_bl = 0;parent->_bl = 0;
}
1.4.2右单旋

使用场景:
新节点插入较高左子树的左侧—左左:右单旋

如图
在这里插入图片描述
具体例子:
在这里插入图片描述

解析:

含义解析

pNodeL = parent->_Left
pNodeLR = pNodeL->_right

思想解析

如上图,右单旋是让pNodeLR节点变为parent的左孩子,然后parent自己变为pNodeL的右孩子,pNodeL变成这个子树的根。

平衡因子的调节
单旋后pNodeL与parent的平衡因子都变为0;

在旋转过程中,有以下几种情况需要考虑:

  1. 5节点的右孩子可能存在,也可能不存在。
  2. 8可能是根节点,也可能是子树
    如果是根节点,旋转完成后,要更新根节点。
    如果是子树,可能是某个节点的左子树,也可能是右子树。

代码实现

/右单旋void rotateR(Node* parent){Node* pparent = parent->_parent;Node* pNodeL = parent->_left;Node* pNodeLR = pNodeL->_right;if (pNodeLR)parent->_left = pNodeLR;pNodeL->_right = parent;if (parent == _root){_root = pNodeL;pparent = nullptr;}else{if (pparent->_left == parent){pparent->_left = pNodeL;}else{pparent->_right = pNodeL;}}pNodeL->_bl = 0;parent->_bl = 0;}
双旋思想

如图
在这里插入图片描述
如上图,如果parent的左子树高,并且左子树中的右子树高(左右高),或则是parent的右子树高,并且右子树的左子树高(右左高),则旋转一次不能解决问题,所以就有了双旋的思想。

1.4.3左右单双旋

使用场景:

新节点插入较高左子树的右侧—左右:先左单旋再右单旋

如图
在这里插入图片描述
具体例子:
在这里插入图片描述
解析:
因为是parent的左子树中的右子树高,所以只需要先将parent的左子树进行左旋,将parent的左子树变为左边高,则旋转后parent整个树就变为了左左高,再用上面单旋的思想,parent以旋转点进行右旋即可;

else if (parent->_bl == -2 && parent->_left->_bl == 1)
{int bl = parent->_left->_right->_bl;Node* pNodeL = parent->_left;Node* pNodeLR = pNodeL->_right;rotateL(pNodeL);     //左旋转rotateR(parent);    //右旋转if (-1 == bl)      //分情况调节平衡因子{pNodeLR->_bl = 0;pNodeL->_bl = 0;parent->_bl = 1;}else if (1 == bl){pNodeLR->_bl = 0;parent->_bl = 0;pNodeL->_bl = -1;}else{pNodeLR->_bl = 0;parent->_bl = 0;pNodeL->_bl = 0;}
}
1.4.4右左单旋

使用场景:
新节点插入较高右子树的左侧—右左:先右单旋再左单旋

如图
在这里插入图片描述
具体例子:
在这里插入图片描述

解析:

因为是parent的右子树中的左子树高,所以只需要先将parent的右子树进行右旋,将parent的右子树变为右边高,则旋转后parent整个树就变为了右右高,再用上面单旋的思想,parent以旋转点进行左旋即可;

代码实现

else if (parent->_bl == 2 && parent->_right->_bl == -1)
{Node* pNodeR = parent->_right;Node* pNodeRL = pNodeR->_left;int bl = pNodeRL->_bl;rotateR(pNodeR);   //先右旋rotateL(parent);   //再左旋pNodeRL->_bl = 0;    //分情况调节平衡因子if (1 == bl){parent->_bl = -1;pNodeR->_bl = 0;}else if (-1 == bl){parent->_bl = 0;pNodeR->_bl = 1;}else{parent->_bl = 0;pNodeR = 0;}
}
双旋后平衡因子的调节

我们从结果来看,忽略过程,从图中可以得到
在这里插入图片描述
解析:
实际上就是将60的左孩子给了30的右孩子,把60的有孩子给了90的左孩子。
所以可以得出:
平衡因子的改变与60的平衡因子有关(与它的左右孩子有关)。
情况分为3种:60的平衡因子为(1,0,-1)

下图解为:右左双旋
当为1时:
在这里插入图片描述
结论:

pNodeRL的平衡因子为0
parent–>-1
pNodeR–>0

当为0时:
在这里插入图片描述
结论:

pNodeRL的平衡因子为0
parent–>0
pNodeR–>0

当为-1时:
在这里插入图片描述
结论:

pNodeRL的平衡因子为0
parent–>0
pNodeR–>1

左右双旋的图解
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

旋转总结:

假如以Parent为根的子树不平衡,即Parent的平衡因子为2或者-2
分以下情况考虑:

  1. Parent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pNodeR 当pNodeR的平衡因子为1时,执行左单旋 当pNodeR的平衡因子为-1时,执行右左双旋
  2. Parent的平衡因子为-2,说明Parent的左子树高,设Parent的左子树的根为pNodeL 当pNodeL的平衡因子为-1是,执行右单旋 当pNodeL的平衡因子为1时,执行左右双旋
    旋转完成后,原Parent为根的子树个高度降低,已经平衡,不需要再向上更新。

1.5 AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    节点的平衡因子是否计算正确。

代码实现
方法一:

	int Hight(Node* root)    //计算该节点的高度{if (root == nullptr){return 0;}int Hightleft = Hight(root->_left);int Hightright = Hight(root->right);return Hightleft > Hightright ? Hightleft + 1 : Hightright + 1;  //返回左右子树高的那一个}bool _isbalance(Node* root){if(root==nullptr){return true;}int hightleft = Hight(root->_left);    //计算左右子树的高度int hightright = Hight(root->_right);if (abs(hightright - hightleft) >= 2)   //判断高度差{return flase;}if (hightright - hightleft !=root->_bl)    //判断计算结果是否与该节点的平衡因子相等{cout << root->_kv->first<<':' << "异常" << endl;return false;}return isbalance(root->_left) && isblance(root->_right);  //递归}

方法一有大量的重复计算(每一个节点都需要重新计算高度)
方法二更优

方法二:

	bool _isbalance(Node* root,int& height)   //height记录高度{if (root == nullptr){height = 0;return true;}if (!isbalance(root->_left,height) || !isblance(root->_right,height)){return false;}int heightleft = 0;int heightright = 0;if (abs(heightright - heightleft) >= 2)    //如果高度差超过1,则不平衡,返回false{return false;}if (heightright - heightleft != root->_bl)   //检查该节点的平衡因子是否正确{cout << root->_kv->first << ':' << "异常" << endl;return false;}height = heightleft > heightright ? heightleft + 1 : heightright + 1;   //计算height的值return true;}bool isbalance(){return _isbalance(_root);}

1.6 AVL树的性能分析

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

本章完~

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

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

相关文章

【C语言】贪吃蛇【附源码】

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 一、游戏说明&#xff1a; 一个基于C语言链表开发的贪吃蛇游戏&#xff1a; 1. 按方向键上下左右&#xff0c;可以实现蛇移动方向的改变。 2. 短时间长按方向键上下左右其中之一&#xff0c;可实现蛇向该方向的短时间…

智能指针(C++11)

智能指针的使用 问题 我们在平时写程序的时候&#xff0c;有些情况下不可避免地会遇见内存泄露的情况。内存泄露是指因为疏忽或错误&#xff0c;造成程序未能释放已经不再使用的内存的情况。例如下面这个例子&#xff0c;内存泄漏不易被察觉。 int div() {int a, b;cin >…

【复习linux相关命令】

查看命令位置&#xff0c;查找命令 which命令 查看命令的位置 [rootVM-12-15-opencloudos ~]# which cd /usr/bin/cd [rootVM-12-15-opencloudos ~]# which java /usr/local/java/jdk1.8.0_261/bin/java [rootVM-12-15-opencloudos ~]# which pwd /usr/bin/pwdfind查找文件 …

element+Vue2,在一个页面跳转到另一个页面,并自动选中table的某一行

需求&#xff1a;点击A页面的某处&#xff0c;跳转到B页面并选中B页面表格的某一行&#xff08;点击B页面的搜索后需要清空默认选择的状态&#xff09;环境&#xff1a;vue2、element的table&#xff0c;table允许多选知识点&#xff1a;主要使用到table的这两属性&#xff1a;…

Diffusion添加噪声noise的方式有哪些?怎么向图像中添加噪声?

添加噪声的方式大致分为两种&#xff0c;一种是每张图像在任意timestep都加入一样的均匀噪声&#xff0c;另一种是按照timestep添加不同程度的噪声 一、在任意timestep都加入一样的noise batch_size 32x_start torch.rand(batch_size,3,256,256) noise torch.randn_like(x_…

亚信安全荣获2023年度5G创新应用评优活动两项大奖

近日&#xff0c;“关于2023 年度5G 创新应用评优活动评选结果”正式公布&#xff0c;亚信安全凭借在5G安全领域的深厚积累和创新实践&#xff0c;成功荣获“5G技术创新的优秀代表”和“5G应用创新的杰出实践”两项大奖。 面向异构安全能力的5G安全自动化响应系统 作为5G技术创…

【Mybatis 基础】增删改查(@Insert, @Delete, @Update, @Select)

Mybatis Insert Delete Update Select Mybatis用法基础操作 - 删除delete 传参SpringbootMybatisCrudApplicationTests 测试类删除预编译SQL 基础操作 - 插入Insert 插入SpringbootMybatisCrudApplicationTests 测试类插入对象主键返回 基础操作 - 更新UPDATE 更新SpringbootMy…

Python进阶编程 --- 1.类和对象

文章目录 第一章&#xff1a;1.初始对象1.1 使用对象组织数据1.2 类的成员方法1.2.1 类的定义和使用1.2.2 创建类对象1.2.3 成员变量和成员方法1.2.4 成员方法的定义语法1.2.5 注意事项 1.3 类和对象1.3.1 基于类创建对象 1.4 构造方法1.5 其他内置方法1.5.1 魔术方法str字符串…

(南京观海微电子)——DDIC显示触控芯片介绍

显示驱动芯片&#xff08;Display Driver Integrated Circuit&#xff0c;简称DDIC&#xff09;的主要功能是控制OLED显示面板。它需要配合OLED显示屏实现轻薄、弹性和可折叠&#xff0c;并提供广色域和高保真的显示信号。同时&#xff0c;OLED要求实现比LCD更低的功耗&#xf…

成绩管理系统|基于springboot成绩管理系统的设计与实现(附项目源码+论文)

基于springboot成绩管理系统的设计与实现 一、摘要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装毕业设计成绩管…

平衡二叉树(AVL树)

文章目录 平衡二叉树&#xff08;AVL树&#xff09;1、平衡二叉树概念2、平衡二叉树的的实现2.1、平衡二叉树的结点定义2.2、平衡二叉树的插入2.3、平衡二叉树的旋转2.3.1、右单旋&#xff08;R旋转&#xff09;2.3.2、左单旋&#xff08;L旋转&#xff09;2.3.3、先右单旋再左…

leetcode 周赛 391场

2. 换水问题 给你两个整数 numBottles 和 numExchange 。 numBottles 代表你最初拥有的满水瓶数量。在一次操作中&#xff0c;你可以执行以下操作之一&#xff1a; 喝掉任意数量的满水瓶&#xff0c;使它们变成空水瓶。用 numExchange 个空水瓶交换一个满水瓶。然后&#xf…

Django安装及第一个项目

1、安装python C:\Users\leell>py --version Python 3.10.6 可以看出我的环境python的版本3.10.6&#xff0c;比较新 2、 Python 虚拟环境创建 2.1 官网教程 目前&#xff0c;有两种常用工具可用于创建 Python 虚拟环境&#xff1a; venv 在 Python 3.3 及更高版本中默…

Vue系列——数据对象

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>el:挂载点</title> </head> <body&g…

C++11入门手册第二节,学完直接上手Qt(共两节)

C++多线程 #include <thread>:C++多线程库 #include <mutex>:C++互斥量库 #include <future>:C++异步库 多线程介绍 线程的创建 void entry_1() { }以普通函数作为线程入口函数:void entry_2(int val) { }​std::thread my_thread_1(entry_1);std::thr…

【c++】类和对象(六)深入了解隐式类型转换

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff0c;本篇文章我们来到初始化列表&#xff0c;隐式类型转换以及explicit的内容 目录 1.初始化列表1.1构造函数体赋值1.2初始化列表1.2.1隐式类型转换与复制初始化 1.3e…

C语言-写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。

0xaaaaaaaa...等是什么&#xff1f;-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137179252 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #define SWAP(num) (((num & 0xAAAAAAAA) >> 1) | ((num & 0x55555555) << …

WIFI驱动移植实验:配置 Linux 内核

一. 简介 前面文章删除了Linux内核源码&#xff08;NXP官方的kernel内核源码&#xff09;自带的 WIFI驱动。 WIFI驱动移植实验&#xff1a;删除Linux内核自带的 RTL8192CU 驱动-CSDN博客 将正点原子提供的 rtl8188EUS驱动源码添加到 kernel内核源码中。文章如下&#xff1a…

JavaScript基础语法–变量

文章目录 认识JavaScript变量程序中变量的数据&#xff08;记录&#xff09;–变量变量的命名格式在Java script中变量定义包含两部分1. 变量声明&#xff08;高级JS引擎接下来定义一个变量&#xff09;2. 其他的写法 变量命名的规范&#xff08;遵守&#xff09;变量的练习a. …

C语言每日一题

1.题目 二.分析 本题有两点需要注意的&#xff1a; do-while循环 &#xff1a;在判断while条件前先执行一次do循环static变量 &#xff1a;程序再次调用时static变量的值不会重新初始化&#xff0c;而是在上一次退出时的基础上继续执行。for( i 1; i < 3; i )将调用两次…