AVL树
一.什么是AVL树
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树的性质:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
- 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n)
二.AVL树的结构
1.AVL树的节点结构
cpp
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;
pair<K, V> _kv;
int _bf;
AVLTreeNode(const pair<K,V>& kv)
:_left(nullptr),
_right(nullptr),
_parent(nullptr),
_kv(kv),
_bf(0)
{}
};
这里的数据存储使用pair类型的数据对,将key索引与对应数据value存储,增加了_parent保存父亲节点,增加了_bf平衡因子来衡量左右子树是否平衡。
2.插入函数
cpp
bool insert(const pair<K, V>& kv)
这里插入方式与搜索二叉树一致,只是在后面增加了平衡调整的步骤
cpp
//如果根为空
if (_root == nullptr)
{
_root = new node(kv);
return true;
}
//找到目标值
node* parent = nullptr;
node* cur = _root;
while (cur)
{
//key < cur
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
//查重
return false;
}
}
//插入目标值
cur = new node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
调节平衡因子
cpp
while(parent)
{
//向上更新
}
如果插在左子树,则平衡因子--,否则++
cpp
if (parent->_left == cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
判断平衡因子是否正常倘若>=2则需要旋转
cpp
if (parent->_bf == 0)
{
break;
}
else if(parent->_bf == -1 || parent->_bf == 1)
{
//向上调整
cur = parent;
parent = parent->_parent;
}
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)
{
rotaterl(parent);
}
else
{
rotatelr(parent);
}
break;
}
else
{
//大问题
assert(false);
}
3.旋转调整
AVL树的旋转分为四种,左单旋,右单旋,左右双旋和右左双旋。
1.右单旋
cpp
void rotater(node* parent)
当插入new节点时,60节点会左右失衡,我们称60为parent,30为subl,则平衡因子会分别变为-2,-1,成为左左高模型,此时使用右单旋。
由于搜索树特性:30<b<60<c,所以可以将右方降下去,从而达到平衡。
-
传入旋转点parent
-
记录关键节点,防止旋转后丢失
cpp
node* subl = parent->_left;
node* sublr = subl->_right;
- 将parent的左指向sublr,若sublr为空节点,则不链接父亲节点否则链接
- 将subl的右指向parent,同时链接父亲节点
cpp
parent->_left = sublr;
if (sublr)
sublr->_parent = parent;
subl->_right = parent;
parent->_parent = subl;
//保存父节点
node* ppnode = parent->_parent;
subl->_parent = ppnode;
- 考虑parent是否为根,是否链接parent的父亲节点
cpp
//考虑parent是否为根
if (ppnode == nullptr)
{
_root = subl;
subl->_parent = nullptr;
}
else
{
subl->_parent = ppnode;
if (ppnode->_left == parent)
{
ppnode->_left = subl;
}
else
{
ppnode->_right = subl;
}
}
- 修改平衡因子
cpp
parent->_bf = subl->_bf = 0;
2.左单旋
大体思路与右单旋相同
cpp
void rotatel(node* parent)
{
node* subr = parent->_right;
node* subrl = subr->_left;
parent->_right = subrl;
if (subrl)
subrl->_parent = parent;
subr->_left = parent;
//保存父节点
node* ppnode = parent->_parent;
parent->_parent = subr;
subr->_parent = ppnode;
//考虑parent是否为根
if (ppnode == nullptr)
{
_root = subr;
subr->_parent = nullptr;
}
else
{
subr->_parent = ppnode;
if (ppnode->_left == parent)
{
ppnode->_left = subr;
}
else
{
ppnode->_right = subr;
}
}
//调节平衡因子
parent->_bf = subr->_bf = 0;
}
3.左右双旋
cpp
void rotatelr(node* parent)
通常双旋有三种情况,在b里插入新节点,在c里插入新节点,或者b,c都为空
记录sul的平衡因子,方便判断是哪种情况
cpp
node* subl = parent->_left;
node* sublr = subl->_right;
int bf = subl->_bf;
rotatel(subl);
rotater(parent);
cpp
if (bf == -1)
{
subl->_bf = 0;
parent = 1;
}
else if (bf == 1)
{
subl->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
subl->_bf = 0;
parent->_bf = 0;
}
sublr->_bf = 0;
每一种情况平衡因子不同,所以需要画图分别讨论
** 4.右左双旋**
cpp
void rotaterl(node* parent)
{
node* subr = parent->_right;
node* subrl = subr->_left;
int bf = subr->_bf;
rotater(subr);
rotater(parent);
if (bf == -1)
{
subr->_bf = 1;
parent = 0;
}
else if (bf == 1)
{
subr->_bf = 0;
parent->_bf = -1;
}
else if (bf == 0)
{
subr->_bf = 0;
parent->_bf = 0;
}
subrl->_bf = 0;
}
三.平衡测试
判断是否平衡
cpp
bool _IsBalance(node* root)
{
if (root == nullptr)
{
return true;
}
int leftheight = _height(root->_left);
int rightheight = _height(root->_right);
if (abs(leftheight - rightheight) >= 2)
{
return false;
}
//判断节点平衡因子是否正确
if (root->_bf != (rightheight - leftheight))
{
return false;
}
return _IsBalance(root->_left) && _IsBalance(root->_right);
}