目录
什么是 AVL 树
ASL 度量查找效率
结构体定义
平衡调整
调整类型
左旋和右旋
右旋
左旋
左、右平衡调整
左平衡调整
右平衡调整
插入数据
模拟建立 AVL 树
什么是 AVL 树
二叉排序树的形状取决于数据集,当二叉树的高度越小、结构越合理,搜索的性能就越好,时间复杂度 O(log2n)。G. M. Adelson-Velsky 和 E. M. Landis 在1962年的论文《An algorithm for the organization of information》中发表了一种名为 AVL 树的数据结构,它就能很好地解决这个问题。AVL 树具有以下 2 个性质:
- 左子树和右子树的深度之差的绝对值不超过 1;
- 左子树和右子树通通都是 AVL 树。
其中为了度量左右子树的深度之差,我们引入平衡因子 (BF)"的概念。例如下面的二叉搜索树的平衡因子为:
对于一棵 AVL 树,里面的所有结点的平衡因子只能取值于:-1、0、1。
ASL 度量查找效率
为了更好地理解 AVL 树,请认真观察下面 2 个二叉搜索树,我们发现第二个二叉搜索树是 AVL 树,树的高度更小,查找的比较次数也更少,效率更高。
现在计算一下该 AVL 树的 ASL:
ASL(成功):(1 + 2 × 2 + 3 × 4) ÷ 6 = 17/6
ASL(失败):4 × 8 ÷ 8 = 4
和二叉搜索树对比,发现无论是成功还是失败的 ASL,AVL 树的都较小,说明效率更高。
结构体定义
typedef struct BiTNode
{
int data;
int bf; //平衡因子
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
平衡调整
调整类型
由于二叉搜索树的结点是一个一个插入的,在插入时可能会造成结点的平衡因子的绝对值超过 1。造成失衡一共有 4 种情况:RR 型、LL 型、RL 型、LR 型,如图所示:
虽然有 4 种情况,但是需要遵循的原则是一样的——在尽可能减小高度的前提下,保持二叉搜索树的性质。下面就看一下 4 种情况的调整示意图,不难发现都是遵循这个原则进行调整的。
需要注意的是,当有多个结点失衡时,需要选择最小失衡子树来调整。
左旋和右旋
右旋
右旋可以形象地理解为把最上面的结点掰下来,这种操作称之为“右旋”。右旋操作后指向新的根结点,即操作之前的根结点的左结点。
void R_Rotate(BiTree &T)
{
BiTree L; //L 表示 T 的左子树
L = T->lchild; //L 指向 T 的左子树
T->lchild = L->rchild; //T 的左子树更改为 T 的左子树的右子树
L->rchild = T; //T 的左子树的右子树修改为 T
T = ptr; //根结点修改为右旋之前 T 的左子树
}
左旋
对于 RR 型的调整,可以形象地理解为把最上面的结点掰下来,这种操作称之为“左旋”。左旋操作后指向新的根结点,即操作之前的根结点的右结点。
void L_Rotate(BiTree &T)
{
BiTree R; //R 表示 T 的右子树
R = T->rchild; //R 指向 T 的右子树
T->rchild = R->lchild; //T 的右子树更改为 T 的右子树的左子树
R->lchild = T; //T 的右子树的左子树修改为 T
T = R; //根结点修改为左旋之前 T 的右子树
}
左、右平衡调整
左平衡调整
这个函数为左平衡旋转,将 RL 型和 LL 型进行判断和操作。需要考虑涉及结点所连接的子树,对每个结点的 BF 都进行修正。
void LeftBalance(BiTree &T)
{
BiTree L; //L 表示 T 的左子树
BiTree Lr; //Lr 表示 L 的右子树
L = T->lchild; //L 指向 T 的左结点
switch(L->bf) //根据 T 的左子树的 BF 作相应平衡处理
{
case 1: //LL 型,新结点插入在 T 的左结点的左子树
{
T->bf = L->bf = 0; //修正 BF 均为 0
R_Rotate(T); //右旋操作
break;
}
case -1: //RL 型,新结点插入在 T 的左结点的右子树,要做双旋操作
{
Lr = L->rchild; //Lr 指向示 L 的右结点
switch(Lr->bf) //修正 T 及其左结点的平衡因子
{
case 1: //Lr 平衡因子为 1
{
T->bf = -1;
L->bf = 0;
break;
}
case 0: //Lr 平衡因子为 0
{
T->bf = L->bf = 0;
break;
}
case -1: //Lr 平衡因子为 -1
{
T->bf = 0;
L->bf = 1;
break;
}
}
Lr->bf = 0; //修正 Lr 平衡因子为 0
L_Rotate(T->lchild); //对 T 的左子树作左旋操作
R_Rotate(T); //对 T 作右旋操作
break;
}
}
}
右平衡调整
这个函数为右平衡旋转,将 LR 型和 RR 型进行判断和操作。需要考虑涉及结点所连接的子树,对每个结点的 BF 都进行修正。
void RightBalance(BiTree &T)
{
BiTree R; //R 表示 T 的右子树
BiTree Rl; //Rl 表示 R 的左子树
R = T->rchild; //R 指向 T 的右结点
switch(R->bf) //根据 T 的右子树的 BF 作相应平衡处理
{
case -1: //RR 型,新结点插入在 T 的右结点的右子树
{
T->bf = R->bf = 0; //修正 BF 均为 0
L_Rotate(T); //左旋操作
break;
}
case 1: //LR 型,新结点插入在 T 的右结点的左子树,要做双旋操作
{
Rl = R->lchild; //Rl 指向 R 的左结点
switch(Rl->bf) //修正 T 及其右结点的平衡因子
{
case -1: //Rl 平衡因子为 -1
{
T->bf = 1;
R->bf = 0;
break;
}
case 0: //Rl 平衡因子为 0
{
T->bf = R->bf = 0;
break;
}
case 1: //Rl 平衡因子为 1
{
T->bf = 0;
R->bf = -1;
break;
}
}
Rl->bf = 0; //修正 Rl 平衡因子为 0
R_Rotate(T->rchild); //对 T 的右子树作右旋操作
L_Rotate(T); //对 T 作左旋操作
break;
}
}
}
插入数据
要插入一个数据,首先需要判断这个数据是否已存在,若在 AVL 树中不存在要插入的数据,则执行插入操作。函数将通过递归找到合适的插入位置,如果在插入时出现失去平衡的情况,要进行对应的平衡旋转处理。
bool InsertAVL(BiTree &T, int e, bool &taller) {
// taller 表示树的高度是否发生变化
if (T == NULL) { // 若传入的 T 为空树,将创建根结点
T = new BiTNode;
T->data = e;
T->bf = 0;
T->lchild = T->rchild = NULL;
taller = true; // 表示树的高度是否发生变化
} else {
if (e == T->data) { // 和 e 有相同数据的结点已存在
taller = false;
return false;
}
if (e < T->data) { // 进入左子树搜索插入位置
if (InsertAVL(T->lchild, e, taller) == false) { // 结点已存在
return false;
}
if (taller == true) { // 数据插入 T 的左子树中并且高度发生变化
switch (T->bf) { // 检查 T 的 BF 判断是否调整
case 1: // 原本左子树比右子树高,需要进行左平衡处理
LeftBalance(T); // 左平衡处理
taller = false;
break;
case 0: // 原本左、右子树等高,仍保持平衡,修正 BF
T->bf = 1;
taller = true;
break;
case -1: // 原本右子树比左子树高,仍保持平衡,修正 BF
T->bf = 0;
taller = false;
break;
}
}
} else { // 进入右子树搜索插入位置
if (InsertAVL(T->rchild, e, taller) == false) { // 结点已存在
return false;
}
if (taller == true) { // 数据插入 T 的左子树中并且高度发生变化
switch (T->bf) { // 检查 T 的 BF 判断是否调整
case 1: // 原本右子树比左子树高,仍保持平衡,修正 BF
T->bf = 0;
taller = false;
break;
case 0: // 原本左、右子树等高,仍保持平衡,修正 BF
T->bf = -1;
taller = true;
break;
case -1: // 原本右子树比左子树高,需要进行右平衡处理
RightBalance(T);
taller = false;
break;
}
}
}
}
return true;
}
模拟建立 AVL 树
按照整数序列 {4,5,7,2,1,3,6} 依次插入的顺序构造相应平衡二叉树。
首先结点 4 加入 AVL 树成为根结点。
结点 5 加入 AVL 树。
结点 7 加入 AVL 树,此时结点 4 的平衡因子为 -2,需要进行调整。
进行 RR 型调整。
结点 2 加入 AVL 树。
结点 1 加入 AVL 树,此时结点 4 的平衡因子为 2,需要进行调整。
进行 LL 型调整。
结点 3 加入 AVL 树,此时结点 5 的平衡因子为 2,需要进行调整。
进行 LR 型调整。
结点 6 加入 AVL 树,此时结点 5 的平衡因子为 -2,需要进行调整。
进行 RL 型调整。
到此为止,AVL 树建立完毕。完整的代码实现如下:
#include <stdio.h>
#include <stdlib.h>
// AVL树的节点结构
typedef struct BiTNode {
int data;
int bf; // 平衡因子
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 右旋转
void R_Rotate(BiTree *P) {
BiTree L;
L = (*P)->lchild;
(*P)->lchild = L->rchild;
L->rchild = (*P);
*P = L;
}
// 左旋转
void L_Rotate(BiTree *P) {
BiTree R;
R = (*P)->rchild;
(*P)->rchild = R->lchild;
R->lchild = (*P);
*P = R;
}
// 左平衡旋转处理
void LeftBalance(BiTree *T) {
BiTree L, Lr;
L = (*T)->lchild;
switch (L->bf) {
case 1:
(*T)->bf = L->bf = 0;
R_Rotate(T);
break;
case -1:
Lr = L->rchild;
switch (Lr->bf) {
case 1:
(*T)->bf = -1;
L->bf = 0;
break;
case 0:
(*T)->bf = L->bf = 0;
break;
case -1:
(*T)->bf = 0;
L->bf = 1;
break;
}
Lr->bf = 0;
L_Rotate(&(*T)->lchild);
R_Rotate(T);
}
}
// 右平衡旋转处理
void RightBalance(BiTree *T) {
BiTree R, Rl;
R = (*T)->rchild;
switch (R->bf) {
case -1:
(*T)->bf = R->bf = 0;
L_Rotate(T);
break;
case 1:
Rl = R->lchild;
switch (Rl->bf) {
case 1:
(*T)->bf = 0;
R->bf = -1;
break;
case 0:
(*T)->bf = R->bf = 0;
break;
case -1:
(*T)->bf = 1;
R->bf = 0;
break;
}
Rl->bf = 0;
R_Rotate(&(*T)->rchild);
L_Rotate(T);
}
}
// 插入节点到AVL树中
int InsertAVL(BiTree *T, int key, int *taller) {
if (!*T) {
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = key;
(*T)->lchild = (*T)->rchild = NULL;
(*T)->bf = 0;
*taller = 1;
} else {
if (key == (*T)->data) { // 树中已存在相同的节点
*taller = 0;
return 0;
}
if (key < (*T)->data) { // 插入到左子树
if (!InsertAVL(&((*T)->lchild), key, taller))
return 0;
if (*taller) { // 高度增加了
switch ((*T)->bf) {
case 1:
LeftBalance(T);
*taller = 0;
break;
case 0:
(*T)->bf = 1;
*taller = 1;
break;
case -1:
(*T)->bf = 0;
*taller = 0;
break;
}
}
} else { // 插入到右子树
if (!InsertAVL(&((*T)->rchild), key, taller))
return 0;
if (*taller) { // 高度增加了
switch ((*T)->bf) {
case 1:
(*T)->bf = 0;
*taller = 0;
break;
case 0:
(*T)->bf = -1;
*taller = 1;
break;
case -1:
RightBalance(T);
*taller = 0;
break;
}
}
}
}
return 1;
}
int main() {
int count; // 数据元素的个数
BiTree T = NULL;
int taller, flag; // taller 反映 T 的高度是否变化
int a_num; // 暂存输入数据
scanf("%d", &count);
for (int i = 0; i < count; i++) {
scanf("%d", &a_num);
flag = InsertAVL(&T, a_num, &taller); // 向 AVL 树中插入 a_num
}
return 0;
}