一、平衡二叉搜索树(AVL树)
AVL 是大学教授 G.M. Adelson-Velsky 和 E.M. Landis 名称的缩写,他们提出的平衡二叉树的概念,为了纪念他们,将 平衡二叉树称为 AVL树。
上节介绍了二叉搜索树的概念及其意义,我们可以知道二叉搜索树旨在把有序向量的二分查找策略融入到树查找中从而提高二叉树的静态查找性能。但是search(),insert()和remove()等主要接口的运行时间,均线性正比于二叉搜索树的高度。而且在最坏情况下,二叉搜索树可能彻底退化成列表,此时的查找效率甚至会降至O(n)。因此,若不能有效地控制树高,则从实际的性能而言,二叉搜索树相比于向量和列表并不能体现出明显的优势。
以下引入平衡树的概念:
(a) 理想平衡:若二叉树的高度恰好为 |logn| 向下取整,则称作理想平衡。
(b) 适度平衡:在渐近意义下适当放松标准之后的平衡性,称作适度平衡。
幸运的是,适度平衡标准下的树确实存在,比如将树高限制为“渐近地不超过O(logn)”,则AVL树、伸展树、红黑树、kd-树都属于适度平衡树,也都可以归为平衡二叉搜索树。
二、等价变换
以下引入二叉搜索树的等价变化概念:
如上,若两颗二叉搜索树的中序遍历序列相同,则称它们彼此等价,这一特点也可表述为“上下可变,左右不乱”,值得一提的是,AVL树就是利用“单旋”和“双旋”这种等价变换手段来在插入节点和删除节点时修复局部性的失衡的。
事实上,包括AVL树在内的所有平衡二叉搜索树,其实现平衡均靠两个法宝:
(a) 如何判断当前树的平衡状态。
(b) 当二叉树发生失衡时,通过哪一套技巧将二叉树拉入平衡范围。
对于AVL树,其对应的两个法宝如下:
(a) 平衡因子:定义为节点左右子树的高度差。AVL树要求所有节点的平衡因子的绝对值不超过1.
(b) 旋转变换:AVL树依靠旋转变换这种等效变换手段将失衡的AVL树重平衡。
旋转:
旋转后左右顺序不变。
三、AVL树失衡与重平衡
AVL树的失衡的两种情况:
(a) 由新节点插入导致:新节点的插入可能导致很多祖先节点的失衡,但是经过一次旋转就能把所有的失衡节点全都回复。插入导致的失衡有以下两种情况
情况1:单旋解决
情况2:双旋解决
(b) 由节点删除导致:节点的删除只会导致一个祖先节点的失衡,但是在节点恢复的时候可能导致新的祖先节点的失衡,而且这种情况在每一层都出现,所以需要逐层向上检查并旋转恢复。删除导致的失衡也有如下两种情况:
情况1:单旋解决
情况2:双旋解决
化繁为简:旋转变换的重构(3+4重构)
通过以上的分析,我们可以看到,无论是由插入导致的失衡还是由删除导致的失衡,每次我们需要旋转的部分都只包括3个节点和4颗子树,类似拆魔方然后组装魔方,上述复杂的旋转变化还不如直接把有问题的3个顶点和4颗子树进行直接重构成右下图所示的结构。
四、AVL树的性能
优点:无论查找、插入或者删除,最坏情况下的复杂度均为O(logn),O(n)的存储空间。
缺点:
(a) 借助高度或者平衡因子,所以需要改造元素结构,或者额外封装这一属性。
(b) 插入或删除后的旋转成本高,删除操作后,最多要旋转logn次(一般5次操作发生一次这种情况),所以频繁插入删除成本高。
(c) 单次动态调整后,全树拓扑结构的变化量可能高达longn,这在实际的应用中是很避讳的。
五、AVL树的实现
avl类通过继承bst类进行实现,并重写了插入和删除操作。
操作 | 功能 | 对象 |
insert(const T& e) | 插入指定词条 | avl树 |
remove(const T& e) | 删除指定词条 | avl树 |
avl.h
#pragma once
#include"bst.h"
#define IsRoot(x) (!((x).parent))
#define IsLChild(x) (!IsRoot(x)&&(&(x)==(x).parent->lc))
#define FromParentTo(x) (IsRoot(x)?_root:(IsLChild(x)?(x).parent->lc:(x).parent->rc))
#define Banlanced(x) (stature((x).lc)==stature((x).rc)) //判断节点是否理想平衡
#define BalFac(x) (stature((x).lc)-stature((x).rc)) //平衡因子
#define AvlBalanced(x) ((-2<BalFac(x))&&(BalFac(x)<2))
#define tallerChild(x) (\
stature((x)->lc) > stature((x)->rc)? (x)->lc:( \
stature((x)->lc) < stature((x)->rc)? (x)->rc:( \
IsLChild(*(x))? (x)->lc:(x)->rc\
)\
)\
)
template<typename T> class avl :public bst<T>
{
public:
binNode<T>* insert(const T& e); //插入指定词条
bool remove(const T& e); //删除指定词条
};
template<typename T> binNode<T>* avl<T>::insert(const T& e)
{
binNode<T>* &x = search(e);
if (x) return x; //若已经存在,则直接返回
binNode<T>* xx = x = new binNode<T>(e, _hot); _size++;
//检查祖先是否失衡
for (binNode<T>* g = _hot; g; g = g->parent)
{
if (!AvlBalanced(*g))
{
FromParentTo(*g) = rotateAt(tallerChild(tallerChild(g)));
break;
}
else
{
updateHeight(g);
}
}
return xx;
}
template<typename T> bool avl<T>::remove(const T& e)
{
binNode<T>* &x = search(e);
if (!x) return false; //若目标不存在则删除
removeAt(x, _hot); _size--;
for (binNode<T>* g = _hot; g; g = g->parent)
{
if (!AvlBalanced(*g))
g = FromParentTo(*g) = rotateAt(tallerChild(tallerChild(g)));
updateHeight(g);
}
return true;
}