1.1 AVL树的概念

2.1AVL树节点的定义
//AVL树节点的定义
template <class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left; //左子树
AVLTreeNode<K, V>* _right; //右子树
AVLTreeNode<K, V>* _parent; //父节点
int _bf; //平衡因子
pair<K, V> _kv;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{}
};
注:这里的平衡因子的值是用右子树高度减左子树高度,也可以反过来;另外,平衡因子只是辅助作用,AVL树可以不用平衡因子这个成员
3.1AVL树的插入
bool Insert(const pair<K, V>& kv) //插入
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
// 先按照二叉搜索树的规则将节点插入到AVL树中
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else //(kv.first < parent->_kv.first)
{
parent->_left = cur;
}
cur->_parent = parent;
// 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否
//破坏了AVL树的平衡性
//更新bf信息
while (parent)
{
//
}
return true;
}
插入后,parent的平衡因子一定会被影响,插入前分为三种:1、0、-1
插入后的影响有以下这两种:
//更新bf信息
while (parent)
{
//插入节点时,其父节点的平衡因子必定受到影响
if (cur == parent->_left)
parent->_bf--;
else
parent->_bf++;
//......
}


3.如果parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进

//更新bf信息
while (parent)
{
//插入节点时,其父节点的平衡因子必定受到影响
if (cur == parent->_left)
parent->_bf--;
else
parent->_bf++;
//根据父节点的平衡因子,看祖先平衡是否被影响
if (parent->_bf == 0)
{
break; //bf 为0,插入并不会引起祖先平衡变化
}
else if (parent->_bf == 1 || parent->_bf == -1) //对于祖先来说,它的子树高度变化
//了,会影响平衡
{
cur = cur->_parent;
parent = parent->_parent; //往上继续更新
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转处理
break;
}
else //AVL树再插入之前有问题
{
assert(false);
}
}
return true;
}
4.1 AVL树的旋转
其实构成旋转条件的插入情况有很多种,可以根据子树高度,细分为多种插入情况,特别是子树高度很高时,情况非常多;因此,我们不应该过多关注节点插在一个什么样子的二叉树,应该关注高度变化。
4.1.1 新节点插入较高左子树的左侧——右单旋
例:

忽略二叉树的结构,只考虑高度,可以把二叉树抽象为这个样子。
可以观察到:“5”的左子树高度导致不平衡,具体为“3”的左子树插入新节点引起高度变化;
想要平衡,“3”这棵树的高度需要降低,那么就让“3”去作为整棵树的根节点;
此时,“3”的左子树的高度为h+1,想要右子树平衡一下,应该让“5”与其右子树作为“3”的右子树,
这样就凑出h+1的高度,最后为了保持二叉树,“3”的右子树变为“5”的左子树。

以上操作,条件是父节点平衡因子为-2,左子树为-1;
需要被操作的节点是“5”——parent指针;
“3”节点——用subL指针来操作,subL = parent->_left;
“3”节点的左子树——用subRL来操作,subLR = subL->_right; 由于左子树可能为空,所以这个指针也可能为空。
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppnode = parent->_parent;//需要记录一下父节点的父节点,为了更新双亲
subL->_right = parent;
parent->_parent = subL;
if (_root == parent) //父节点是整棵树的根节点
{
_root = subL;
subL->_parent = nullptr;
}
else //祖先节点与subL的连接
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
//更新平衡因子
parent->_bf = 0;
subL->_bf = 0;
}
4.1.2 新节点插入较高右子树的右侧——左单旋
例:

可以观察到:“5”的右子树高度导致不平衡,具体为“3”的右子树插入新节点引起高度变化;
想要平衡,“3”这棵树的高度需要降低,那么就让“3”去作为整棵树的根节点;
此时,“3”的右边子树的高度为h+1,想要右子树平衡一下,应该让“5”与其右边子树作为“3”的左子树,
这样就凑出h+1的高度,最后为了保持二叉树,“3”的左子树变为“5”的右子树。

以上操作,条件是父节点平衡因子为-2,左子树为-1;
需要被操作的节点是“5”——parent指针;
“3”节点——用subR指针来操作,subR = parent->_right;
“3”节点的右子树——用subRL来操作,subRL = subL->_left; 由于左子树可能为空,所以这个指针也可能为空。
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
subR->_bf = 0;
parent->_bf = 0;
}
4.1.3 新节点插入较高左子树的右侧——先左旋再右旋
例:

这种情况,直接开转得不到什么结果,应该将“h+1”的树拆成左右子树将双旋变成单旋后再旋转:

可以观察到:“4”节点左子树插入了一个节点导致整棵树不平衡,“4”这棵树相当于“3”的右子树是较高树的情况,所以对“3”进行左旋;

此时,“3”这棵树相当于 本来是“4”的左子树插入了新节点导致不平衡,应该对“4”进行右单旋。

实现代码时,左右旋转只需要复用前面的代码就行,可是双旋完后的平衡因子调节比较麻烦;
上面例子是新节点插在“4”的左边,最后 需要更新平衡因子的节点是“3”,“4”,“5”;
平衡后的值分别为:0,0,1;
当然也有新节点插在“4”的右边的情况,具体是看“4”节点的平衡因子判断插入情况:
1.平衡因子为-1(在左边插入),旋转后需要更新的节点的值为:0、0、1;
2.平衡因子为1 (在右边插入),旋转后需要更新的节点的值为:-1,0,0;
3.平衡因子为0,(特殊情况), 旋转后需要更新的节点的值为:0,0,0;
特殊情况:

判断条件是父节点平衡因子为-2,左子树平衡因子为1
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf; //判断平衡因子
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0;
if (bf == 1)
{
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0)
{
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
4.1.4 新节点插入较高右子树的左侧:先右单旋再左单旋

如法炮制: “4”节点右子树插入了一个节点导致整棵树不平衡,“4”这棵树相当于“3”的左子树是较高树的情况,所以对“3”进行右旋;

此时,“3”这棵树相当于 本来是“4”的右子树插入了新节点导致不平衡,应该对“4”进行左单旋。

实现代码时,左右旋转只需要复用前面的代码就行,可是双旋完后的平衡因子调节比较麻烦;
上面例子是新节点插在“4”的右边,最后 需要更新平衡因子的节点是“3”,“4”,“5”;
平衡后的值分别为:0,0,-1;
当然也有新节点插在“4”的右边的情况,具体是看“4”节点的平衡因子判断插入情况:
1.平衡因子为-1(在左边插入),旋转后需要更新的节点的值为:1、0、0;
2.平衡因子为1 (在右边插入),旋转后需要更新的节点的值为:0、0、-1;
3.平衡因子为0,(特殊情况), 旋转后需要更新的节点的值为:0,0,0;
特殊情况:

判断条件是父节点平衡因子为2,左子树平衡因子为-1
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
subRL->_bf = 0;
if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
}
}
完整代码:
//AVL树节点的定义
template <class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; //平衡因子
pair<K, V> _kv;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{}
};
template <class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
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 (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else //(kv.first < parent->_kv.first)
{
parent->_left = cur;
}
cur->_parent = parent;
//更新bf信息
while (parent)
{
//插入节点时,其父节点的平衡因子必定受到影响
if (cur == parent->_left)
parent->_bf--;
else
parent->_bf++;
//根据父节点的平衡因子,看祖先平衡是否被影响
if (parent->_bf == 0)
{
break; //bf 为0,插入并不会引起祖先平衡变化
}
else if (parent->_bf == 1 || parent->_bf == -1) //对于祖先来说,它的子树高度变
//化了,会影响平衡
{
cur = 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 //(parent->_bf == 2 && cur->_bf == -1) 右左双旋
{
RotateRL(parent);
}
break;
}
else //AVL树再插入之前有问题
{
assert(false);
}
}
return true;
}
private:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)
subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
subR->_bf = 0;
parent->_bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
parent->_bf = 0;
subL->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0;
if (bf == 1)
{
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0)
{
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
subRL->_bf = 0;
if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
}
}
Node* _root = nullptr;
};
对于AVL树的删除,由于过于复杂,不详细说明;如果以学习为目的去了解AVL树,了解到插入已经足够,没必要完完全全复刻出来!如果你有兴趣或者需要可以自行了解。
5.1 AVL树的性能
感谢浏览!!
本文围绕AVL树展开,介绍了其概念、节点定义。重点讲解了AVL树的插入操作及插入后对平衡因子的影响,还详细阐述了四种旋转情况,包括右单旋、左单旋、先左旋再右旋、先右单旋再左单旋,最后提及AVL树性能,未详述删除操作。
2129

被折叠的 条评论
为什么被折叠?



