文字描述

平衡二叉树(Balanced Binary Tree或Height-Balanced Tree)

  因为是俄罗斯数学家G.M.Adel’son-Vel’skii和E.M.Landis在1962年提出来的,所以又称AVL树。它或者是一颗空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。若将二叉树上结点的平衡因子BF(Balanced Factor)定义为该结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1,0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。

  那么如何使二叉排序树成为平衡树呢?即在一颗二叉排序树中因插入一个结点后失去平衡的话,怎么调整才能使之重新平衡呢?

  一般情况下,假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针a(即a是离插入结点最近,且平衡因子绝对值超过1的祖先结点),则失去平衡后进行调整的规律可归纳为下面4中情况:

  (1)单向右旋平衡处理,图9.13(a)所示:在*a的左子树根结点的左子树上插入结点后,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行一次向右的顺时针旋转操作。

  (2)双向旋转(先左后右),图9.13(b)所示:在*a的左子树根结点的右子树上插入结点后,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作。

  (3)单向左旋平衡处理,图9.13(c)所示:在*a的右子树根结点的右子树上插入结点后,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行一次向左的逆时针旋转操作。

  (4)双向旋转(先右后左),图9.13(d)所示:在*a的右子树根结点的左子树上插入结点后,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行两次选择(先右旋后左旋)

上诉4种情况中,(1)和(3)对称,(2)和(4)对称。它们旋转后依然能保持二叉排序树的特性且由不平衡变为平衡。可以用二叉排序树的特性(”对于二叉排序树,中序遍历所得关键字序列自小至大有序”)证明之。

示意图

算法分析

  在平衡二叉排序树上查找的时间复杂度为logn, 不会出现最差的情况。

代码实现

 //./a.out 45 12 53 3 37 100 24 61 90 78
