🔥🔥 欢迎来到小林的博客!!
🛰️博客主页:✈️小林爱敲代码
🛰️文章专栏:✈️小林的C++之路
🛰️欢迎关注:👍点赞🙌收藏✍️留言
AVL树是一个平衡二叉搜索,相比于红黑树,它更平衡。但是相比于插入删除的操作,红黑树更优。因为旋转有消耗,而红黑树的旋转明显要比AVL树少的多。所以这篇文章给大家带来了平衡二叉树的插入和查找操作,全程动图讲解!千万不要错过!至于删除操以后有机会为大家更新。
每日一句: 即使道路坎坷不平,车轮也要前进;即使江河波涛汹涌,船只也航行。
目录
AVL是一颗平衡二叉搜索树,相比于二叉搜索树。AVL它能始终保持平衡,因为二叉搜索树的缺陷太大。当有序插入数据时,整颗树就会变成一个单链表。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树需要满足以下两点性质:
1.它的左右子树都是AVL树。
2.每颗子树的高度差(平衡因子)不超过1或者小于-1。
那我们来看看一颗典型的AVL树

我们可以发现,左边高,它的高度差就是 -1,右边高,高度差就是1。两边一样高,那么高度差就是 0 。所以我们可以用平衡因子,来确定它的一个高度差。这样可以更好的控制它的高度。
为了更好的控制,我们决定使用三叉链。也就是新增一个parent节点指向自己的父亲,根节点的父亲为空指针。而我们还要一个平衡因子变量,来记录它子树的高度差。left和right指针指向左右两个孩子。这里我们采取key,value模型,所以用pair结构体,作为节点的值。
节点结构体代码:
template<class K,class V>
struct AVLNode
{
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
int _bf; //平衡因子,记录高度差
pair<K, V> _kv; //存储的一对数据
//构造函数
AVLNode(const pair<K,V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0) //新增节点肯定是叶子节点,所以高度默认0
,_kv(kv){}
};
只需要一个成员变量 root 来指向整颗树的根即可。构造函数就把root初始化为空。
template <class K, class V>
class AVLTree
{
typedef AVLNode<K, V> Node;
public:
AVLTree() :_root(nullptr){}
private:
Node* _root;//AVL树的根
};
AVL树的插入,我们要先找到可以插入的位置,然后插入。插入之后我们树的高度可能会发生变化,因此我们需要向上去更新平衡因子。所以我们插入后分多种情况,那就是插入后平衡因子为 0, 1/-1 ,2/-2的情况
插入后平衡因子为0的情况:
插入后平衡因子为0,那么说明在插入之前,这颗树的平衡因子是 -1 或者1,所以在插入之后这棵树的平衡因子变成了0,那么就意味着这棵树已经平衡了,这种情况就不需要做什么事情了。

插入后平衡因子为1/-1的情况:
如果插入和平衡因子为1或者-1,说明在插入之前这棵树的平衡因子是0。也就是说插入之前这颗树是平衡的,而插入之后,引发了高度变化。所以可能会造成不平衡的情况,这种情况我们需要一种往上更新平衡因子。在更新的过程可能会遇到平衡因子变成 2 或者 -2的情况,这时候就需要发生旋转。

平衡因子为2/-2的情况:
在插入的过程中,平衡因子可能会出现为2/-2的情况。当平衡因子为1/-1的时候,会一直往上更新,而在更新的过程中就会发生平衡因子为 2/-2的情况,这种情况我们就需要发生旋转。

假设我们插入一个10,那么10插入的位置应该在 8的右边。

那么此时 8 的左右子树高度发生了变化,因为插入了一个新节点。如果新增节点在右边,则 平衡因子自减,如果新增节点在右边,则平衡因子自增。所以新增后,平衡因子的变化是。

也就是,7所在的节点的子树不平衡了,因为它的平衡因子 > 1了。所以此时我们要发生旋转。而旋转有四种情况。
第一种情况,右边一边高
就如上图的情况,7所在节点的右子树右边一边高,所以这时候我们需要左旋转。那么我们假设7节点为parent。7的右节点8为subR,8的左节点为subRL,而7的父节点为parentparent

左单旋的步骤:
1.让parent的右节点连接subRL
2.subRL的父节点连接parent,如果为空则不连接
3.subR的左节点连接parent
4.parent的父节点连接subR
5.grandparent与subR连接
旋转流程图

