目录
一、概念
AVL树来源于二叉搜索树。当二叉搜索树是以下单支的情况下,搜索效率退化为O(n)。为了解决这个问题,我们通过一些手段将二叉搜索树左右平衡(每一个节点的左右子树高度差不大于1),这样平衡的二叉搜索树就是AVL树。
为了更好地实现AVL树,我们引入平衡因子(balance factor)的概念,简写为bf。每个节点都有平衡因子,并且值为该节点右子树高度减左子树高度。AVL树的平衡因子绝对值不能大于1。
二、实现
2.1 节点
除了正常树中的记录左右节点的地址、数据外,还需要记录父亲节点的地址和平衡因子的记录。根节点的父亲节点为空。
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; // 节点的平衡因子
};
2.2 树
成员函数中主要难点是插入函数。私有成员中的左右旋函数就是对不平衡二叉搜索树平衡的手段。
// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
AVLTree()
: _pRoot(nullptr)
{}
// 在AVL树中插入值为data的节点
bool Insert(const T& data);
// AVL树的验证
bool IsAVLTree()
{
return _IsAVLTree(_pRoot);
}
private:
// 根据AVL树的概念验证pRoot是否为有效的AVL树
bool _IsAVLTree(Node* pRoot);
size_t _Height(Node* pRoot);
// 右单旋
void RotateR(Node* pParent);
// 左单旋
void RotateL(Node* pParent);
// 右左双旋
void RotateRL(Node* pParent);
// 左右双旋
void RotateLR(Node* pParent);
private:
Node* _pRoot;
};
2.3 插入
插入的整个过程:1.找到插入节点并直接插入。2.由插入节点倒退回根节点对节点的平衡因子进行更新。3.根据平衡因子的更新情况,决定是否要对子树进行旋转平衡。
平衡因子更新情况:1.bf = 0,该节点左右子树高度变为相同,以该节点为根节点的子树高度不变,于是不需要再往上更新了。2. bf = 1 || bf = -1,以该节点为根节点的子树高度加一,需要继续往上更新,若一直更新到根节点就停止。3.bf = 2 || bf = -2 ,二叉树已经不平衡了,需要旋转操作改变结构来平衡以该节点为根节点的子树。
// 在AVL树中插入值为data的节点
bool Insert(const T& data)
{
if (_pRoot == nullptr)
{
_pRoot = new Node(data);
}
else
{
//找到插入节点,并插入
Node* cur = _pRoot;
Node* parent = nullptr;
while (cur)
{
if (data < cur->_data)
{
parent = cur;
cur = cur->_pLeft;
}
else if (data > cur->_data)
{
parent = cur;
cur = cur->_pRight;
}
else
return false;
}
cur = new Node(data);
if(parent->_data > cur->_data)
parent->_pLeft = cur;
else
parent->_pRight = cur;
cur->_pParent = parent;
while (parent)
{
//更新平衡因子
if (parent->_pLeft == cur)
parent->_bf--;
else
parent->_bf++;
//检测平衡因子
if (parent->_bf == 0)
break;
else if (parent->_bf == 1 || parent->_bf == -1)
{
//继续向上更新
cur = parent;
parent = parent->_pParent;
}
else if (parent->_bf < -1 || parent->_bf > 1)
{
//出现平衡因子绝对值大于1,需要旋转平衡
if (parent->_bf == -2 && cur->_bf == -1)
RotateR(parent);
if (parent->_bf == 2 && cur->_bf == 1)
RotateL(parent);
if (parent->_bf == -2 && cur->_bf == 1)
RotateLR(parent);
if (parent->_bf == 2 && cur->_bf == -1)
RotateRL(parent);
break;
}
else // 插入之前平衡因子就有问题
assert(false);
}
}
}
2.4 旋转
2.4.1 左旋
需要左旋的情况如下,由于2节点右子树高度增加,导致1节点bf=2>1,二叉树失衡。于是我们对值为1的节点进行左旋,具体做法就是,将1节点的右子树指针指向2节点的左节点,再让2节点的左子树指针指向1节点。旋转完成后1、2 节点的bf都为0。
以下是链接过程的代码实现,需要注意的是,每两个节点之间是有两条链的,在对父亲节点的链接前要判断一下,自身节点是不为空的。
// 左单旋
void RotateL(Node* pParent)
{
Node* childR = pParent->_pRight;
Node* childRL = childR->_pLeft;
Node* ppParent = pParent->_pParent;
pParent->_pRight = childRL;
if (childRL)
childRL->_pParent = pParent;
childR->_pLeft = pParent;
pParent->_pParent = childR;
if (_pRoot == pParent)
{
_pRoot = childR;
childR->_pParent = nullptr;
}
else
{
if (ppParent->_pLeft == pParent)
ppParent->_pLeft = childR;
else
ppParent->_pRight = childR;
childR->_pParent = ppParent;
}
childR->_bf = pParent->_bf = 0;
}
2.4.2 右旋
与左旋是相反的情况。2节点bf = -2 < -1,将2节点右旋。2节点的左指针指向1节点的右孩子,1节点的右指针指向2节点。并完成相应父节点的链接。
// 右单旋
void RotateR(Node* pParent)
{
Node* childL = pParent->_pLeft;
Node* childLR = childL->_pRight;
Node* ppParent = pParent->_pParent;
pParent->_pLeft = childLR;
if (childLR)
childLR->_pParent = pParent;
childL->_pRight = pParent;
pParent->_pParent = childL;
if (pParent == _pRoot)
{
_pRoot = childL;
_pRoot->_pParent = nullptr;
}
else
{
if (ppParent->_pLeft == pParent)
ppParent->_pLeft = childL;
else
ppParent->_pRight = childL;
childL->_pParent = ppParent;
}
childL->_bf = pParent->_bf = 0;
}
2.4.3 右左双旋
由于2节点子树的高度增加导致1节点的右子树高度增加,1节点bf>1失衡。这时先对3节点右旋,形成下图右。下图右不就是以上需要左旋的情况吗,于是我们在对1节点进行左旋即可得到平衡二叉树。
对1节点进行左旋。
代码实现只需要对左右旋函数进行复用即可,需要注意的是平衡因子的更新。对最下面的节点(上图中2节点)的平衡因子进行判断。如果其值为-1则插入在左边,那么最终右边的3节点平衡因子不为0,应更新为1。同理可知值为1和值为0下的平衡因子更新情况。
// 右左双旋
void RotateRL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
int bf = subRL->_bf;
RotateR(pParent->_pRight);
RotateL(pParent);
if (bf == 1)
{
pParent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
pParent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
pParent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
assert(false);
}
2.4.4 左右双旋
逻辑完全与右左双旋相同,把左右调换一下即可。
// 左右双旋
void RotateLR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
int bf = subLR->_bf;
RotateL(pParent->_pLeft);
RotateR(pParent);
if (bf == 1)
{
pParent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == -1)
{
pParent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 0)
{
pParent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
assert(false);
}
2.5 验证
验证一颗AVLTree要从根节点开始,不断往下去检查左右子树的高度差是否小于2。
bool _IsAVLTree(Node* pRoot)
{
if (pRoot == nullptr)
return true;
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);
return abs(rightHeight - leftHeight) < 2
&& _IsAVLTree(pRoot->_pLeft)&& _IsAVLTree(pRoot->_pRight);
}
size_t _Height(Node* pRoot)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}