//./a.out 45 12 53 100 61
//测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #define DEBUG
#define TRUE 1
#define FALSE 0
#define LH +1 //左高
#define EH 0 //等高
#define RH -1 //右高
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)< (b))
#define LQ(a,b) ((a)<=(b))
#define LCHILD 1
#define RCHILD 2 typedef int ElemType;
typedef int Boolean;
//平衡二叉树采用二叉链表作为存储结构
typedef struct BSTNode{
ElemType data;
//结点的平衡因子
int bf;
//左,右孩子指针
struct BSTNode *lchild, *rchild;
}BSTNode, *BSTree; /*右旋平衡处理算法
*
*对以*p为根的二叉排序树作右旋处理,处理之后p指向新的树根结点,即旋转之前的左子树的根结点。f始终指向*p的父亲结点
*提示:建议结合图9.13(a)看,此处*p, lc相当于图中A、B结点。
*/
void R_Rotate(BSTree *p, BSTree f){
//*p是其父亲结点f的左孩子结点还是右孩子结点?
int flag = -;
if(f && (f->lchild == (*p))){
//*p是f的左孩子结点
flag = LCHILD;
}
if(f && (f->rchild == (*p))){
//*p是f的右孩子结点
flag = RCHILD;
} //lc指向*p的左子树根结点
BSTNode *lc = (BSTNode*)(*p)->lchild;
//lc的右子树挂接为*p的左子树
(*p)->lchild = lc->rchild;
//p指向新的根结点
lc->rchild = *p;
*p = lc; //更新父亲结点f的孩子结点指针
if(f && (flag==LCHILD)){
f->lchild = *p;
}
if(f && (flag==RCHILD)){
f->rchild = *p;
}
} /*左旋平衡处理算法
*
*提示:和右旋平衡算法是对称的,建议结合图9.13(c)看,此处*p,rc相当图图中的A、B结点。
*/
void L_Rotate(BSTree *p, BSTree f){
int flag = -;
if(f && (f->lchild == (*p))){
flag = LCHILD;
}
if(f && (f->rchild == (*p))){
flag = RCHILD;
} BSTNode *rc = (BSTNode*)(*p)->rchild;
(*p)->rchild = rc->lchild;
rc->lchild = *p;
*p = rc; if(f && (flag==LCHILD)){
f->lchild = *p;
}
if(f && (flag==RCHILD)){
f->rchild = *p;
}
} //对指针T所指结点为根的二叉树作左平衡选择处理,本算法结束时,指针T指向新的根结点,f为*T的父亲结点。
void LeftBalance(BSTree *T, BSTree f){
//lc指向*T的左子树根结点
BSTNode *lc = (BSTNode*)(*T)->lchild;
BSTNode *rd;
//检查*T的左子树的平衡度,并作相应平衡处理
switch(lc->bf){
//新结点插在了*T的左孩子的左子树上,要做单右旋处理
case LH:
lc->bf = (*T)->bf = EH;
R_Rotate(T, f);
break;
//新结点插在了*T的左孩子的右子树上,要做双旋处理
case RH:
//rd指向*T的左孩子的右子树根
rd = lc->rchild;
switch(rd->bf){
//修改*T及其左孩子的平衡因子。
//提示:建议结合图9.13(b)看,此处*T, lc, rd相当于图中A、B、C结点。
case LH:
(*T)->bf = RH;
lc->bf = EH;
break;
case EH:
(*T)->bf = EH;
lc->bf = EH;
break;
case RH:
(*T)->bf = EH;
lc->bf = LH;
break;
}
rd->bf = EH;
//对*T的左子树lc做左旋平衡处理
L_Rotate(&lc, *T);
//对*T左右旋平衡处理
R_Rotate(T, f);
break;
default:
break;
}
return ;
} //和左平衡算法是对称的,此处不再赘述
void RightBalance(BSTree *T, BSTree f){
BSTNode *rc = (BSTNode*)(*T)->rchild;
BSTNode *ld;
switch(rc->bf){
case LH:
//提示:建议结合图9.13(d)看,此处*T, rc, ld相当于图中的A、B、C结点。
ld = rc->lchild;
switch(ld->bf){
case LH:
(*T)->bf = EH;
rc->bf = RH;
break;
case EH:
(*T)->bf = EH;
rc->bf = EH;
break;
case RH:
(*T)->bf = LH;
rc->bf = EH;
break;
}
ld->bf = EH;
R_Rotate(&rc, *T);
L_Rotate(T, f);
break;
case RH:
rc->bf = (*T)->bf = EH;
L_Rotate(T, f);
break;
default:
break;
}
return ;
} /*平衡二叉树的插入算法
*
*若在平衡二叉排序树中T不存在和e有相同关键字的结点,则插入一个数据元素为e
*的新结点点,并返回TRUE;否则返回FALSE。若因插入而使二叉排序树失去平衡,则
*作平衡选择处理,布尔变量taller反映T长高与否。
*/
int InsertAVL(BSTree *T,BSTree f, ElemType e, Boolean *taller){
if(!(*T)){
//插入新结点,树"长高",置taller为TRUE,并返回TRUE
(*T) = (BSTree)malloc(sizeof(BSTNode));
(*T)->data = e;
(*T)->bf = EH;
(*T)->lchild = (*T)->rchild = NULL;
*taller = TRUE;
return TRUE;
}else{
if(EQ((*T)->data, e)){
//树中已经存在和e相同的结点,不再插入,并返回FALSE
*taller = FALSE;
return FALSE;
}
if(LT(e, (*T)->data)){
//应该继续在*T的左子树上进行搜索
BSTree *p = malloc(sizeof(BSTree));
*p = (BSTree)((*T)->lchild);
if(!InsertAVL(p, *T, e, taller)){
//未插入
free(p);
return FALSE;
}
//已插入到*T的左子树中, 更新*T的左子树结点
(*T)->lchild = *p;
if(*taller){
//左子树"长高",检查*T的平衡度
switch((*T)->bf){
case LH:
//原本左子树比右子树高,现在左子树上又长高了,需要作左平衡处理
LeftBalance(T, f);
(*T)->bf = EH;
*taller = FALSE;
break;
case EH:
//原本左子树和右子树等高,现在左子树上又长高了,现在*T的左子树比右子树高
(*T)->bf = LH;
*taller = TRUE;
break;
case RH:
//原本左子树和右子树矮,现在左子树上又长高了,现在*T的左子树比右子树等高
(*T)->bf = EH;
*taller = FALSE;
break;
}
}
free(p);
return TRUE;
}else{
//应该继续在*T的右子树上进行搜索
BSTree *p2 = malloc(sizeof(BSTree));
*p2= (BSTree)((*T)->rchild);
if(!InsertAVL(p2, *T, e, taller)){
//未插入
free(p2);
return FALSE;
}
//已插入到*T的右子树中, 更新*T的右子树结点
(*T)->rchild = *p2;
if(*taller){
//右子树"长高",检查*T的平衡度
switch((*T)->bf){
case LH:
//原本左子树比右子树高,现在右子树上长高了,现在*T的左子树比右子树等高
(*T)->bf = EH;
*taller = FALSE;
break;
case EH:
//原本左子树和右子树等高,现在右子树上长高了,现在*T的左子树比右子树矮
(*T)->bf = RH;
*taller = TRUE;
break;
case RH:
//原本左子树和右子树矮,现在右子树上长高了,需要作右平衡处理
RightBalance(T, f);
(*T)->bf = EH;
*taller = FALSE;
break;
}
}
free(p2);
return TRUE;
}
}
}
//二叉树先根遍历算法的函数声明
int PreOrderTraverse(BSTree T);
//二叉树中根遍历算法的函数声明
int InOrderTraverse(BSTree T);
//二叉树后根遍历算法的函数声明
int PostOrderTraverse(BSTree T);
//分别以先、中、后根遍历算法依次打印二叉树中的结点元素的函数声明
void print(BSTree T); int main(int argc, char *argv[])
{
if(argc < )
return FALSE;
int i = ;
ElemType e;
Boolean taller;
BSTree Tree = NULL;
for(i=; i<argc; i++){
e = atoi(argv[i]);
printf("插入数据: %d\n", e);
InsertAVL(&Tree, NULL, e, &taller);
print(Tree); }
return TRUE;
} //分别以先、中、后根遍历算法依次打印二叉树中的结点元素的函数实现
void print(BSTree T){
printf("先根遍历:\t");
PreOrderTraverse(T);
printf("\n"); printf("中根遍历:\t");
InOrderTraverse(T);
printf("\n"); printf("后根遍历:\t");
PostOrderTraverse(T);
printf("\n");
} //二叉树先根遍历算法的函数实现
int PreOrderTraverse(BSTree T){
if(T){
printf("[%-3d(%-2d)] ", ((BSTNode*)T)->data, ((BSTNode*)T)->bf);
PreOrderTraverse((BSTree)T->lchild);
PreOrderTraverse((BSTree)T->rchild);
}
return ;
} //二叉树中根遍历算法的函数实现
int InOrderTraverse(BSTree T){
if(T){
InOrderTraverse((BSTree)T->lchild);
printf("[%-3d(%-2d)] ", ((BSTNode*)T)->data, ((BSTNode*)T)->bf);
InOrderTraverse((BSTree)T->rchild);
}
return ;
} //二叉树后根遍历算法的函数实现
int PostOrderTraverse(BSTree T){
if(T){
PostOrderTraverse((BSTree)T->lchild);
PostOrderTraverse((BSTree)T->rchild);
printf("[%-3d(%-2d)] ", ((BSTNode*)T)->data, ((BSTNode*)T)->bf);
}
return ;
}

