文字描述

平衡二叉树(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每日命令(8):mv命令

    mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录. 一.命令格式: mv [选项] 源文件或目 ...

  2. 1. Tensorflow高效流水线Pipeline

    1. Tensorflow高效流水线Pipeline 2. Tensorflow的数据处理中的Dataset和Iterator 3. Tensorflow生成TFRecord 4. Tensorflo ...

  3. 【Unity笔记】打包安卓APK时Build Setting中的三种Build System

    Internal(Default):Unity内置,仅需Android SDK支持.不能导出工程,适用于仅适用Unity开发的工程. Gradle(New):使用Gradle进行构建,需要Androi ...

  4. 教你一招:Microsoft Office Word已停止工作

    1/按组合键WIN+R打开运行对话框 2/在打开框中键入%USERPROFILE%\AppData\Roaming\Microsoft\Templates,单击“确定”按钮 3/在打开的窗口鼠标右键删 ...

  5. hdoj:2027

    #include <iostream> #include <string> #include <vector> using namespace std; int m ...

  6. headfirst python 05, 06

    处理数据 with open('james.txt') as jaf: data = jaf.readLine() james = data.strip().split(',') #先去掉空格而否有, ...

  7. 稍稍解读下JDK8的HashMap

    首先,源码中上来就有一大段注释,但最重要的就是第一句. 大意如下: 本map经常用作一个 binned (bucketed) hash table (下面有解释),但是,当bins很大的时候,它们会被 ...

  8. Java关键字-----------------java中synchronized关键字的用法

    在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...

  9. java对象与map对象相互转换

    /** * 使用org.apache.commons.beanutils进行转换 */ class A { public static Object mapToObject(Map<String ...

  10. [转]搞个这样的 APP 要多久

    我有些尴尬地拿着水杯,正对面坐着来访的王总,他是在别处打拼的人,这几年据说收获颇丰,见移动互联网如火如荼,自然也想着要进来干一场,尽管王总从事的行当也算跟IT沾边,但毕竟太长时间不接触技术,有些东西不 ...