查找->动态查找表->平衡二叉树
文字描述
平衡二叉树(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 ;
}
平衡二叉树
运行
查找->动态查找表->平衡二叉树的更多相关文章
- 查找->动态查找表->二叉排序树
文字描述 二叉排序树的定义 又称二叉查找树,英文名为Binary Sort Tree, 简称BST.它是这样一棵树:或者是一棵空树:或者是具有下列性质的二叉树:(1)若它的左子树不空,则左子树上所有结 ...
- 查找->动态查找表->哈希表
文字描述 哈希表定义 在前面讨论的各种查找算法中,都是建立在“比较”的基础上.记录的关键字和记录在结构中的相对位置不存在确定的关系,查找的效率依赖于查找过程中所进行的比较次数.而理想的情况是希望不经过 ...
- 查找->动态查找表->键树(无代码)
文字描述 键树定义 键树又叫数字查找树,它是一棵度大于或等于2的树,树中的每个结点中不是包含一个或几个关键字,而是只含有组成关键字的符号.例如,若关键字是数值,则结点中只包含一个数位:若关键字是单词, ...
- 查找->动态查找表->B+树(无代码)
文字描述 B+树定义 B+树是应文件系统所需而出的一种B-树的变型树.一棵m阶的B+树和m阶的B-树的差异在于: (1)有n棵子树的结点中含有n个关键字 (2)所有的叶子结点中包含了全部关键字的信息, ...
- C语言数据结构基础学习笔记——动态查找表
动态查找表包括二叉排序树和二叉平衡树. 二叉排序树:也叫二叉搜索树,它或是一颗空树,或是具有以下性质的二叉树: ①若左子树不空,则左子树上所有结点的值均小于它的根结点的值: ②若右子树不空,则右子树上 ...
- 查找(顺序表&有序表)
[1]查找概论 查找表是由同一类型是数据元素(或记录)构成的集合. 关键字是数据元素中某个数据项的值,又称为键值. 若此关键字可以唯一标识一个记录,则称此关键字为主关键字. 查找就是根据给定的某个值, ...
- Informatica 常用组件Lookup缓存之五 使用动态查找高速缓存
对于关系查找,当目标表也是查找表时,可能要配置转换以使用动态高速缓存.PowerCenter 将在处理第一个查找请求时创建高速缓存.它将根据查找条件为传递给转换的每行查询高速缓存.当您使用动态高速缓存 ...
- 查找->静态查找表->分块查找(索引顺序表)
文字描述 分块查找又称为索引顺序查找,是顺序查找的一种改进方法.在此查找算法中,除表本身外, 还需要建立一个”索引表”.索引表中包括两项内容:关键字项(其值为该字表内的最大关键字)和指针项(指示该子表 ...
- 查找->静态查找表->次优查找(静态树表)
文字描算 之前分析顺序查找和折半查找的算法性能都是在“等概率”的前提下进行的,但是如果有序表中各记录的查找概率不等呢?换句话说,概率不等的情况下,描述查找过程的判定树为何类二叉树,其查找性能最佳? 如 ...
随机推荐
- 【iCore4 双核心板_ARM】例程十九:USBD_MSC实验——虚拟U盘
实验步骤: 1.将SD卡插在SD卡槽中. 2.将跳线冒跳至USB_OTG,将USB_OTG通过Micor USB线与USB主机(电脑)相连. 3.烧写程序,我的电脑中将出现一个磁盘. 实验现象: 核心 ...
- 关于ECMP 等价路由
1.ECMP简介 Equal-CostMultipathRouting,等价多路径.即存在多条到达同一个目的地址的相同开销的路径.当设备支持等价路由时,发往该目的 IP 或者目的网段的三层转发流量就可 ...
- vector、map 内存释放
一.vector void TestVector() { cout << "begin create vector" << endl; int iSize ...
- Guava学习笔记(一):Maven
<dependencies> <dependency> <groupId>com.google.guava</groupId> <artifact ...
- @Autowired注入为null问题分析
题说明 最近看到Spring事务,在学习过程中遇到一个很苦恼问题 搭建好Spring的启动环境后出现了一点小问题 在启动时候却出现[java.lang.NullPointerException] 不过 ...
- Spring 整合 Junit4 进行单元测试
1. pom.xml 引入JAR依赖: <dependency> <groupId>junit</groupId> <artifactId>junit& ...
- Nginx-设定允许的ip和要拒绝的ip
作用范围和配置的顺序有关系,先配置的优先级高,会覆盖和后一个配置重合的部分, 可以添加多个allow和多个deny: 1)这个配置127.0.0.1可以通过访问. allow 127.0.0.1; d ...
- [Linux] 设置系统时区
1. 检查当前时区 以 root 身份登录. # date Fri Sep :: UTC 其中 UTC 是指当前使用的时间系统为世界标准时间,也称世界协调时间.英文名称为 Coordinated Un ...
- 【win10】更改资源管理器显示:快速访问和此电脑
通常,我习惯通过按 win+E来打开资源管理器,然后显示各个分区并进行操作.在win10打开资源管理器默认显示的是快速访问,并不是显示的分区.下面是修改步骤. 1.按Win+E打开资源管理器,点击[查 ...
- springCloud学习之服务注册和发现
leader让完一个简单的springcloud的demo,自己之前听说过springcloud微服务,但是没有重视.现在网上查各种资料,但是感觉不怎么样啊,还是不会,明天晚上把代码给他看,天啦,这个 ...