平衡二叉树

运行

查找->动态查找表->平衡二叉树的更多相关文章

  1. 查找->动态查找表->二叉排序树

    文字描述 二叉排序树的定义 又称二叉查找树,英文名为Binary Sort Tree, 简称BST.它是这样一棵树:或者是一棵空树:或者是具有下列性质的二叉树:(1)若它的左子树不空,则左子树上所有结 ...

  2. 查找->动态查找表->哈希表

    文字描述 哈希表定义 在前面讨论的各种查找算法中,都是建立在“比较”的基础上.记录的关键字和记录在结构中的相对位置不存在确定的关系,查找的效率依赖于查找过程中所进行的比较次数.而理想的情况是希望不经过 ...

  3. 查找->动态查找表->键树(无代码)

    文字描述 键树定义 键树又叫数字查找树,它是一棵度大于或等于2的树,树中的每个结点中不是包含一个或几个关键字,而是只含有组成关键字的符号.例如,若关键字是数值,则结点中只包含一个数位:若关键字是单词, ...

  4. 查找->动态查找表->B+树(无代码)

    文字描述 B+树定义 B+树是应文件系统所需而出的一种B-树的变型树.一棵m阶的B+树和m阶的B-树的差异在于: (1)有n棵子树的结点中含有n个关键字 (2)所有的叶子结点中包含了全部关键字的信息, ...

  5. C语言数据结构基础学习笔记——动态查找表

    动态查找表包括二叉排序树和二叉平衡树. 二叉排序树:也叫二叉搜索树,它或是一颗空树,或是具有以下性质的二叉树: ①若左子树不空,则左子树上所有结点的值均小于它的根结点的值: ②若右子树不空,则右子树上 ...

  6. 查找(顺序表&有序表)

    [1]查找概论 查找表是由同一类型是数据元素(或记录)构成的集合. 关键字是数据元素中某个数据项的值,又称为键值. 若此关键字可以唯一标识一个记录,则称此关键字为主关键字. 查找就是根据给定的某个值, ...

  7. Informatica 常用组件Lookup缓存之五 使用动态查找高速缓存

    对于关系查找,当目标表也是查找表时,可能要配置转换以使用动态高速缓存.PowerCenter 将在处理第一个查找请求时创建高速缓存.它将根据查找条件为传递给转换的每行查询高速缓存.当您使用动态高速缓存 ...

  8. 查找->静态查找表->分块查找(索引顺序表)

    文字描述 分块查找又称为索引顺序查找,是顺序查找的一种改进方法.在此查找算法中,除表本身外, 还需要建立一个”索引表”.索引表中包括两项内容:关键字项(其值为该字表内的最大关键字)和指针项(指示该子表 ...

  9. 查找->静态查找表->次优查找(静态树表)

    文字描算 之前分析顺序查找和折半查找的算法性能都是在“等概率”的前提下进行的,但是如果有序表中各记录的查找概率不等呢?换句话说,概率不等的情况下,描述查找过程的判定树为何类二叉树,其查找性能最佳? 如 ...

随机推荐

  1. linux每日命令(13):more命令

    more命令,功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上. more会以一页一页的显示方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会 ...

  2. 大津法---OTSU算法

    简介: 大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出.从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景 ...

  3. JVM 内部原理(一)— 概述

    JVM 内部原理(一)- 概述 介绍 版本:Java SE 7 图中显示组件将会从两个方面分别解释.第一部分涵盖线程独有的组件,第二部分涵盖独立于线程的组件(即线程共享组件). 目录 线程独享(Thr ...

  4. Amazon

    刚接到Recruiter电话,说恭喜,feedback都非常好. 心里大石落地,FLAG / UAT终于完成一家. 接下来就要加倍努力冲刺其他公司了. Mark: (入职以后一定要去地里补发一波面经, ...

  5. js调用winform程序(带参数)

    我们会发现,我们点击迅雷下载的时候  网页可以调用应用程序,而且连接会传入迅雷,这个是怎么做到的呢? 原理: 先注册表中添加软件的具体信息,然后通过 href 可以直接调用 1.写入注册表信息,注册, ...

  6. Java知多少(31)static关键字以及Java静态变量和静态方法

    static 修饰符能够与变量.方法一起使用,表示是“静态”的. 静态变量和静态方法能够通过类名来访问,不需要创建一个类的对象来访问该类的静态成员,所以static修饰的成员又称作类变量和类方法.静态 ...

  7. Mysql系列六:(Mycat分片路由原理、Mycat常用分片规则及对应源码介绍)

    一.Mycat分片路由原理 我们先来看下面的一个SQL在Mycat里面是如何执行的: , ); 有3个分片dn1,dn2,dn3, id=5000001这条数据在dn2上,id=10000001这条数 ...

  8. Virtualbox的centos7 nat和桥接网络配置

    在实际配置虚拟机的过程中,网络配置时候一个很繁琐的过程,经常一个点没注意到,就访问不了了.在此,做一个简单的教程以供后续使用时可以参考! 方法一: 使用NAT网络 1. 选择网卡 安装centos7的 ...

  9. Angular4学习笔记(八)- ng-content

    内容投影 ng-content ng-content是一个占位符,有些类似于router-outlet. 以前举の例子,父组件包含子组件都是直接指明子组件的selector,比如子组件的selecto ...

  10. MySQL主从介绍 准备工作 配置主 配置从 测试主从同步

    配置主: • 安装mysql • 修改my.cnf,增加server-id=130和log_bin=xiaobo1 • 添加环境变量 Vim /root/.bash_profile PATH=$PAT ...