前言
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当 于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之 差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,这就是AVL树。
一、AVL树的概念
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
1.它的左右子树都是AVL树。
2.左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)。
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持O(log2n),搜索时间复杂度O(log2n)。
二、控制二叉搜索树的子树高度差
1.AVL树节点的定义
代码如下(示例):
template<class K,class V>
struct AVLTNode
{
typedef AVLTNode<K,V> Node;
AVLTNode(const pair<K,V>& kv)
:_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_kv(kv)
,_bf(0){}
Node* _parent;
Node* _left;
Node* _right;
pair<K, V> _kv;
int _bf; //平衡因子
};
2.平衡因子
结点的左子树的深度减去右子树的深度,即结点的平衡因子 = 右子树的高度 - 左子树的高度 。通过节点的平衡因子来控制高度差。
新插入节点父亲及祖先平衡因子(bf)变化规律:
1.新增节点在左子树,父亲bf--,反之在右子树则父亲bf++。
2.更新后如果bf==0,父亲的子树高度不变,则不需要继续向上更新平衡因子。
3.更新后如果bf==-1 or 1,父亲所在的子树高度变化,继续向上更新。
4.更新后如果bf==2 or -2,则父亲所在子树违反规则,需要旋转。
3.搜索二叉树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧---左左:右单旋
2.新节点插入较高右子树的右侧---右右:左单旋
3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
4.新节点插入较高右子树的左侧---右左:先右单旋再左单旋
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因 子的更新。
以上四种情况可以做出以下总结:
三、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证 查询时高效的时间复杂度,即O(log2(n)) 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如: 插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。 因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树, 但一个结构经常修改,就不太适合,这时候就适合用红黑树。