AVL树
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有N个结点,其高度可保持在 ,搜索时间复杂度O(log2N) 。
AVL树的实现是由二叉搜索树和平衡因子完成的。
AVL树结点定义如下:
template <class T>
struct AVLTreeNode {
AVLTreeNode* _pLeft;
AVLTreeNode* _pRight;
AVLTreeNode* _pParent;
T _data;
int _bf; //平衡因子
AVLTreeNode(const T& data) : _data(data)
, _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _bf(0) {}
};
AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入
过程可以分为两步:
- 按照二叉搜索树的方式插入新节点(注:要判断插入的结点是否为第一个结点)
// 找到二叉搜索树应该插入的位置
Node* pCur = _pRoot;
Node* pParent = nullptr;
while (pCur) {
pParent = pCur;
if (data < pCur->_data)
pCur = pCur->_pLeft;
else if (data > pCur->_data)
pCur = pCur->_pRight;
else
return false;
}
//插入新节点
pCur = new Node(data);
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
pCur->_pParent = pParent;
- 调整节点的平衡因子
pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
a.如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
b.如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可
此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
① 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功
② 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新
③ 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理
//更新pParent 的平衡因子
while (pParent) {
if (pCur == pParent->_pLeft)
--pParent->_bf;
else
++pParent->_bf;
if (0 == pParent->_bf) //父节点高度没有变,就插入成功了
return true;
else if (-1 == pParent->_bf || 1 == pParent->_bf) { //父亲结点的父节点平衡因子也要变的,这就是结点结构体要保存父节点的原因,再更改上面的要循环上去,就是在循环里的原因
pCur = pParent;
pParent = pCur->_pParent;
} else { //双亲结点已经不满足AVL的性质了,对以双亲的为根的二叉树进行旋转
if (2 == pParent->_bf) {
if (pCur->_bf == 1)
RotateL(pParent);
else
RotateRL(pParent);
}
else {
if (pCur->_bf == -1)
RotateR(pParent);
else
RotateLR(pParent);
}
//旋转之后就不需要再更新了,上面的高度还是那样
break;
}
}
旋转处理
1.pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
当pSubL的平衡因子为-1是,执行右单旋
//右单旋
void RotateR(Node* pParent) {
PNode pSubL = pParent->_pLeft;
PNode pSubLR = pSubL->_pRight;
pParent->_pLeft = pSubLR;
if (pSubLR)
pSubLR->_pParent = pParent;
pSubL->_pRight = pParent;
PNode pPParent = pParent->_pParent;
pParent->_pParent = pSubL;
//把pParent的父结点赋给pSubL的父结点
pSubL->_pParent = pPParent;
//判断pParent是不是根节点
if (NULL == pPParent) {
_pRoot = pSubL;
pSubL->_pParent = NULL;
} else {
if (pPParent->_pLeft == pParent)
pPParent->_pLeft = pSubL;
else
pPParent->_pRight = pSubL;
}
//更改平衡因子
pParent->_bf = pSubL->_bf = 0;
}
当pSubL的平衡因子为1时,执行左右双旋
//双旋:先左单旋在右单旋
void RotateLR(Node* pParent) {
PNode pSubL = pParent->_pLeft;
PNode pSubLR = pSubL->_pRight;
// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
int bf = pSubLR->_bf;
RotateL(pParent->_pLeft);
RotateR(pParent);
//更改平衡因子, 因为左单旋和右单旋pParent和pSubL清零
if (1 == bf)
pSubL->_bf = -1;
else if (-1 == bf) //不能else,还有不需要更新的情况
pParent->_bf = 1;
}
2.pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
当pSubR的平衡因子为1时,执行左单旋
当pSubR的平衡因子为-1时,执行右左双旋
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。