数据结构:平衡二叉树

平衡二叉树

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

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

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

在这里插入图片描述

图 1 平衡与不平衡的二叉树及结点的平衡因子

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

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

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

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

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

在这里插入图片描述

图 2 平衡二叉树

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

在这里插入图片描述

图 3 二叉排序树变为平衡二叉树的过程

当二叉排序树的平衡性被打破时,就如同扁担的两头出现了一头重一头轻的现象,如图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为根结点; 图 4 二叉排序树转化为平衡二叉树

    在这里插入图片描述

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

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

  • 单向右旋平衡处理: 图 5 单向右旋

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

    在这里插入图片描述

  • 单向左旋平衡处理:

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

    在这里插入图片描述

  • 双向旋转(先左后右)平衡处理: 图 7 双向旋转(先左后右)

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

    在这里插入图片描述

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

  • 双向旋转(先右后左)平衡处理: 图 8 双向旋转(先右后左)

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

    在这里插入图片描述

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

在对查找表{13,24,37,90,53}构建平衡二叉树时,由于符合第4条的规律,所以进行先右旋后左旋的处理,最终由不平衡的二叉排序树转变为平衡二叉树。

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

//分别定义平衡因子
#define LH +1
#define EH 0
#define RH -1typedef struct BSTNode{int data;int bf;BSTNode *lchild, *rchild;
}BSTNode, *BSTree;
//对以T为根结点的二叉树做右旋处理,令T指针指向新的根结点void R_Rotate(BSTree *T){BSTree lc = (*T)->lchild;(*T)->lchild = lc->rchild;lc->rchild = *T;*T = lc;
}
//对以T为根结点的二叉树做左旋处理,令T指针指向新的根结点
void L_Rotate(BSTree *T){BSTree rc = (*T)->rchild;(*T)->rchild = rc->lchild;rc->lchild = *T;*T = 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, int key, bool *taller){if ((*T) == NULL){//如果本身为空树,则直接添加key为根结点(*T) = new BSTNode;(*T)->bf = EH;(*T)->lchild = NULL;(*T)->rchild = NULL;(*T)->data = key;*taller = true;}else if ((*T)->data == key){//若二叉排序树中存在关键字key,则不做任何处理*taller = false;return 0;}else if ((*T)->data > key){//若关键字小于结点T的数据域,则插入到T的左子树if (!InsertAVL(&(*T)->lchild, key, taller)) return 0;//若插入不影响树的平衡,则直接结束if (*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;}}}else {//同样,当 key>T->data 时,需要插入到以 T 为根结点的树的右子树中,同样需要做和以上同样的操作if (!InsertAVL(&(*T)->rchild, key, 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;
}
//判断现有平衡二叉树中是否依据具有关键字key的结点
bool FindNode(BSTree T, int key, BSTree *pos){BSTree p = T;(*pos) = NULL;while (p){if (p->data == key){(*pos) = p;return true;}else if (p->data > key) p = p->lchild;else p = p->rchild;}return false;
}
//中序遍历
void InorderTra(BSTree T){if (T->lchild) InorderTra(T->lchild);cout << T->data << " ";if (T->rchild) InorderTra(T->rchild);
}int main(){int a[9] = {1, 23, 45, 34, 98, 9, 4, 35, 23};BSTree root = NULL, pos;bool taller;for (int i = 0; i < 9; i++){InsertAVL(&root, a[i], &taller);}if (FindNode(root, 45, &pos)) cout << pos->data << endl;InorderTra(root);return 0;
}

总结

使用平衡二叉树进行查找操作的时间复杂度为O(logn)。

在平衡失衡后,不管如何旋转其目的都是为了1.降低高度;2.保持二叉树的性质。所以我们可以将三个结点的关键字进行“比较”中间值为根结点,最小值为左子树,最大值为右子树,如图所示。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/108247.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Win10玩游戏老是弹回桌面的解决方法

在Win10电脑中&#xff0c;用户不仅可以办公&#xff0c;也可以畅玩各种各样的游戏。但是&#xff0c;有时候用户在玩游戏的时候&#xff0c;遇到了游戏老是自己弹回桌面的问题&#xff0c;这样是非常影响游戏体验的&#xff0c;却不清楚具体的解决方法。下面小编给大家带来了简…

系统架构与Tomcat的安装和配置

2023.10.16 今天是学习javaweb的第一天&#xff0c;主要学习了系统架构的相关知识和原理&#xff0c;下载了web服务器软件&#xff1a;Tomcat&#xff0c;并对其进行了配置。 系统架构 包括&#xff1a;C/S架构 和 B/S架构。 C/S架构&#xff1a; Client / Server&#xff0…

OJ项目——统一数据格式返回,我是如何处理的?

目录 前言 OJ项目中是如何处理的 1、准备一个类&#xff0c;作为统一的数据返回格式 2、准备一个类&#xff0c;实现ResponseBodyAdvice接口 3、我们如何写返回值更好 4、进一步优化返回值 小结 前言 关于SpringBoot的同一功能处理&#xff0c;本博主在这篇博客已经有介…

React + Router

React Router 这个只是专门讲解 React Router 新开的例子。 教程来源&#xff1a;https://reactrouter.com/en/main/start/tutorial 创建新项目 yarn create vite my-react-router-app --template react-ts cd my-react-router-app yarn安装 React Router 依赖: yarn add…

MatrixOne Logtail 设计解析

Logtail 是 CN&#xff08;Computation Node&#xff09;与 TN&#xff08;Transaction Node&#xff09;之间的一种日志同步协议&#xff0c;是 CN 和 TN 协同工作的基础。本文将介绍 logtail 协议的基本定位&#xff0c;协议内容和产生过程&#xff0c;也会提及一些遇到的挑战…

如何选择最适合你的LLM优化方法:全面微调、PEFT、提示工程和RAG对比分析

一、前言 自从ChatGPT问世以来&#xff0c;全球各地的企业都迫切希望利用大型语言模型&#xff08;LLMs&#xff09;来提升他们的产品和运营。虽然LLMs具有巨大的潜力&#xff0c;但存在一个问题&#xff1a;即使是最强大的预训练LLM也可能无法直接满足你的特定需求。其原因如…

微信小程序获取手机号(2023年10月 python版)[无需订阅]

技术栈&#xff1a; 1. 微信开发者工具中的调试基础库版本&#xff1a;3.1.2。 2. 后台&#xff1a;django。 步骤&#xff1a; 1. 首先在后台django项目的定时任务中增加一个下载access_token函数&#xff0c;并把得到的access_token保存在数据库中&#xff08;其实随便哪里…

INTELlij IDEA编辑VUE项目

菜单中选择setting–>Plugins 或者快捷键 ctrlalts 搜索vue&#xff0c;但有些情况会搜索不出来&#xff0c;先说搜索到的情况 如下图所示&#xff1a; 如果没有vue.js则说明过去已经安装了。 搜索到了后点击Install安装即可&#xff0c; 但即使搜索成功了&#xff0c;也不…

功夫猫小游戏

欢迎来到程序小院 功夫猫 玩法&#xff1a; 对准对方猫点击鼠标左键进行扑街&#xff0c;碰到敌方猫扑街X1&#xff0c;不要让对方猫碰到自己&#xff0c;统计扑街次数&#xff0c;快去玩功夫猫吧^^。开始游戏https://www.ormcc.com/play/gameStart/189 html <canvas id&q…

非技术背景项目经理如何发展?

非技术背景的项目经理在现代企业中扮演着重要的角色&#xff0c;他们负责协调和管理项目的各个方面&#xff0c;确保项目按时、按预算和按质量要求完成。对于没有技术背景的项目经理来说&#xff0c;他们需要通过一些特定的方法和策略来发展自己的职业生涯。 首先&#xff0c;…

Mac下通过nvm管理node

背景 本地有两个项目&#xff0c;老项目需要用到node 14&#xff0c;新项目需要用node 16&#xff0c;所以只能通过nvm来管理node了 卸载原始的node 我的node是通过官网的.pkg文件安装的&#xff0c;可以通过以下命令进行删除 sudo rm -rf /usr/local/{bin/{node,npm},lib/…

Python数据挖掘入门进阶与实用案例:自动售货机销售数据分析与应用

文章目录 写在前面01 案例背景02 分析目标03 分析过程04 数据预处理1. 清洗数据2.属性选择3.属性规约 05 销售数据可视化分析1.销售额和自动售货机数量的关系2.订单数量和自动售货机数量的关系3.畅销和滞销商品4.自动售货机的销售情况5.订单支付方式占比6.各消费时段的订单用户…

【LVS】lvs的四种模式的区别是什么?

LVS中的DR模式、NAT模式、TUN模式和FANT模式是四种不同的负载均衡模式&#xff0c;它们之间的主要区别在于数据包转发方式和网络地址转换。 DR模式&#xff08;Direct Routing&#xff09;&#xff1a;此模式通过改写请求报文的目标MAC地址&#xff0c;将请求发给真实服务器&a…

爬虫学习日记第七篇(爬取github搜索仓库接口,其实不算爬虫)

github提供的搜索仓库的API https://api.github.com/ # 连接数据库 db mysql.connector.connect(host"***",user"***",password"***",database"***" ) # 创建游标 cursor db.cursor() # 从数据库中读取CVE ID cursor.execute("…

day06-前后端项目上传到gitee、后端多方式登录接口、发送短信功能、发送短信封装、短信验证码接口、短信登录接口

1 前后端项目上传到gitee 2 后端多方式登录接口 2.1 序列化类 2.2 视图类 2.3 路由 3 发送短信功能 4 发送短信封装 4.0 目录结构 4.1 settings.py 4.2 sms.py 5 短信验证码接口 6 短信登录接口 6.1 视图类 6.2 序列化类 1 前后端项目上传到gitee # 我们看到好多开源项目…

2310如何维护旧代码

从编译原来的代码,12k错误,转为正常使用. 1,把原来的全局代码加上: 流冲 入; #include "小流函数.cpp"这是以前的全局函数,以前的错误写法,为此,继续加上. 2,然后编译过了,就是运行时出错. 打印("符",c);来定位出错位置. 3,为串与现有的为串冲突了,因此改…

通过循环生成多个echarts图表并实现自适应

不推荐使用grid布局&#xff0c;不清楚为什么左边一列的不会自适应&#xff0c;换成flex布局就可以了 主要方法借助中的getInstanceByDom方法 完整代码&#xff1a; <template><div class"statis"><div class"content" ><!-- v-for …

编辑器功能:用一个快捷键来【锁定】或【解开】Inspector面板

一、需求 我有一个脚本&#xff0c;上面暴露了许多参数&#xff0c;我要在场景中拖物体给它进行配置。 如果不锁定Inspector面板的话&#xff0c;每次点击物体后&#xff0c;Inspector的内容就是刚点击的物体的内容&#xff0c;而不是挂载脚本的参数面板。 二、 解决 &…

Vue项目使用svg之svg-sprite-loader详细使用

项目中为了体验好、性能优、资源丰富等原因经常会用svg这种矢量图&#xff0c;但是svg不能直接像image标签一样直接使用&#xff0c;这就需要前端的同学自己处理了。 svg有以下优点&#xff1a; svg放大不失真,png&#xff0c;jpg会出现失真现象svg的体积非常小&#xff0c;对…