目录
1. AVL树的概念
一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是 AVL 树。如果它有 n 个结点,其高度可保持在O(log_2 n) ,搜索时间复杂度 O(log_2 n) 。
2. AVL树节点的定义
template<class K,class V>
struct AVLTreeNode
{
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)//平衡因子默认是0
{}
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;//左孩子
AVLTreeNode<K, V>* _right;//右孩子
AVLTreeNode<K, V>* _parent;//父节点
int _bf;//平衡因子
};
template<class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
private:
Node* _root = nullptr;
};
3. AVL树的插入
//插入,插入的操作还是跟二叉搜索树的插入基本一致,
//需要注意的是平衡因子的更新,和更新后的旋转
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//更新平衡因子
while (parent)
{
//插入在左,parent的平衡因子--
//插入在右,parent的平衡因子++
if (parent->_left == cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
//是否继续更新,要看子树高度的变化
//1.parent的平衡因子是0,说明插入之前,Parent的平衡因子为正负1,
// 插入后被调整成0,此时满足AVL树的性质,插入成功
//2.,parent的平衡因子是-1或1,说明插入之前,parent的平衡因子是0,
// 插入后被调整为正负1,此时子树高度发生变化,需要继续向上更新平衡因子
//3.parent的平衡因子是正负2,说明插入之前,Parent的平衡因子为正负1,
// 说明插入之后严重影响平衡,需要就地处理:旋转
//
// 旋转之后:
// 1、让这颗子树左右高度不超过1
// 2、旋转过程中继续保持他是搜索树
// 3、更新调整孩子节点的平衡因子
// 4、让这颗子树的高度跟插入前保持一致
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)
{
RotateL(parent);
}
//向右单旋
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
//左右双旋
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
//右左双旋
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);//防止自己写错出现特殊情况
}
break;
}
else
{
assert(false);//防止自己写错出现特殊情况
}
}
return true;
}
4. AVL树的旋转
4.1 向左单旋
因为插入的情况有很多种,我就不一一画图了,只画其中一种。
新增节点是80的左或右才会引发左单旋。
插入之前AVL树是平衡的,插入之后AVL树就不合符要求了。插入后通过更新平衡因子发现,parent的平衡因子变成了2,而subR的平衡因子是1,说明该子树需要以parent为轴点向左单旋。
左旋操作:把subR(60)的左边给parent(30)的右边,如果subRl(40)不是空节点(可能为空,比如简单版),那么就让subRl(40)的父指针指向parent(30)。再让parent(30)变成subR(60)的左边,让parent(30)的父指针指向subR(60)。如果parent节点(30)就是根,那么现在让subR(60)节点变成根。如果 parent节点(30)不是根,而是一个子树,那么就定义一个pNode指针,pNode是parent节点的父亲,如果parent是pNode的左树,就让subR(60)变成pNode的左树,如果parent是pNode的右树,就让subR(60)变成pNode的右树,旋转完成后更新平衡因子。从下图中可以看到,树变平衡了,而此次旋转完成后需要更新平衡因子的只有parent(30)和subR(60)节点,所以将他们两的的平衡因子更新为0即可。
复杂版:
简单版:
//向左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRl = subR->_left;
parent->_right = subRl;
//subRl不为空就让父指针指向parent节点
if (subRl)
subRl->_parent = parent;
Node* pNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (pNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (pNode->_left == parent)
{
pNode->_left = subR;
}
else
{
pNode->_right = subR;
}
subR->_parent = pNode;
}
//更新旋转后的平衡因子
parent->_bf = subR->_bf = 0;
}
4.2 向右单旋
右单旋具体操作参考左单旋,新增节点是30的左或右才会引发右单旋。
右旋操作:把60的右给80的左,80变成60的右边,链接父指针,如果之前80是根,那么现在60是根。如果不是根,那么让pNode原来指向80,现在指向60,更新60和80的平衡因子。
复杂版:
简单版:
//向右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLr = subL->_right;
parent->_left = subLr;
if (subLr)
subLr->_parent = parent;
Node* pNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (pNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (pNode->_left == parent)
{
pNode->_left = subL;
}
else
{
pNode->_right = subL;
}
subL->_parent = pNode;
}
parent->_bf = subL->_bf = 0;
}
4.3 左右双旋
左右双旋:这里采用复用的方式进行左右双旋,即先以30为轴点左单旋,再以80为轴点右单旋,旋转完成后,通过判断新增节点 是subLr的左,还是右,还是自己来进行平衡因子的更新。这里有三种情况:1.新增节点是subLr的左,此时subLr的bf为-1,旋转完成后,parent的bf为1,subL的bf和subLr的bf都为0;2.新增节点是subLr的右,此时subLr的bf为1,旋转完成后,parent的bf为0,subL的bf为-1,subLr的bf为0;3.subLr自己就是新增节点,此时subLr的bf为0,旋转完成后,parent的bf,subL的bf和subLr的bf都为0(如简单版所示)。
复杂版:
简单版:
//左右双旋:先向左单旋,再向右单旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLr = subL->_right;
int bf = subLr->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1)//subLr左子树新增
{
parent->_bf = 1;
subL->_bf = 0;
subLr->_bf = 0;
}
else if (bf == 1)//subLr右子树新增
{
parent->_bf = 0;
subL->_bf = -1;
subLr->_bf = 0;
}
else if (bf == 0)//subLr自己就是新增
{
parent->_bf = 0;
subL->_bf = 0;
subLr->_bf = 0;
}
else
{
assert(false);
}
}
4.4 右左双旋
右左双旋具体操作参考左右双旋。
平衡因子的更新可不是复制下来那么简单,要画图去看。
复杂版:
简单版:
//右左双旋:先向右单旋,再向左单旋
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 = 0;
subR->_bf = 1;
subRl->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRl->_bf = 0;
}
else if(bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRl->_bf = 0;
}
else
{
assert(false);
}
}
5. AVL树的验证
//中序遍历
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;;
_InOrder(root->_right);
}
//高度
int Height(Node* root)
{
if (root == nullptr)
return 0;
int lh = Height(root->_left);
int rh = Height(root->_right);
return lh > rh ? lh + 1 : rh + 1;
}
//判断AVL树是否正常
bool IsBalance()
{
return _IsBalance(_root);
}
bool _IsBalance(Node* root)
{
//空树也是AVL树
if (root == nullptr)
{
return true;
}
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
//这里可以很方便的帮助我们看到哪个节点的平衡因子异常了
if (rightHeight - leftHeight != root->_bf)
{
cout<< root->_kv.first <<"平衡因子异常" << endl;
}
//这里求的是绝对值,所以算出来的值只要小于2,
//根和每个子树同样满足条件,才认为这个AVL树是正常的
return abs(leftHeight - rightHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
测试:
6. AVL树的删除
7. AVL树的性能
完整代码:AVLTree/AVLTree/AVLTree.h · 晚风不及你的笑/作业库 - 码云 - 开源中国 (gitee.com)