一、什么是平衡二叉树?

平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉排序树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。

我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor),那么平衡二叉树上所有结点的平衡因子只可能是-1、0和1。距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树

平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。

在插入过程中,当最小不平衡子树根结点的平衡因子 BF 大于 1 时,就右旋;小于 -1 时就左旋。插入结点后,最小不平衡子树的 BF 与它的子树的 BF 符号相反时,就需要对结点先进行一次旋转以使得符号相同后,再反向旋转一次才能够完成平衡操作。

局限性:由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。

二、平衡二叉树不平衡的情形

把需要重新平衡的结点叫做 α(下面是 6 和2),由于任意两个结点最多只有两个儿子,因此高度不平衡时,α 结点的两颗子树的高度相差 2。容易看出,这种不平衡可能出现在下面 4 中情况中:

情形 1 和情形 4 是关于 α 的镜像对称,二情形 2 和情形 3 也是关于 α 的镜像对称,因此理论上看只有两种情况,但编程的角度看还是四种情形。

第一种情况是插入发生在 “外边” 的情形(左左或右右),该情况可以通过一次单旋转完成调整;第二种情况是插入发生在 “内部” 的情形(左右或右左),这种情况比较复杂,需要通过双旋转来调整。

三、调整措施

一、单旋转

上图是左左的情况,k2 结点不满足平衡性,它的左子树 k1 比右子树 z 深两层,k1 子树中更深的是 k1 的左子树 x,因此属于左左情况。

为了恢复平衡,我们把 x 上移一层,并把 z 下移一层,但此时实际已经超出了 AVL 树的性质要求。为此,重新安排结点以形成一颗等价的树。为使树恢复平衡,我们把 k2 变成这棵树的根节点,因为 k2 大于 k1,把 k2 置于 k1 的右子树上,而原本在 k1 右子树的 Y 大于 k1,小于 k2,就把 Y 置于 k2 的左子树上,这样既满足了二叉查找树的性质,又满足了平衡二叉树的性质。

这种情况称为单旋转。

二、双旋转

对于左右和右左两种情况,单旋转不能解决问题,要经过两次旋转。

对于上图情况,为使树恢复平衡,我们需要进行两步,第一步,把 k1 作为根,进行一次右右旋转,旋转之后就变成了左左情况,所以第二步再进行一次左左旋转,最后得到了一棵以 k2 为根的平衡二叉树。

四、平衡二叉树实现算法

二叉排序树的结点结构:(增加一个 bf,用来存储平衡因子)

