目录
一、AVL树的特点
在上一篇博客中说过,二叉搜索树是可能会退化的,这就导致查找的效率很低,而AVL树则是一种特殊的二叉搜索树,它的左右子树高度差绝对值不超过1,这样就可以避免二叉搜索树退化为单支树而造成效率低下。
AVL树的特点:左右子树都是AVL树;左右子树高度之差的绝对值不超过1。
AVL树的这种相对平衡是通过平衡因子和旋转控制的,平衡因子是结点的左子树高度减去右子树高度的值,这个值的绝对值是不超过1的,即可以为-1、0和1。
二、AVL树的插入
AVL树的插入过程比较复杂,会涉及到旋转,本文也是主要针对AVL树的插入过程,进行详细分析和讲解。
AVL树的插入与普通的二叉搜索树类似,需要从根结点开始遍历,并不断记录父结点的位置,找到要插入的位置,然后再判断是插入在左还是右,根据平衡因子的计算(右子树高度-左子树高度)很容易知道,若插入在左,则父结点的平衡因子-1,若插入在右,则平衡因子+1。插入之后,我们需要判断平衡因子是否符合规则(绝对值不超过1),分三种情况讨论。
1)插入后平衡因子为0,说明原来的平衡因子 + / - 1后为0,即原来的平衡因子为1或者-1,即一边高一边低,插入后左右子树高度相同。插入的结点是可能对祖先结点的平衡影子造成影响的,但在这种情况下是没有影响的,因为整个子树的高度是不变的,直接插入就可以解决问题。(例如下图中,在3的左侧插入一个结点,对于其父结点2来说平衡因子不变,仍然是1)
2)插入后平衡因子为1 / -1,说明原来的平衡因子为0,插入后整个子树的高度是增加1了的,例如在1的左侧插入一个结点,这种情况下是会影响祖先结点的,插入后2的平衡因子就变成了0,因此,在这种情况下,插入后需要向上遍历,改变祖先结点的平衡因子。
3)插入后平衡因子为2 / -2,说明原来的平衡因子为1或者-1,插入后加剧了“不平衡”,这时候已经不能满足AVL树的性质了,需要通过旋转来调整。
接下来主要针对第三种情况进行讲解。
我们先来看一下平衡因子变为2或者-2的情况:
说明:图中h是子树的高度(不包含新增结点),h>=0,图中的红色标记的数字则是插入结点后的平衡因子。
在AVL树的旋转平衡中有两种,单旋和双旋,在上图中情况一和情况三是单旋,情况二和情况四是双旋转,接下来逐一分析。
在分析旋转之前先看结点。
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data = T())
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _bf(0)
{}
AVLTreeNode<T>* _pLeft;
AVLTreeNode<T>* _pRight;
AVLTreeNode<T>* _pParent;
T _data;
int _bf; // 节点的平衡因子
};
1.单旋
1)右单旋
情况一是需要右旋的,当新增结点在a所在子树时,可以看到,10的左子树高度加一,影响了10的平衡因子,进而影响20的平衡因子,使得20需要旋转。旋转的本质就是降低高的那侧,从图中很容易知道,c中的所有结点都是比10大的,b中所有结点比20小(依据二叉搜索树的性质),因此可以将20以及c作为10的右子树,然后将10的右子树给20,作为20的左子树,这样平衡因子就符合要求了,且变化过程没有违背二叉搜索树的性质。
图示:
代码实现:
将平衡因子为-2的结点(图中为20)起名为pParent,它的左孩子(图中为10)为subL,左孩子的右子树为subLR,它的父结点为pParentParent。
实现细节:subL的右子树指向pParent,pParent的父结点变为subL,pParent的左孩子变为subLR,subLR的父结点变为pParent,由于这个可能是子树,上面还有其他结点,所以要将pParentParent与subL连接起来。
特别注意:subLR可能为空,要判断,否则直接访问其父结点会报错;注意更改平衡因子。
// 右单旋
void RotateR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
Node* pParentParent = pParent->_pParent;
pParent->_pLeft = subLR;
if (subLR)
subLR->_pParent = pParent;
subL->_pRight = pParent;
pParent->_pParent = subL;
//连接
if (pParentParent == nullptr)
{
_pRoot = subL;
subL->_pParent = nullptr;
}
else
{
if (pParentParent->_pLeft == pParent)
{
pParentParent->_pLeft = subL;
}
else
{
pParentParent->_pRight = subL;
}
subL->_pParent = pParentParent;
}
pParent->_bf = subL->_bf = 0;
}
2)左单旋
左单旋与右单旋非常类似,这里直接给出旋转草图。
代码实现:
// 左单旋
void RotateL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
Node* pParentParent = pParent->_pParent;
pParent->_pRight = subRL;
if(subRL)
subRL->_pParent = pParent;
subR->_pLeft = pParent;
pParent->_pParent = subR;
//连接上面的根
//pParent是根
if (pParentParent == nullptr)
{
_pRoot = subR;
subR->_pParent = nullptr;
}
else
{
//左
if (pParentParent->_pLeft == pParent)
pParentParent->_pLeft = subR;
else
pParentParent->_pRight = subR; //右
subR->_pParent = pParentParent;
}
pParent->_bf = subR->_bf = 0;
}
2.双旋
在情况二和情况四中,如果仅是单旋是解决不了问题的,需要双旋才可以。
例如,我们以情况二为例:
可以看到,如果只是按照前边的方法进行旋转,最后就是不断的循环,是不能解决问题的。
要解决这里的问题,需要进一步细分,将b所在子树拆开,分别考虑插入的结点的位置来旋转。
1)左右双旋
左右双旋是解决情况二的,但是在情况二中又要分三种情况讨论,因为不同情况下平衡因子的更新不同。
情况1:h>0,且插入的结点在subLR的左,即subLR的平衡因子为-1
情况2:h>0,且插入的结点在subLR的右,即subLR的平衡因子为1
情况3:h=0
由草图也可以清晰看到,三种情况下都是先对10进行左旋,然后再对15进行右旋,双旋可以复用前边的单旋函数进行实现,最后改一下平衡因子即可。
同前边的单旋一样,20是pParent,10是subL,先对subL进行左旋,再对pParent进行右旋。
需要注意的是平衡因子的更新。
代码:
// 左右双旋
void RotateLR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
int bf = subLR->_bf;
RotateL(pParent->_pLeft);
RotateR(pParent);
if (bf == 0)
{
subL->_bf = 0;
subLR->_bf = 0;
pParent->_bf = 0;
}
else if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
pParent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
subLR->_bf = 0;
pParent->_bf = 1;
}
else
assert(false);
}
2)右左双旋
右左双旋与左右双旋非常类似,这里就不再画草图了,只给出代码。
// 右左双旋
void RotateRL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
int bf = subRL->_bf;
RotateR(pParent->_pRight);
RotateL(pParent);
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
pParent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
pParent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
pParent->_bf = 0;
}
else
assert(false);
}
3.插入函数
bool Insert(const T& data)
{
//插入影响的是祖先
if (_pRoot == nullptr)
{
_pRoot = new Node(data);
return true;
}
Node* cur = _pRoot;
Node* parents = nullptr;
while (cur)
{
if (cur->_data > data)
{
parents = cur;
cur = cur->_pLeft;
}
else if (cur->_data < data)
{
parents = cur;
cur = cur->_pRight;
}
else
{
//相等
return false;
}
}
cur = new Node(data);
//判断插入在左还是右
if (parents->_data > data)
{
//左
parents->_pLeft = cur;
}
else
{
//右
parents->_pRight = cur;
}
cur->_pParent = parents;
//更新平衡因子
while (parents)
{
//在左边插入减1,右边加1(平衡因子 = 右子树高度-左子树高度)
if (cur == parents->_pLeft)
parents->_bf--;
else
parents->_bf++;
if (parents->_bf == 0)
{
//说明原来是1或者-1,一边高一边低,正好插入在低的那边
//不会影响上面的祖先,不用更新上面的
break;
}
else if (parents->_bf == 1 || parents->_bf == -1)
{
//说明原来是0,插入后会影响上面的祖先
cur = parents;
parents = parents->_pParent;
}
else if (parents->_bf == 2 || parents->_bf == -2)
{
//不平衡,需要旋转
//同号单纯一边高,单旋即可,异号双旋
if (parents->_bf == 2 && cur->_bf == 1)
RotateL(parents);
else if(parents->_bf == -2 && cur->_bf == -1)
RotateR(parents);
else if (parents->_bf == 2 && cur->_bf == -1)
//先右旋变成单纯一边高,再左旋
RotateRL(parents);
else
RotateLR(parents);
break;
}
else
{
//说明有问题了
assert(false);
}
}
return true;
}
三、AVL树完整代码 (旋转+插入)
// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
AVLTree()
: _pRoot(nullptr)
{}
// 在AVL树中插入值为data的节点
bool Insert(const T& data)
{
//插入影响的是祖先
if (_pRoot == nullptr)
{
_pRoot = new Node(data);
return true;
}
Node* cur = _pRoot;
Node* parents = nullptr;
while (cur)
{
if (cur->_data > data)
{
parents = cur;
cur = cur->_pLeft;
}
else if (cur->_data < data)
{
parents = cur;
cur = cur->_pRight;
}
else
{
//相等
return false;
}
}
cur = new Node(data);
//判断插入在左还是右
if (parents->_data > data)
{
//左
parents->_pLeft = cur;
}
else
{
//右
parents->_pRight = cur;
}
cur->_pParent = parents;
//更新平衡因子
while (parents)
{
//在左边插入减1,右边加1(平衡因子 = 右子树高度-左子树高度)
if (cur == parents->_pLeft)
parents->_bf--;
else
parents->_bf++;
if (parents->_bf == 0)
{
//说明原来是1或者-1,一边高一边低,正好插入在低的那边
//不会影响上面的祖先,不用更新上面的
break;
}
else if (parents->_bf == 1 || parents->_bf == -1)
{
//说明原来是0,插入后会影响上面的祖先
cur = parents;
parents = parents->_pParent;
}
else if (parents->_bf == 2 || parents->_bf == -2)
{
//不平衡,需要旋转
//同号单纯一边高,单旋即可,异号双旋
if (parents->_bf == 2 && cur->_bf == 1)
RotateL(parents);
else if(parents->_bf == -2 && cur->_bf == -1)
RotateR(parents);
else if (parents->_bf == 2 && cur->_bf == -1)
//先右旋变成单纯一边高,再左旋
RotateRL(parents);
else
RotateLR(parents);
break;
}
else
{
//说明有问题了
assert(false);
}
}
return true;
}
~AVLTree()
{
Destroy(_pRoot);
_pRoot = nullptr;
}
private:
// 右单旋
void RotateR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
Node* pParentParent = pParent->_pParent;
pParent->_pLeft = subLR;
if (subLR)
subLR->_pParent = pParent;
subL->_pRight = pParent;
pParent->_pParent = subL;
//连接
if (pParentParent == nullptr)
{
_pRoot = subL;
subL->_pParent = nullptr;
}
else
{
if (pParentParent->_pLeft == pParent)
{
pParentParent->_pLeft = subL;
}
else
{
pParentParent->_pRight = subL;
}
subL->_pParent = pParentParent;
}
pParent->_bf = subL->_bf = 0;
}
// 左单旋
void RotateL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
Node* pParentParent = pParent->_pParent;
pParent->_pRight = subRL;
if(subRL)
subRL->_pParent = pParent;
subR->_pLeft = pParent;
pParent->_pParent = subR;
//连接上面的根
//pParent是根
if (pParentParent == nullptr)
{
_pRoot = subR;
subR->_pParent = nullptr;
}
else
{
//左
if (pParentParent->_pLeft == pParent)
pParentParent->_pLeft = subR;
else
pParentParent->_pRight = subR; //右
subR->_pParent = pParentParent;
}
pParent->_bf = subR->_bf = 0;
}
// 右左双旋
void RotateRL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
int bf = subRL->_bf;
RotateR(pParent->_pRight);
RotateL(pParent);
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
pParent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
pParent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
pParent->_bf = 0;
}
else
assert(false);
}
// 左右双旋
void RotateLR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
int bf = subLR->_bf;
RotateL(pParent->_pLeft);
RotateR(pParent);
if (bf == 0)
{
subL->_bf = 0;
subLR->_bf = 0;
pParent->_bf = 0;
}
else if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
pParent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
subLR->_bf = 0;
pParent->_bf = 1;
}
else
assert(false);
}
void Destroy(Node* pRoot)
{
if (pRoot == nullptr)
return;
Destroy(pRoot->_pLeft);
Destroy(pRoot->_pRight);
delete pRoot;
}
private:
Node* _pRoot;
};