[数据结构 - 第6章] 树之二叉平衡树(C语言实现)
一、什么是平衡二叉树?
平衡二叉树(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章》 查找
[数据结构 - 第6章] 树之二叉平衡树(C语言实现)的更多相关文章
- [数据结构 - 第6章] 树之链式二叉树(C语言实现)
一.什么是二叉树? 1.1 定义 二叉树,是度为二的树,二叉树的每一个节点最多只有二个子节点,且两个子节点有序. 1.2 二叉树的重要特性 (1)二叉树的第 i 层上节点数最多为 2n-1: (2)高 ...
- AVL树(二叉平衡树)详解与实现
AVL树概念 前面学习二叉查找树和二叉树的各种遍历,但是其查找效率不稳定(斜树),而二叉平衡树的用途更多.查找相比稳定很多.(欢迎关注数据结构专栏) AVL树是带有平衡条件的二叉查找树.这个平衡条件必 ...
- 各种查找算法的选用分析(顺序查找、二分查找、二叉平衡树、B树、红黑树、B+树)
目录 顺序查找 二分查找 二叉平衡树 B树 红黑树 B+树 参考文档 顺序查找 给你一组数,最自然的效率最低的查找算法是顺序查找--从头到尾挨个挨个遍历查找,它的时间复杂度为O(n). 二分查找 而另 ...
- java项目---用java实现二叉平衡树(AVL树)并打印结果(详)(3星)
package Demo; public class AVLtree { private Node root; //首先定义根节点 private static class Node{ //定义Nod ...
- HDU 1166 敌兵布阵(线段树 or 二叉索引树)
http://acm.hdu.edu.cn/showproblem.php?pid=1166 题意:第一行一个整数T,表示有T组数据. 每组数据第一行一个正整数N(N<=50000),表示敌人有 ...
- 【数据结构05】红-黑树基础----二叉搜索树(Binary Search Tree)
目录 1.二分法引言 2.二叉搜索树定义 3.二叉搜索树的CRUD 4.二叉搜索树的两种极端情况 5.二叉搜索树总结 前言 在[算法04]树与二叉树中,已经介绍过了关于树的一些基本概念以及二叉树的前中 ...
- 从零开始学算法---二叉平衡树(AVL树)
先来了解一些基本概念: 1)什么是二叉平衡树? 之前我们了解过二叉查找树,我们说通常来讲, 对于一棵有n个节点的二叉查找树,查询一个节点的时间复杂度为log以2为底的N的对数. 通常来讲是这样的, 但 ...
- Algorithms: 二叉平衡树(AVL)
二叉平衡树(AVL): 这个数据结构我在三月份学数据结构结构的时候遇到过.但当时没调通.也就没写下来.前几天要用的时候给调好了!详细AVL是什么,我就不介绍了,维基百科都有. 后面两月又要忙了. ...
- 判断一颗二叉树是否为二叉平衡树 python 代码
输入一颗二叉树,判断这棵树是否为二叉平衡树.首先来看一下二叉平衡树的概念:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.因此判断一颗二叉平衡树的关键在于 ...
随机推荐
- wordpress调用指定tag的文章
前面的文章wordpress调用指定分类文章如何实现有网友回复要如何调用指定tag的文章,原理是类似的,有两种方法,随ytkah一起来看看 1.第一种 <?php $args=array( 't ...
- Can总线上的电平及物理层仲裁
CAN总线采用差分信号传输,通常情况下只需要两根信号线(CAN-H和CAN-L)就可以进行正常的通信.在干扰比较强的场合,还需要用到屏蔽地即CAN-G(主要功能是屏蔽干扰信号),CAN协议推荐用户使用 ...
- applyMiddleware 沉思录
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null); 给({ getState, dis ...
- Spring核心模块解析
Spring框架是一个轻量级的集成式开发框架,可以和任何一种框架集成在一起使用,可以说是一个大的全家桶.Spring从1.x发展到现在的5.x可以说是越来越强大,下面来看看Spring都包含哪些核心的 ...
- 20-2 树莓派搭建服务器 Tornado Web服务器
Drive.google.com/drive/folders/1ahbeoEHkjxoo4NV1wReOmpoRWbl448z- 1.Tornado简介 Tornado一款使用 Python 编写的, ...
- 常用方法 Entitys转换为DataTable
效率比较屁,将近可以用 public static DataTable EntitiesToDataTable<T>(List<T> entitys) { Type t = t ...
- GoCN每日新闻(2019-11-07)
GoCN每日新闻(2019-11-07) GoCN每日新闻(2019-11-07) 1. [译] 排序运行时间能否做到 O(n)?让 Go 语言来告诉你 https://mp.weixin.qq.co ...
- Centos7 Nginx安装使用
一.Nginx简介 1.什么是nginx Nginx是一款使用C语言开发的高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.由俄罗斯的程序设计师Igor Sysoev ...
- kotlin基础 range
a = 1..10 //[1,10] b = 1 unitl 10 //[1,10)
- VUE-013-为elementUI 设置 tootip 宽度
在表格显示列表中,通常添加 :show-overflow-tooltip="true" 以显示不能完全展示的单元格文案提示.单通常显示为全屏宽度,不易查看,可通过设置全局的样式,进 ...