// 二叉树的二叉链表结点结构定义
// 结点结构
typedef struct BiTNode
{
// 结点数据
int data;
// 结点的平衡因子
int bf;
// 左右孩子指针
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

4.1 左旋操作

对于左旋操作,代码如下:

// 对以指针T所指结点为根的二叉树作左平衡旋转处理
// 本算法结束时,指针T指向新的根结点
void leftBalance(BiTree *T)
{
BiTree L, Lr;
L = (*T)->lchild; // L指向T的左子树根结点 // 检查T的左子树的平衡度,并作相应平衡处理
switch (L->bf)
{
case LH: // 新结点插入在T的左孩子的左子树上,要作单右旋处理
(*T)->bf = L->bf = EH;
rightRotate(T);
break; case RH: // 新结点插入在T的左孩子的右子树上,要作双旋处理
Lr = L->rchild; // Lr指向T的左孩子的右子树根 // 修改T及其左孩子的平衡因子
switch (Lr->bf)
{
case LH:
(*T)->bf = RH;
L->bf = EH;
break; case EH:
(*T)->bf = L->bf = EH;
break; case RH: (*T)->bf = EH;
L->bf = LH;
break;
} Lr->bf = EH;
leftRotate(&(*T)->lchild); // 对T的左子树作左旋平衡处理
rightRotate(T); // 对T作右旋平衡处理
}
}

4.2 右旋操作

右旋操作代码如下:

// 对以指针T所指结点为根的二叉树作右平衡旋转处理,
// 本算法结束时,指针T指向新的根结点
void rightBalance(BiTree *T)
{
BiTree R, Rl;
R = (*T)->rchild; // R指向T的右子树根结点 // 检查T的右子树的平衡度,并作相应平衡处理
switch (R->bf)
{
// 新结点插入在T的右孩子的右子树上,要作单左旋处理
case RH:
(*T)->bf = R->bf = EH;
leftRotate(T);
break; // 新结点插入在T的右孩子的左子树上,要作双旋处理
case LH:
Rl = R->lchild; // Rl指向T的右孩子的左子树根 // 修改T及其右孩子的平衡因子
switch (Rl->bf)
{
case RH:
(*T)->bf = LH;
R->bf = EH;
break; case EH:
(*T)->bf = R->bf = EH;
break; case LH:
(*T)->bf = EH;
R->bf = RH;
break;
} Rl->bf = EH;
rightRotate(&(*T)->rchild); // 对T的右子树作右旋平衡处理
leftRotate(T); // 对T作左旋平衡处理
}
}

总结:右旋,则原左子树的根结点变新树根结点,它的右子树根结点是原来的树根结点,那么它原来的右子树根结点呢?变成原来的树根结点(即现在的右子树根结点)的左子树根结点,因为此结点的左子树变成新的树根结点了,左子树也就空缺了。左旋类似。

五、完整实现

实现代码如下:

#include <stdio.h>
#include <stdlib.h> #define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */ typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如TRUE等 */ /* 二叉树的二叉链表结点结构定义 */
typedef struct BiTNode /* 结点结构 */
{
int data; // 结点数据
int bf; // 结点的平衡因子
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree; // 对以p为根的二叉排序树作右旋处理,
// 处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点
void rightRotate(BiTree *P)
{
BiTree L;
L = (*P)->lchild; // L指向P的左子树根结点
(*P)->lchild = L->rchild; // L的右子树挂接为P的左子树
L->rchild = (*P);
*P = L; // P指向新的根结点
} // 对以P为根的二叉排序树作左旋处理,
// 处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0
void leftRotate(BiTree *P)
{
BiTree R;
R = (*P)->rchild; // R指向P的右子树根结点
(*P)->rchild = R->lchild; // R的左子树挂接为P的右子树
R->lchild = (*P);
*P = R; // P指向新的根结点
} #define LH +1 // 左高
#define EH 0 // 等高
#define RH -1 // 右高 // 对以指针T所指结点为根的二叉树作左平衡旋转处理
// 本算法结束时,指针T指向新的根结点
void leftBalance(BiTree *T)
{
BiTree L, Lr;
L = (*T)->lchild; // L指向T的左子树根结点 // 检查T的左子树的平衡度,并作相应平衡处理
switch (L->bf)
{
case LH: // 新结点插入在T的左孩子的左子树上,要作单右旋处理
(*T)->bf = L->bf = EH;
rightRotate(T);
break; case RH: // 新结点插入在T的左孩子的右子树上,要作双旋处理
Lr = L->rchild; // Lr指向T的左孩子的右子树根 // 修改T及其左孩子的平衡因子
switch (Lr->bf)
{
case LH:
(*T)->bf = RH;
L->bf = EH;
break; case EH:
(*T)->bf = L->bf = EH;
break; case RH: (*T)->bf = EH;
L->bf = LH;
break;
} Lr->bf = EH;
leftRotate(&(*T)->lchild); // 对T的左子树作左旋平衡处理
rightRotate(T); // 对T作右旋平衡处理
}
} // 对以指针T所指结点为根的二叉树作右平衡旋转处理,
// 本算法结束时,指针T指向新的根结点
void rightBalance(BiTree *T)
{
BiTree R, Rl;
R = (*T)->rchild; // R指向T的右子树根结点 // 检查T的右子树的平衡度,并作相应平衡处理
switch (R->bf)
{
// 新结点插入在T的右孩子的右子树上,要作单左旋处理
case RH:
(*T)->bf = R->bf = EH;
leftRotate(T);
break; // 新结点插入在T的右孩子的左子树上,要作双旋处理
case LH:
Rl = R->lchild; // Rl指向T的右孩子的左子树根 // 修改T及其右孩子的平衡因子
switch (Rl->bf)
{
case RH:
(*T)->bf = LH;
R->bf = EH;
break; case EH:
(*T)->bf = R->bf = EH;
break; case LH:
(*T)->bf = EH;
R->bf = RH;
break;
} Rl->bf = EH;
rightRotate(&(*T)->rchild); // 对T的右子树作右旋平衡处理
leftRotate(T); // 对T作左旋平衡处理
}
} // 若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个
// 数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树
// 失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。
Status insertAVL(BiTree *T, int e, Status *taller)
{
// 插入新结点,树“长高”,置taller为TRUE
if (!*T)
{
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = e;
(*T)->lchild = (*T)->rchild = NULL;
(*T)->bf = EH;
*taller = TRUE;
}
else
{
// 树中已存在和e有相同关键字的结点则不再插入
if (e == (*T)->data)
{
*taller = FALSE; return FALSE;
} // 应继续在T的左子树中进行搜索
if (e<(*T)->data)
{
// 未插入
if (!insertAVL(&(*T)->lchild, e, taller))
return FALSE; // 已插入到T的左子树中且左子树“长高”
if (*taller)
{
// 检查T的平衡度
switch ((*T)->bf)
{
case LH: // 原本左子树比右子树高,需要作左平衡处理
leftBalance(T);
*taller = FALSE;
break; case EH: // 原本左、右子树等高,现因左子树增高而使树增高
(*T)->bf = LH;
*taller = TRUE;
break; case RH: // 原本右子树比左子树高,现左、右子树等高
(*T)->bf = EH;
*taller = FALSE;
break;
}
} }
else
{
// 应继续在T的右子树中进行搜索
if (!insertAVL(&(*T)->rchild, e, taller)) // 未插入
return FALSE; // 已插入到T的右子树且右子树“长高”
if (*taller)
{
// 检查T的平衡度
switch ((*T)->bf)
{
case LH: // 原本左子树比右子树高,现左、右子树等高
(*T)->bf = EH;
*taller = FALSE;
break; case EH: // 原本左、右子树等高,现因右子树增高而使树增高/
(*T)->bf = RH;
*taller = TRUE;
break; case RH: // 原本右子树比左子树高,需要作右平衡处理
rightBalance(T);
*taller = FALSE;
break;
}
} }
} return TRUE;
} // 中序递归遍历
void inOrderTraverse(BiTree T)
{
// 判断二叉树是否存在
if (T == NULL)
return; inOrderTraverse(T->lchild); // 中序遍历左子树
printf("%d ", T->data); // 显示结点数据,可以更改为其它对结点操作
inOrderTraverse(T->rchild); // 最后中序遍历右子树
} int main(void)
{
int a[10] = { 3, 2, 1, 4, 5, 6, 7, 10, 9, 8 };
BiTree T = NULL;
Status taller; // 插入操作
for (int i = 0; i<10; i++)
{
insertAVL(&T, a[i], &taller);
} // 中序递归遍历
printf("中序递归遍历:");
inOrderTraverse(T);
printf("\n"); printf("\n另外,本样例建议断点跟踪查看平衡二叉树结构\n\n"); return 0;
}

输出结果如下图所示:

参考:

《大话数据结构 - 第8章》 查找

平衡二叉树详解

最新文章

  1. 《React Native入门与实战》读书笔记(1)
  2. PHP实现 bitmap 位图排序 求交集
  3. JQuery中的push和join
  4. CentOS 命令随笔
  5. hdu 单调队列
  6. include,include_once,require,require_once的区别
  7. String StringBuffer StringBuilder (转)
  8. mysql存储过程分库分表
  9. awk中引用shell变量执行替换的脚本
  10. Java关键字汇总
  11. Linux常用监控命令简介 – vmstat,ps,free,uptime 等
  12. 开源HUSTOJ
  13. web font
  14. Django url管理之include
  15. c函数创建文件和路径
  16. linux网络设备驱动
  17. Android打印当前所有线程及对应栈信息
  18. 设置定时任务(Timer类的介绍)
  19. dubbo入门之微服务客户端服务端配置
  20. 读CSV转换datatable

热门文章

  1. 如何开发一个异常检测系统:异常检测 vs 监督学习
  2. 【IIS】跨域(转)
  3. 如何使用h5py读入数据
  4. solr根据id删除索引
  5. Type Encodings
  6. ### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-02291: 违反完整约束条件 (SSM.SYS_C0011830) - 未找到父项关键字
  7. WinDbg常用命令系列---!findstack
  8. CGLIB和Java动态代理的区别(笔记)
  9. GoCN每日新闻(2019-10-28)
  10. 学习HSDB