一.AVL树的概念
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树
- 左右子树都是AVL树
- 左右子树高度之差(平衡因子)的绝对值不超过1
例如:
上面这棵树不是一个AVL树,因为节点16的平衡因子为2,超过了1,此时为了让它变成AVL树,则要进行旋转
2.AVL树节点的定义
template<class K,class V>
struct AVLTreeNode
{
AVLTreeNode<K,V>* _left; //左孩子节点
AVLTreeNode<K,V>* _right; //右孩子节点
AVLTreeNode<K,V>* _parent; //双亲节点
int _bf; //平衡因子
std::pair<K,V> _kv;
AVLTreeNode(const std::pair<K,V>& kv)
:_left=nullptr
,_right=nullptr
,_parent=nullptr
,_bf(0)
,_kv(kv)
{}
}
3.AVL树的插入
- 首先按照二叉搜索树的方法找到新节点的插入位置(如果元素不存在则插入,存在则返回false)
- 新节点插入后,AVL树的平衡可能会被破坏,所以此时要进行更新平衡因子(如果新节点插在双亲结点的左侧,则双亲结点的平衡因子–,否则平衡因子++)
- 如果双亲节点的平衡因子变为1或-1,则证明原来的平衡因子为0,此时子树有一侧高度增加,因此要继续向上进行更新平衡因子知道平衡因子为0
- 如果双亲节点的平衡因子变为2或-2,则证明该树已经不满足AVL树性质,因此要进行旋转(具体方法在下一篇)
4.AVL树的插入实现
bool Insert(const std::pair<K, V>& kv)
{
if (_root == nullptr) //如果树为空,则直接插入新节点
{
_root = new Node(kv);
_root->_bf = 0;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > kv.first) //如果新节点k值小于cur,则向左子树继续查找
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first<kv.first) //如果新节点k值大于cur,则向右子树继续查找
{
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)
{
if (cur == parent->_left) //如果插入在左子树,则双亲的平衡因子-1
{
parent->_bf--;
}
else //如果插入在右子树,则双亲的平衡因子+1
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break; //双亲的平衡因子为0,不再更新
}
else if (abs(parent->_bf) == 1)
{
//2.高度变了,继续更新
cur = parent;
parent = parent->_parent;
}
else if (abs(parent->_bf) == 2)
{
//不平衡旋转
if (parent->_bf == 2)
{
if (cur->_bf == -1)
RotateRL(parent); //右左双旋
else if (cur->_bf == 1)
RotateL(parent); //左单旋
}
else if (parent->_bf==-2)
{
if (cur->_bf == 1)
RotateLR(parent); //左右双旋
else if (cur->_bf == -1)
RotateR(parent); //右单旋
}
break;
}
else
{
assert(false);
}
}
return true;
}
5.AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,要求每个节点的左右子树高度差不超过1,可以保证查询时高效的时间复杂度,即log2(N);但是如果对结构进行修改操作,性能就会很低下。由于插入后也要维护平衡,则要通过大量旋转来维护。