写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。

目录:
1.平衡二叉树简介
2.二叉排序树转换平衡为平衡二叉树
3.不平衡因子的四种情况
4.构建平衡二叉树的代码实现

1.平衡二叉树简介

平衡二叉树,又称为 AVL 树。实际上就是遵循以下两个特点的二叉树:

  • 每棵子树中的左子树和右子树的深度差不能超过 1;
  • 二叉树中每棵子树都要求是平衡二叉树;

其实就是在二叉树的基础上,若树中每棵子树都满足其左子树和右子树的深度差都不超过 1,则这棵二叉树就是平衡二叉树。

如下图所示,其中 (a) 的两棵二叉树中由于各个结点的平衡因子数的绝对值都不超过 1,所以 (a) 中两棵二叉树都是平衡二叉树;而 (b) 的两棵二叉树中有结点的平衡因子数的绝对值超过 1,所以都不是平衡二叉树。

平衡因子:每个结点都有其各自的平衡因子,表示的就是其左子树深度同右子树深度的差。平衡二叉树中各结点平衡因子的取值只可能是:0、1 和 -1。

2.二叉排序树转换平衡为平衡二叉树

为了排除动态查找表中不同的数据排列方式对算法性能的影响,需要考虑在不会破坏二叉排序树本身结构的前提下,将二叉排序树转化为平衡二叉树。

例如,使用上一节的算法在对查找表{13,24,37,90,53}构建二叉排序树时,当插入 13 和 24 时,二叉排序树此时还是平衡二叉树:

当继续插入 37 时,生成的二叉排序树如下图 (a),平衡二叉树的结构被破坏,此时只需要对二叉排序树做“旋转”操作(如下图 (b)),即整棵树以结点 24 为根结点,二叉排序树的结构没有破坏,同时将该树转化为了平衡二叉树:


当二叉排序树的平衡性被打破时,就如同扁担的两头出现了一头重一头轻的现象,如下图(a)所示,此时只需要改变扁担的支撑点(树的树根),就能使其重新归为平衡。实际上图b中的 (b) 是对(a) 的二叉树做了一个向左逆时针旋转的操作。

继续插入 90 和 53 后,二叉排序树如下图(a)所示,导致二叉树中结点 24 和 37 的平衡因子的绝对值大于 1 ,整棵树的平衡被打破。此时,需要做两步操作:

  1. 如下图(b) 所示,将结点 53 和 90 整体向右顺时针旋转,使本该以 90 为根结点的子树改为以结点 53 为根结点;
  2. 如下图 (c) 所示,将以结点 37 为根结点的子树向左逆时针旋转,使本该以 37 为根结点的子树,改为以结点 53 为根结点;

做完以上操作,即完成了由不平衡的二叉排序树转变为平衡二叉树。

3.不平衡因子的四种情况

1.单向右旋平衡处理:若由于结点 a 的左子树为根结点的左子树上插入结点,导致结点 a 的平衡因子由 1 增至 2,致使以 a 为根结点的子树失去平衡,则只需进行一次向右的顺时针旋转,如下图这种情况:

2.单向左旋平衡处理:如果由于结点 a 的右子树为根结点的右子树上插入结点,导致结点 a 的平衡因子由 -1变为 -2,则以 a 为根结点的子树需要进行一次向左的逆时针旋转,如下图这种情况:

3.双向旋转(先左后右)平衡处理:如果由于结点 a 的左子树为根结点的右子树上插入结点,导致结点 a 平衡因子由 1 增至 2,致使以 a 为根结点的子树失去平衡,则需要进行两次旋转操作,如下图这种情况:

上图中插入结点也可以为结点 C 的右孩子,则(b)中插入结点的位置还是结点 C 右孩子,(c)中插入结点的位置为结点 A 的左孩子。

4.双向旋转(先右后左)平衡处理:如果由于结点 a 的右子树为根结点的左子树上插入结点,导致结点 a 平衡因子由 -1 变为 -2,致使以 a 为根结点的子树失去平衡,则需要进行两次旋转(先右旋后左旋)操作,如下图这种情况:

上图中插入结点也可以为结点 C 的右孩子,则(b)中插入结点的位置改为结点 B 的左孩子,(c)中插入结点的位置为结点 B 的左孩子

4.构建平衡二叉树的代码实现

