AVL(Adelson-Velskii和Landis)树是带有平衡条件的二叉查找树。平衡条件是:其每个节点的左子树和右子树的高度差最多等于1的二叉查找树。
AVL树的查找操作跟非AVL二叉查找树的查找是一样的,查找最小值沿左子树寻找,查找最大值沿右子树查找
AVL树的插入操作除了新建节点并插入到树中,还要为保持树的平衡性做调整的动作。通常通过围绕不平衡的点进行旋转来保持平衡性。
对于AVL树的插入操作破坏的是从插入点到根节点路径上的节点的平衡性。因为只有这些节点的子树由于插入导致平衡性的变化。所以,在插入操作时,
需要沿着插入点到根节点的路径上更新每个节点的新高度,并检查节点左右子树的高度差。当发现节点左右子树的高度差大于等于2时,说明此节点是一个
不平衡的节点,需要通过单旋转或者双旋转来恢复树的平衡性。第一个不平衡的节点也是所有不平衡节点中最深的那个。
假设节点a是一个不平衡的节点,在节点a处出现不平衡的情形有四种:
1.对a的左儿子的左子树进行一次插入(左-左)
2.对a的左儿子的右子树进行一次插入(左-右)
3.对a的右儿子的左子树进行一次插入(右-左)
4.对a的右儿子的右子树进行一次插入(右-右)
其中情形1和4是关于a点的镜像对称,情形2和3是关于a点的镜像对称。所以,理论上可以分为两大类,一类是插入发生在"外边"的情况(即左-左或右-右),
此类情形可以通过围绕a点的单旋转来恢复平衡性;第二类是插入操作发生在内部的情形(即右-左或左-右),此类情形可以通过围绕a点的双旋转来调整。
左左情形的单旋转又可以细分成两类,一种是a节点的左儿子没有右儿子,另一种是a节点的左儿子没有右儿子。所以编码实现时要适应这两种情况,
避免造成子树的丢失。如下图所示
左左情形中,要围绕不平衡节点(K1)和K1->Left进行旋转,当K1->Left(假定称为K2)有右儿子时,旋转前值的排序肯定是K1>K2->Right>K2。所以使K1->Left=K2->Right
来避免K2的右子树丢失,并最终返回K2的新节点。值得注意的是调整后的新节点K2与其新的父节点(上图中的5,4)是怎样保持链接的,由于插入操作的实现是递归方式的,先递归向下寻找可插入的点,插入完成后会递归返回,在返回时会检查节点的平衡性,如果不平衡做旋转操作。所以调整后新节点K2于其新的父节点的链接是通过对旋转函数的调用来实现的,如:T = SingleRotateWithLeft( T );或K3->Left = SingleRotateWithRight( K3->Left );等。左单旋转code如下所示:
static Position SingleRotateWithLeft(Position K1) { Position K2; K2 = K1->Left; K1->Left = K2->Right; K2->Right = K1; K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1; K2->Height = Max(Height(K2->Left), K1->Height) + 1; //或者K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1; return K2; }
右右情形和左左情形类似,也可以细分为两种情况,可以跟左左情况对比来看。如下图所示:
右右单旋转code如下所示:
static Position SingleRotateWithRight(Position K1) { Position K2; K2 = K1->Right; K1->Right = K2->Left; K2->Left = K1; K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1; K2->Height = Max(Height(K2->Right), K1->Height) + 1; return K2; }左右情形即在节点K3的左儿子的右子树上插入造成的。可以通过两步单旋转来完成所谓的双旋转操作:先将K3的左儿子进行一次右单旋转,旋转后的结果是K3左儿子的左子树更深,所以再通过一次围绕K3的左单旋转最终修复K3的不平衡性。左右情形的双旋转如下图所示:
左右双旋转的code如下所示:
static Position DoubleRotateWithLeft(Position K3) { K3->Left = SingleRotateWithRight(K3->Left); return SingleRotateWithLeft(K3); }
和左右情形的双旋转类似,右左情形是在节点K3的右儿子的左子树上插入造成不平衡性。也可以通过两次单旋转来完成所谓的双旋转操作:先将K3的右儿子进行一次左单旋转,然后将K3再进行一次右单旋转。右左情形的双旋转示意图如下:
右左情形的双旋转code实现如下:
static Position DoubleRotateWithRight(Position K3) { K3->Right = SingleRotateWithLeft(K3->Right); return SingleRotateWithRight(K3); }
节点的删除操作也会造成不平衡,根据插入操作分析的4种不平衡性,删除操作也会造成这四种不平衡性,即LL,LR,RR,RL。相应的也要通过单旋转或者双旋转来修复不平衡性。可以把删除造成的不平衡的4种情形看作插入时造成的4种不平衡情形的镜像。
删除时的策略还是使用跟普通二叉查找树一样,将删除节点的右子树中最小的那个节点替换删除节点,并递归的删除替换节点,直到替换节点只有左子树。
删除节点后,开始递归的在删除节点到根节点的路径上检查每个节点的平衡性,若不平衡则调整。4种不平衡的情形,在code处理时可分为两大类并检查。同样假设a节点不平衡。
1.删除的节点在a节点的左子树
2.删除的节点在a节点的右子树
进一步细分上面两种情形:
1.删除的节点在a节点的左子树:
此种情形会减小a节点左子树的高度,此时根据a节点的左儿子的左右子树高度的差异,可以确定删除操作对应的不平衡性是LL还是LR。
1.1如果a节点的左儿子的左子树比右子树高,则属于LL类型,只需要将a节点进行左单旋转即可。
1.2如果a节点的左儿子的左子树比右子树低,则属于LR类型,只需要将a节点进行左双旋转即可。
2.删除的节点在a节点的右子树:
此种情形会减小a节点的右子树高度,根据a节点右儿子的左右子树高度大小,可以确定删除操作造成的不平衡性属于RR还是RL。
2.1如果a节点的右儿子的左子树比右子树低,则属于RR类型,只需要将a节点进行右单旋转即可。
2.2如果a节点的右儿子的左子树比右子树高,则属于RL类型,只需要将a节点进行右双旋转即可。
无图无真相,还是上图吧,下图是左子树删除的情形
下图是右子树删除的情形: