AVL树
代码
高度平衡二叉搜索树
- 引入
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当
于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年
发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之
差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。AVL树用三个人名首字母命名的。
-
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
-
它的左右子树都是AVL树
-
左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
- 不一定需要平衡因子,使用平衡因子是一种控制实现方式
-
非常接近满二叉树,插入10亿个节点将近30层左右.
模拟实现
Insert重点
在SB树的基础之上如何保证接近平衡呢>-平衡因子.一个节点的平衡因子受到他的左右子树的影响。
- 插入到节点的右边,该节点的平衡因子需要++,左边就–.
- 新增节点只会影响他的祖先节点的平衡因子.
控制平衡:
-
更新平衡因子,新增节点到根节点的祖先路径
-
出现异常平衡因子就需要旋转平衡树
-
出现一下五种情况:
- 更新以后,parent->bf == 0,更新结束。说明更新前parent->bf是1或者-1,现在变成0,说明填上了矮的那边,parent所处的子树高度不变也就不会对上一层进行影响。
- 更新以后,parent->bf==1/-1,继续向上更新,更新前parent->bf=0,现在变成1、-1,我有一遍子树变高了
parent所在的子树高度变了,需要继续向上更新.- 更新以后,parent->bf=2或者-2,parent已经不平衡了,需要旋转处理。根据平衡因子的正负来区别类型。
简单情况推演:
旋转->平衡
右单旋
- 右单旋引入
右单旋(左腿长)
保持搜索树的规则
控制平衡
- 右单旋抽象图
- 右单旋具象图
- 右单旋代码理解
void RotateR(Node* parent)
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
//进行旋转维护三叉连
parent->_left = sublr;
if (sublr)
sublr->_parent = parent;
Node* pparent = parent->_parent;
subl->_right = parent;
parent->_parent = subl;
if (parent == _root)
{
_root = subl;
_root->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
pparent->_left = subl;
else
pparent->_right = subl;
subl->_parent = pparent;
}
subl->_parent = parent->_bf = 0;
}
- 连接时要注意是否是空节点,以及一定要保证三叉连。
左单旋
右边腿长
void RotateL(Node* parent)
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
Node* pparent = parent->_parent;
parent->_parent = subr;
subr->_left = parent;
parent->_right = subrl;
if (subrl)
subrl->_parent = parent;
if (parent == _root)
{
_root = subr;
subr->_parent == nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subr;
}
else
{
pparent->_right = subr;
}
subr->_parent = pparent;
}
subr->_bf = parent->_bf = 0;
}
左右双旋
-
如何验证AVL树是没有问题的?高度,每一颗子树都得检查一遍是不是AVL树的形状。
IsBalance()
-
平衡因子是否都更新正确?树构建完成之后再进行插入节点。
双旋平衡因子更新错误
右左双旋
双旋时平衡因子的更新是由两次单旋完成的,在这里出现的问题,在未是平衡树的时候就将平衡因子更新为0了。
-
如何识别是那种情况?
可以通过60 的平衡因子识别三种情况,所以在两次旋转之前要将三个特殊节点保存
void RotateRL(Node* parent)
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
int bf = subrl->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
parent->_bf = -1;
}
else if (bf == -1)
{
subr->_bf = 1;
}
else if (bf == 0)
{
//
}
else
{
assert(false);
}
}
左右双旋
对三种情况特判来更新平衡因子即可.
void RotateLR(Node* parent)
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
int bf = sublr->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1)
{
parent->_bf = 1;
}
else if (bf == 1)
{
subl->_bf = -1;
}
else if (bf == 0)
{
}
else
{
assert(false);
}
}
所以要在双旋的函数中,在单旋操作对于平衡因子更新之后,再进行对于平衡因子特殊情况的自我更新。对于值的更新不明白的自己画个图就明白了,-1 1
0都是确定的,因为只有出发这些情况才会满足。性能接近logN.
erase了解
首先按照搜索树的规则删除,然后更新平衡因子,如果有需要再进行旋转.
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证
查询时高效的时间复杂度,即 logN.但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,