平衡二叉树
0. 为何要使用AVL树?
二叉搜索树的搜索效率与其树的深度相关,而二叉搜索树的组成又与其插入序列相关,在极端情况下,二叉搜索树退化为一条单链(比如插入序列是 1 2 3 … n),使得搜索效率大大降低,为了避免这种情况出现,我们采用二叉平衡树对插入结点进行调整,使得树的深度尽可能小
1. 定义
- 平衡因子
BF(T) = h <math> <semantics> <mrow> <msub> <mi> L </mi> </msub> </mrow> <annotation encoding="application/x-tex"> _L </annotation> </semantics> </math>L - h <math> <semantics> <mrow> <msub> <mi> R </mi> </msub> </mrow> <annotation encoding="application/x-tex"> _R </annotation> </semantics> </math>R,其中 h <math> <semantics> <mrow> <msub> <mi> L </mi> </msub> </mrow> <annotation encoding="application/x-tex"> _L </annotation> </semantics> </math>L、h <math> <semantics> <mrow> <msub> <mi> R </mi> </msub> </mrow> <annotation encoding="application/x-tex"> _R </annotation> </semantics> </math>R 分别是左右子树的高度 - 平衡二叉树(AVL 树)
空树,或者任一结点左、右子树高度差的绝对值不超过 1,即 |BF(T)|≤1 的树
2. 平衡二叉树的调整
0. 遵循原则
- 从离插入结点最近的结点调整
1. RR 单旋
当"插入结点"(BR)是"被破坏平衡结点"(A)右子树的右子树时,即 RR 插入时,采用 RR 旋转调整
其基本思路是把 B 的左子树腾出来挂到 A 的右子树上,返回 B 作为当前子树的根
示意图:
AVLTree RRRotation(AVLTree A){
AVLTree B = A->right; // B 为 A 的右子树
A->right = B->left; // B 的左子树挂在 A 的右子树上
B->left = A; // A 挂在 B 的左子树上
return B; // 此时 B 为根结点了
}
2. LL 单旋
当"插入结点"(BL)是"被破坏平衡结点"(A)左子树的左子树时,即 LL 插入,采用 RR 旋转调整
其基本思路是把 B 的右子树腾出来挂到 A 的左子树上,返回 B 作为当前子树的根
示意图:
AVLTree LLRotation(AVLTree A){
// 此时根节点是 A
AVLTree B = A->left; // B 为 A 的左子树
A->left = B->right; // B 的右子树挂在 A 的左子树上
B->right = A; // A 挂在 B 的右子树上
return B; // 此时 B 为根结点了
}
3. LR 双旋
当"插入结点"(CL 或者 CR)是"被破坏平衡结点"(A)左子树的右子树时,即 LR 插入,采用 LR 旋转调整
基本思想是先将 B 作为根结点进行 RR 单旋转化为 LL 插入,再将 A 作为根结点进行 LL 单旋(先 RR 再 LL)
示意图:
AVLTree LRRotation(AVLTree A){
// 先 RR 单旋
A->left = RRRotation(A->left);
// 再 LL 单旋
return LLRotation(A);
}
总结:叫 LR 双旋是从上到下看,而实际先 RR 单旋再 LL 单旋是从下往上的过程
4. RL 双旋
当"插入结点"(CL 或者 CR)是"被破坏平衡结点"(A)右子树的左子树时,即 RL 插入,采用 RL 旋转调整
基本思想是先将 B 作为根结点进行 LL 单旋转化为 RR 插入,再将 A 作为根结点进行 RR单旋(先 LL 再 RR)
示意图:
AVLTree RLRotation(AVLTree A){
// 先 LL 单旋
A->right = LLRotation(A->right);
// 再 RR 单旋
return RRRotation(A);
}
总结:叫 RL 双旋是从上到下看,而实际先 LL 单旋再 RR 单旋是从下往上的过程