#include <stdio.h>
#include <stdlib.h>
//分别定义平衡因子数
#define LH +1
#define EH  0
#define RH -1
typedef int ElemType;
typedef enum {false,true} bool;
//定义二叉排序树
typedef struct BSTNode{ElemType data;int bf;//balance flagstruct BSTNode *lchild,*rchild;
}*BSTree,BSTNode;
//对以 p 为根结点的二叉树做右旋处理,令 p 指针指向新的树根结点
void R_Rotate(BSTree* p)
{//借助文章中的图 5 所示加以理解,其中结点 A 为 p 指针指向的根结点BSTree lc = (*p)->lchild;(*p)->lchild = lc->rchild;lc->rchild = *p;*p = lc;
}
对以 p 为根结点的二叉树做左旋处理,令 p 指针指向新的树根结点
void L_Rotate(BSTree* p)
{//借助文章中的图 6 所示加以理解,其中结点 A 为 p 指针指向的根结点BSTree rc = (*p)->rchild;(*p)->rchild = rc->lchild;rc->lchild = *p;*p = rc;
}
//对以指针 T 所指向结点为根结点的二叉树作左子树的平衡处理,令指针 T 指向新的根结点
void LeftBalance(BSTree* T)
{BSTree lc,rd;lc = (*T)->lchild;//查看以 T 的左子树为根结点的子树,失去平衡的原因,如果 bf 值为 1 ,则说明添加在左子树为根结点的左子树中,需要对其进行右旋处理;反之,如果 bf 值为 -1,说明添加在以左子树为根结点的右子树中,需要进行双向先左旋后右旋的处理switch (lc->bf){case LH:(*T)->bf = lc->bf = EH;R_Rotate(T);break;case RH:rd = lc->rchild;switch(rd->bf){case LH:(*T)->bf = RH;lc->bf = EH;break;case EH:(*T)->bf = lc->bf = EH;break;case RH:(*T)->bf = EH;lc->bf = LH;break;}rd->bf = EH;L_Rotate(&(*T)->lchild);R_Rotate(T);break;}
}
//右子树的平衡处理同左子树的平衡处理完全类似
void RightBalance(BSTree* T)
{BSTree lc,rd;lc= (*T)->rchild;switch (lc->bf){case RH:(*T)->bf = lc->bf = EH;L_Rotate(T);break;case LH:rd = lc->lchild;switch(rd->bf){case LH:(*T)->bf = EH;lc->bf = RH;break;case EH:(*T)->bf = lc->bf = EH;break;case RH:(*T)->bf = EH;lc->bf = LH;break;}rd->bf = EH;R_Rotate(&(*T)->rchild);L_Rotate(T);break;}
}int InsertAVL(BSTree* T,ElemType e,bool* taller)
{//如果本身为空树,则直接添加 e 为根结点if ((*T)==NULL){(*T)=(BSTree)malloc(sizeof(BSTNode));(*T)->bf = EH;(*T)->data = e;(*T)->lchild = NULL;(*T)->rchild = NULL;*taller=true;}//如果二叉排序树中已经存在 e ,则不做任何处理else if (e == (*T)->data){*taller = false;return 0;}//如果 e 小于结点 T 的数据域,则插入到 T 的左子树中else if (e < (*T)->data){//如果插入过程,不会影响树本身的平衡,则直接结束if(!InsertAVL(&(*T)->lchild,e,taller))return 0;//判断插入过程是否会导致整棵树的深度 +1if(*taller){//判断根结点 T 的平衡因子是多少,由于是在其左子树添加新结点的过程中导致失去平衡,所以当 T 结点的平衡因子本身为 1 时,需要进行左子树的平衡处理,否则更新树中各结点的平衡因子数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;}}}//同样,当 e>T->data 时,需要插入到以 T 为根结点的树的右子树中,同样需要做和以上同样的操作else{if(!InsertAVL(&(*T)->rchild,e,taller))return 0;if (*taller){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 1;
}
//判断现有平衡二叉树中是否已经具有数据域为 e 的结点
bool FindNode(BSTree root,ElemType e,BSTree* pos)
{BSTree pt = root;(*pos) = NULL;while(pt){if (pt->data == e){//找到节点,pos指向该节点并返回true(*pos) = pt;return true;}else if (pt->data>e){pt = pt->lchild;}elsept = pt->rchild;}return false;
}
//中序遍历平衡二叉树
void InorderTra(BSTree root)
{if(root->lchild)InorderTra(root->lchild);printf("%d ",root->data);if(root->rchild)InorderTra(root->rchild);
}int main()
{int i,nArr[] = {1,23,45,34,98,9,4,35,23};BSTree root=NULL,pos;bool taller;//用 nArr查找表构建平衡二叉树(不断插入数据的过程)for (i=0;i<9;i++){InsertAVL(&root,nArr[i],&taller);}//中序遍历输出InorderTra(root);//判断平衡二叉树中是否含有数据域为 103 的数据if(FindNode(root,103,&pos))printf("\n%d\n",pos->data);elseprintf("\nNot find this Node\n");return 0;
}

本篇博客转载C语言中文网

平衡二叉排序树(完整案例详解及完整C代码实现)相关推荐

  1. 【工厂扫码打印扫码装箱错误追溯系统】完整案例详解(PythonPyQt 源码Mysql数据库)

    一. 市场需求 目前很多工厂产品装箱过程中仍存在一些问题: 商品打包发货出错,少发,错发,漏发 --- 追溯问题到底出在哪个环节? 手工制作装箱单,发货单,打印商品条码标签,外箱标签 --- 花费太多 ...

  2. 《工厂订单出入库信息管理系统》完整案例详解(含演示网址账号)(GoVue源码MysqlRedis数据库)

    近期开发了一套工厂订单及出入库信息管理系统,现在系统已经正式上线,我也抽出时间对之前的工作了进行了二次整理,在总结的过程中继续完善. 系统演示网址:出入库系统   演示账号:admin 密码:1234 ...

  3. 数据可视化大屏应急管理综合指挥调度系统完整案例详解(PHP-API、Echarts、百度地图)

    文章目录 项目说明 一.项目说明 单位信息数据库字段: 资源数据库字段 项目需求 二.项目开发 1.项目分析 2.引入库 3.项目开发 (1)地图容器构建 (2)筛选和返回按钮事件 (3)企业筛选功能 ...

  4. 【案例详解-带完整源码】C#通过调用python生成热力图

    目录 效果 方法 C#端 python端 注意事项 源码 源码具体调试步骤 效果 将二维数组 转为热力图: 方法 由于C#没有方便的热力图工具,因此可以调用python程序来生成热力图 C#端 // ...

  5. csv导入mysql_京东金融数据分析:MySQL+HIVE的结合应用案例详解【附全代码】

    - 点击上方"中国统计网"订阅我吧!-  文末领取[案例数据集+全部代码] 给定的数据为业务情景数据,所有数据均已进⾏了采样和脱敏处理,字段取值与分布均与真实业务数据不同.提供了时 ...

  6. Sentry For Vue 完整接入详解(2021 Sentry v21.8.x)前方高能预警!三万字,慎入!

    内容源于:https://docs.sentry.io/platforms/javascript/guides/vue/ 系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创 ...

  7. java json path_Java使用JSONPath解析JSON完整内容详解

    JsonPath是一种简单的方法来提取给定JSON文档的部分内容. JsonPath有许多编程语言,如Javascript,Python和PHP,Java. JsonPath提供的json解析非常强大 ...

  8. [opencv完整项目详解] 传统图像算法解决路标的检测和识别(改进升级版)

    之前路标匹配[opencv完整项目详解] 传统图像算法解决路标的检测和识别 的一个改进版. 之前路标匹配存在的一个问题: 所有路标与模板的相似度都处于较高状态(基本都在50%以上),其主要原因就是虽然 ...

  9. [opencv完整项目详解] 传统图像算法解决路标的检测和识别

    前言: 这是数字图像课程的大作业,老师要求不可以采用深度学习的方法检测和识别特定的路标,只能采用传统的图像算法提取特征从而检测出特定的车牌. 参考文章: https://blog.csdn.net/m ...

最新文章

  1. 刚刚!美团取消支付宝支付
  2. 机器学习狗太苦逼了!自动化调参哪家强?
  3. PetShop 4数据库分析一
  4. _mysql.c(42) : fatal error C1083: Cannot open include file: 'config-win.h':no such file or directory
  5. HyperLogLog原理与在Redis中的使用
  6. HashTable 基础
  7. iOS开发UI篇—常见的项目文件介绍
  8. Linux 操作系统基础知识总结
  9. 机器学习十大算法实现python代码汇总
  10. 超越GraphCL,GNN+对比学习的节点分类新SOTA
  11. Shiro源码-创建subject
  12. DOM初探(14)——查看滚动条的滚动距离
  13. c语言双重性,C语言双重循环应用初探
  14. 菜鸟学JAVA之——常用类(StringBuffer、StringBuilder、Comparable、Comparator等)
  15. 脑电波-使用意念说话
  16. 2018 年,我在朋友圈的碎碎念
  17. Java实战之管家婆记账系统(5)——主界面及功能实现
  18. 6G研究悄然启动,马斯克参与其中,汽车行业受益几何?
  19. A类、B类、C类IP地址区别,同一网段是?
  20. Unity中使用SteamVR Plugin时不显示HTC Vive手柄控制器的解决办法

热门文章

  1. Python--day46--MySQL视图
  2. 使用Python将Excel中的数据导入到MySQL
  3. .NET Core 控制台应用程序使用异步(Async)Main方法
  4. 如何多快好省的建设企业级呼叫中心(一)
  5. 简便方法创建自签名证书
  6. 第三章 Selenide测试框架(三)
  7. 用WebCollector爬取新浪微博数据
  8. 作业调度框架_Quartz
  9. 035、Linux下Dmidecode查看硬件信息
  10. 可口的JAVA-并发控制之CountDownLatch