数据结构与算法之平衡二叉树的插入、旋转以及遍历
平衡二叉树的定义
平衡二叉树 (AVL,Balanced Binary Tree),简称平衡树 (AVL 树)-- 树上任一结点的左子树和右子树的高度之差不超过 1。
- 结点的平衡因子=左子树高 - 右子树高。
- 平衡二叉树结点的平衡因子的值只可能是 -1、0 或 1。
- 只要有任一结点的平衡因子绝对值大于 1,就不是平衡二叉树。
typedef struct AVLNode{
int key; //数据域
int balance; //平衡因子
struct AVLNode *lchild,*rchild;
}AVLNode,*AVLTree;
平衡二叉树的插入
每次调整的“对象”都是最小不平衡二叉树, 如下图:
调整最小不平衡子树
LL
假定有如下子树,{A}、{B}、{AR}、{BL}、{BR},且假定所有子树的高度为 H(或者你认为是 1 也可以)。如下图
即:A 的平衡粒子为 1,B 为 0,AR 无子树
现在:以 LL(左孩子的左子树) 插入,操作如下图:
此时:B 的子树高度为 H+1(已做 LL 操作),AR 不变,那 A 的平衡因子为 2 了。此时就成 A 为最小不平衡子树。
那么此时我们怎么去调整呢,使其满足二叉排序树的特性:
这里提一下二叉排序树的特性是:
- 左子树结点值<根节点值<右子树结点值
- 即:
BL < B < BR < A < AR
解决办法 (右旋):
使 LL 平衡右旋 (右单旋转):由于在结点 A 的左孩子 (L) 的左子树 (L) 上插入了一个新节点,A 的平衡因子由 1 增至 2,导致以 A 为根的子树失去平衡,需要一次向右的旋转操作。将 A 的左孩子 B 向右上旋转代替 A 成为根节点,将 A 结点向右下旋转成为 B 的右子树的根节点,而 B 的原右子树则作为 A 结点的左子树。
RR
与 上述 相同,如下图:
此时 A 树为最小不平衡树。
- 二叉排序树的特性:左子树结点值<根结点值<右子树结点值
AL<A<BL<B<BR
解决办法 (左旋):
RR 平衡旋转 (左单旋转)。优于在结点 A 的右孩子 ® 的右子树 ® 上插入了新结点,A 的平衡因子由 -1 减至 -2,导致以 A 为根的子树失去平衡,需要一次向左的旋转操作。将 A 的右孩子 B 向左上旋转代替 A 成为根节点,将 A 结点向左下旋转成为 B 的左子树的根节点,而 B 的原左子树则作为 A 结点的右子树。
代码示例
右旋
实现 f
向右下旋转,p
向右上旋转:
其中 f
是爹,p
为左孩子,gf
为 f
他爹
f->lchild=p=->rchild;
p->rchild=f;
gf->lchild/rchild=p;
BL<B<BR<A<AR
左旋、右旋操作后可以保持二叉排序树的特性
左旋
实现 f
向左下旋转,p
向左上旋转:
其中 f
是爹,p
为右孩子,gf
为 f
他爹
f->rchild=p->lchild;
p->lchild=f;
gf->lchild/rchild=p;
LR
这里考虑的是插入到 CR,即 C 的右子树的位置
LR 平衡旋转 (先左后右双旋转)。由于在 A 的左孩子 (L) 的右子树 ® 上插入新节点,A 的平衡因子由 1 增至 2,导致以 A 为根的子树失去平衡,需要进行两次旋转操作,先做旋转后右旋转。先将 A 结点的左孩子 B 的右子树的根节点 C 向左上旋转提升到 B 结点的位置,然后再把该 C 结点向右上旋转提升到 A 结点的位置。
二叉树结点关系:BL<B<CL<C<CR<A<AR
此时左旋:
此时在右旋:
下图为插入结点是 CL 的处理过程
RL
新增结点可能是在 Cl 可能是在 CR,这里以 CL 为例
RL 平衡旋转 (先有后左双旋转)。由于在 A 的右孩子 ® 的左子树 (L) 上插入新节点,A 的平衡因子由 -1 减至 -2,导致以 A 为根的子树失去平衡,需要进行两次旋转操作,先有旋转后左旋转。先将 A 结点的右孩子 B 的左子树的根节点C 先右上旋转提升到 B 结点的位置,然后再把该 C 结点向左上 旋转提升到 A 结点的位置,过程如下图:
下面是插入结点是 CR 的处理过程:
总结
- LL
- 在 A 的左子树中插入导致不平衡
- 调整:A 的左孩子结点右上旋
- RR
- 在 A 的右孩子的右子树中插入导致不平衡
- 调整:A 的右孩子结点左上旋
- LR
- 在 A 的左孩子的右子树中插入导致不平衡
- 调整:A 的左孩子的右孩子先左上旋在右上旋
- RL
- 在 A 的右孩子的左子树中插入导致不平衡
- 调整:A 的右孩子的左孩子先右上旋后左上旋
每次调整之后注意验证:是否符合左<根<右
旋转操作说明:
右旋操作
注意:只有左孩子可以右旋
实现 f
向右下旋转,p
向右上旋转:
其中 f
是爹,p
为左孩子,gf
为 f
他爹
f->lchild=p=->rchild;
p->rchild=f;
gf->lchild/rchild=p;
左旋操作
注意:只有右孩子可以左旋
实现 f
向左下旋转,p
向左上旋转:
其中 f
是爹,p
为右孩子,gf
为 f
他爹
f->rchild=p->lchild;
p->lchild=f;
gf->lchild/rchild=p;
练习
RR 型
RL 型
LR 型
3 查找效率分析
若树高为 h,则最坏情况下,查找一个关键字最多需要对比 h 次,即查找操作的时间复杂度不可能 超过 O(h);
平衡二叉树——树上任一结点的左子树和右子树的高度之差不超过 1。
假设以
n
h
n_h
nh表示深度为 h 的平衡树中含有的最少结点数。则有
n
0
n_0
n0=0,
n
1
n_1
n1=0,
n
2
n_2
n2=0,并且有
n
h
n_h
nh=
n
h
−
1
n_{h-1}
nh−1+
n
h
−
2
n_{h-2}
nh−2+1。
可以证明含有 n 个结点的平衡二叉树的最大深度为
O
l
o
g
2
n
O_{log_2n}
Olog2n,平衡二叉树的平均查找长度为
O
l
o
g
2
n
O_{log_2n}
Olog2n