最后,我们会发现parent和subR的平衡因子都变了0。也就意味着这颗子树达到了平衡。
左旋转代码:
// 左单旋
void RotateL(Node* parent)
{
Node* grandparent = parent->_parent;
Node* subR = parent->_right;
Node* subRL = subR->_left;
//父亲连接subRL
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
//subR的左边连接parent
subR->_left = parent;
parent->_parent = subR;
//grandparent连接subR
if (grandparent == nullptr)
{
//grandparent为空,说明parent一开始是根节点
_root = subR;
subR->_parent = nullptr;
}
else
{
//如果parent一开是grandparent的左子树,则grandparent的左子树连接subR
if (parent == grandparent->_left)
grandparent->_left = subR;
else
grandparent->_right = subR;
subR->_parent = grandparent;
}
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
第二种情况:左边一边高
左边一边高和右边一边高的思路完全一样。只不过方向反过来。。下面这棵树就是左边一边高,所以我们用右单旋进行调整。

右单旋的步骤:
1.让parent的左节点连接subLR
2.subLR的父节点连接parent,如果为空则不连接
3.subL的右节点连接parent
4.parent的父节点连接subL
5.grandparent与subL连接
右单旋旋转动图:

右单旋代码:
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* grandpraent = parent->_parent;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
parent->_parent = subL;
if (grandpraent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (grandpraent->_left == parent)
grandpraent->_left = subL;
else
grandpraent->_right = subL;
subL->_parent = grandpraent;
}
subL->_bf = parent->_bf = 0;
}
第三种情况:新节点插入较高左子树的右侧—左右
一边高是直线,曲线就是整棵树是左边高,但是它的孩子却在和它相反的一边。这就意味着这棵树是曲线的,所以单纯的左右旋转无法使它达到平衡。而旋转完后,我们还需要控制平衡因子,控制平衡因子又有三种情况。
情况1.当subL右节点的平衡因子为0时(也就是新增)

把subL左旋转后

随后这颗树就变成了左边一边高,我们在把parent右旋转。

这种情况下,新增节点,subL,parent的平衡因子都变成了0。
情况2.subL的右节点平衡因子为-1时

和之前一样,先把subL左旋转

再把parent右旋转

旋转后
subL的平衡因子为0
subLR的平衡因子为0
parent的平衡因子为1
情况3.subL的右节点平衡因子为1时

老规矩,把subL进行左单旋

再把parent右单旋

旋转后
subL的平衡因子为-1
subLR的平衡因子为0
parent的平衡因子为0
左右双旋代码:
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int lrbf = subLR->_bf;
//先左旋subL
RotateL(subL);
//右旋parent
RotateR(parent);
//保存LR的平衡因子
if (lrbf == 0)
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if (lrbf == -1)
{
subL->_bf = subLR->_bf = 0;
parent->_bf = 1;
}
else if (lrbf == 1)
{
subL->_bf = -1;
subLR->_bf = parent->_bf = 0;
}
else
assert(false);
}
第四种情况:新节点插入较高左子树的左侧—右左
这种情况和第三种情况相反,但也另外分了三种情况。
情况1.subRL就是新增节点

先右旋转subR

再左旋转parent

旋转完后,subRL和subR还有parent的平衡因子都为0。
情况2.当在subRL的平衡因子为-1时

先右单旋subR

再左单旋parent

最后的平衡因子分别为:
subRL 0
parent 0
subR 1
情况3.当在subRL的平衡因子为1时

老规矩,先右旋 subR

然后左旋转parent

最后的平衡因子分别是:
parent -1
subRL 0
subR 0
右左双旋代码:
//右左双旋
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int rlbf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (rlbf == 0)
{
subR->_bf = subRL->_bf = parent->_bf = 0;
}
else if (rlbf == -1)
{
subRL->_bf = parent->_bf = 0;
subR->_bf = 1;
}
else if (rlbf == 1)
{
parent->_bf = -1;
subR->_bf = subRL->_bf = 0;
}
else
assert(false);
}
bool IsBalance()
{
return _IsBalance(_root);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int LeftHeight = _Height(root->_left);
int RightHeight = _Height(root->_right);
if (root->_bf != RightHeight - LeftHeight)
{
cout << root->_kv._first <<"现在是平衡因子是:" << root->_bf << endl;
cout << root->_kv._first <<"平衡因子应该是:" << RightHeight - LeftHeight << endl;
}
return abs(LeftHeight - RightHeight) < 2;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
直接查找即可
//查找
bool Find(const K& k)
{
if (_root == nullptr)
return false;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > k)
cur = cur->_left;
else if (cur->_kv.first < k)
cur = cur->_right;
else
return true;
}
return false;
}
全部代码:
template<class K, class V>
struct AVLNode
{
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
int _bf; //平衡因子,记录高度差
pair<K, V> _kv; //存储的一对数据
//构造函数
AVLNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0) //新增节点肯定是叶子节点,所以高度默认0
, _kv(kv) {}
};
template <class K, class V>
class AVLTree
{
typedef AVLNode<K, V> Node;
public:
AVLTree() :_root(nullptr){}
bool insert(const pair<K,V>& kv)
{
//如果是第一次插入
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root; //新增节点的插入位置
Node* parent = nullptr; //插入位置的父亲节点
while (cur)
{
parent = cur;
if (cur->_kv.first > kv.first)//新插节点的值比当前节点小,往左子树找
cur = cur->_left;
else if (cur->_kv.first < kv.first)
cur = cur->_right;
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 == parent->_right) //插入节点在父亲节点的右边
parent->_bf++; //在右边++,在左边--
else
parent->_bf--;
if (parent->_bf == 0)
{
//平衡因子为0,说明这棵树之前的平衡因子是-1或者1,也就是说插入新节点后变平衡了
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//父亲的平衡因子是1或者-1,说明插入之前是0,也就是说插入之前是平衡的
//插入之后高度发生了变化,所以需要继续往上更新平衡因子
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//平衡因子为2或者-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)
RotateLR(parent);
else if (parent->_bf == 2 && cur->_bf == -1)
RotateRL (parent);
break;
}
else
assert(false);
}
}
// 左单旋
void RotateL(Node* parent)
{
Node* grandparent = parent->_parent;
Node* subR = parent->_right;
Node* subRL = subR->_left;
//父亲连接subRL
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
//subR的左边连接parent
subR->_left = parent;
parent->_parent = subR;
//grandparent连接subR
if (grandparent == nullptr)
{
//grandparent为空,说明parent一开始是根节点
_root = subR;
subR->_parent = nullptr;
}
else
{
//如果parent一开是grandparent的左子树,则grandparent的左子树连接subR
if (parent == grandparent->_left)
grandparent->_left = subR;
else
grandparent->_right = subR;
subR->_parent = grandparent;
}
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* grandpraent = parent->_parent;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
parent->_parent = subL;
if (grandpraent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (grandpraent->_left == parent)
grandpraent->_left = subL;
else
grandpraent->_right = subL;
subL->_parent = grandpraent;
}
subL->_bf = parent->_bf = 0;
}
//左右双旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int lrbf = subLR->_bf;
//先左旋subL
RotateL(subL);
//右旋parent
RotateR(parent);
//保存LR的平衡因子
if (lrbf == 0)
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if (lrbf == -1)
{
subL->_bf = subLR->_bf = 0;
parent->_bf = 1;
}
else if (lrbf == 1)
{
subL->_bf = -1;
subLR->_bf = parent->_bf = 0;
}
else
assert(false);
}
//右左双旋
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int rlbf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (rlbf == 0)
{
subR->_bf = subRL->_bf = parent->_bf = 0;
}
else if (rlbf == -1)
{
subRL->_bf = parent->_bf = 0;
subR->_bf = 1;
}
else if (rlbf == 1)
{
parent->_bf = -1;
subR->_bf = subRL->_bf = 0;
}
else
assert(false);
}
//查找
bool Find(const K& k)
{
if (_root == nullptr)
return false;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > k)
cur = cur->_left;
else if (cur->_kv.first < k)
cur = cur->_right;
else
return true;
}
return false;
}
bool IsBalance()
{
return _IsBalance(_root);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int LeftHeight = _Height(root->_left);
int RightHeight = _Height(root->_right);
if (root->_bf != RightHeight - LeftHeight)
{
cout << root->_kv.first << "现在是平衡因子是:" << root->_bf << endl;
cout << root->_kv.first << "平衡因子应该是:" << RightHeight - LeftHeight << endl;
}
return abs(LeftHeight - RightHeight) < 2;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
private:
Node* _root;//AVL树的根
};
💦💦关于AVL的删除,以后有机会会为大家更新。如果有写的不好或者有错误的地方,欢迎大家指出。后序会持续为大家更新红黑树,C/C++,数据结构以及linux操作系统相关的知识,如果不嫌弃可以点个收藏加关注。谢谢大佬们了!
🔥🔥你们的支持是我最大的动力,希望在往后的日子里,我们大家一起进步!!!🔥🔥
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度; 在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复