欢迎关注我的公众号是【CodeAllen】,关注回复【1024】获取精品学习资源
程序员技术交流①群:736386324 ,程序员技术交流②群:371394777

平衡二叉排序树

平衡二叉树是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1

有两位俄罗斯数学家 G.M. Adelson-Velsky和E.M. Landis发明一种解决平衡二叉树的算法,所以很多资料称这样的平衡二叉树为AVL树

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

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

二叉排序树转化为平衡二叉树

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

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

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

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

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

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

  2. 如图 4(c) 所示,将以结点 37 为根结点的子树向左逆时针旋转,使本该以 37 为根结点的子树,改为以结点 53 为根结点;

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

当平衡二叉树由于新增数据元素导致整棵树的平衡遭到破坏时,就需要根据实际情况做出适当的调整,假设距离插入结点最近的“不平衡因子”为 a。则调整的规律可归纳为以下 4 种情况:

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

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

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

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

在对查找表{13,24,37,90,53}构建平衡二叉树时,由于符合第 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语言】57 平衡二叉树(AVL树)相关推荐

  1. java数据结构与算法之平衡二叉树(AVL树)的设计与实现中的事实代码

    普通二叉查找树的问题   在开篇,我们提到过,普通二叉树(二叉查找树)在操作的时间复杂度上不一定遵循O(㏒n),也有可能是O(n),这是为什么呢?在上一篇中,我们明明插入都按照一定规则比较的呀,其实那 ...

  2. Python数据结构11:树的实现,树的应用,前中后序遍历,二叉查找树BST,平衡二叉树AVL树,哈夫曼树和哈夫曼编码

    1.概念 树一种基本的"非线性"数据结构. 相关术语: 节点Node:组成树的基本部分.每个节点具有名称,或"键值",节点还可以保存额外数据项,数据项根据不同的 ...

  3. Java数据结构——平衡二叉树(AVL树)

    AVL树的引入 搜索二叉树有着极高的搜索效率,但是搜索二叉树会出现以下极端情况: 这样的二叉树搜索效率甚至比链表还低.在搜索二叉树基础上出现的平衡二叉树(AVL树)就解决了这样的问题.当平衡二叉树(A ...

  4. 一种基于平衡二叉树(AVL树)插入、查找和删除的简易图书管理系统

    目录 1. 需求分析 2. 项目核心设计 2.1 结点插入 2.2 结点删除 3 测试结果 4 总结分析 4.1 调试过程中的问题是如何解决的,以及对设计与实现的回顾讨论和分析 4.2 算法的时间和空 ...

  5. 数据结构(C语言版)严蔚敏(树、二叉树的相关概念笔记)

    数据结构(C语言版)严蔚敏(树的相关概念笔记) 1. 树中一个节点的孩子个数称为该节点的度,树中节点的最大度数称为树的度: 2. 度大于0的节点称为[分支节点](非终端节点),度为0的节点称为[叶子节 ...

  6. 数据结构-平衡二叉树(AVL树)

    目录 1,平衡二叉树的介绍 1.1,二叉排序树存在的问题 1.2,平衡二叉树 1.3,平衡二叉树的创建 1.4,平衡二叉树的查找 2,代码实现 2.1,平衡二叉树的节点类型 2.2,LL旋转(单右旋转 ...

  7. 数据结构——平衡二叉树(AVL树)之插入

    文章目录 前言 一.定义 二.基本操作 1.查找, 2.插入(如何调整) 如何调整 代码实现插入 前言 首先我们来思考一下一个普通二叉树保存数据,如果想查找一个数据,由于普通二叉树保存数据是随机的,要 ...

  8. 平衡二叉树的构造c语言,平衡二叉树(C语言,又称AVL树,实现LeftBalance,RightBalance)...

    1. 背景 因为二叉排序树在平衡的情况下查询效率最佳,AVL树让二叉排序树节点变动后维持平衡(左子树高度-右子树高度 差的绝对值<2) 2. 算法原理 根据节点的平衡因子及插入结果,实际修正二叉 ...

  9. 【从蛋壳到满天飞】JS 数据结构解析和算法实现-AVL树(一)

    前言 [从蛋壳到满天飞]JS 数据结构解析和算法实现,全部文章大概的内容如下: Arrays(数组).Stacks(栈).Queues(队列).LinkedList(链表).Recursion(递归思 ...

最新文章

  1. 一份来自上海院校的考研预调剂系统已开放名单!
  2. WebSocket实战之————Workerman服务器的安装启动
  3. 转:SLAM算法解析:抓住视觉SLAM难点,了解技术发展大趋势
  4. 进化:一个平庸人的互联网之路
  5. 《王者荣耀》游戏技术总监:技术架构与同步方案上做出改变?
  6. java 编译原理 字符串_Java编译原理(javac)
  7. linux安装nagios客户端
  8. apache ignite_使用Spring Data的Apache Ignite
  9. apache mahout_Apache Mahout:入门
  10. 用MATLAB玩转机器人-第1章 认识MATLAB
  11. Android UI:机智的远程动态更新策略
  12. 开源非英文关键词编程语言
  13. my new start
  14. 【Linux】快速入门法宝~你值得拥有
  15. 2017 Google IO 开发者大会直播入口
  16. HTML如何实现滚动文字
  17. 精彩回顾 | Dev.Together 2022 开发者生态峰会圆满落幕
  18. 使用conda时出现Solving environment: failed with initial frozen solve. Retrying with flexible solve错误
  19. 「Java工具类」汉语转拼音工具类HanyuPinyinHelper.java
  20. 移动开发者大会 -- 后感

热门文章

  1. 扫地机器人拖实木地板_云鲸拖扫一体机,自动清洗拖布这个方案解决了这类产品的一个痛点...
  2. 小厨房设计软件测试,小厨房只要设计的合理 照样可以轻松在家做大餐
  3. 简单理解hibernate懒加载
  4. A quick first look at the kernel printk()
  5. 这才是数字孪生污水处理厂该有的样子 | 智慧水务
  6. Kotlin高仿微信-第35篇-支付-二维码收款(二维码)
  7. POJ1753 flip Game翻转棋盘
  8. ESP32学习一-程序下载(windows工具)
  9. JAVA 类的继承(私有属性、自动转型)(入门级小白一看就懂)
  10. Joshua Porter 20条UI设计原则