【数据结构Note5】-二叉排序树 BST和平衡二叉树AVL

news2024/11/29 4:34:12

二叉排序树BST

二叉排序树,又称二叉查找树(BST,Binary Search Tree)

二叉排序树是左子树节点值<根节点值<右子树节点值的二叉树

image-20221113132722225

所以对二叉排序树进行中序遍历会得到一个递增的序列(左子树-根-右子树)

1. 二叉排序树的查找

若树非空,目标值与根结点的值比较

  1. 若相等,则查找成功;
  2. 若小于根结点,则在左子树上查找
  3. 若大于根节点在右子树上查找。

查找成功,返回结点指针;查找失败返回NULL,分为递归和非递归两种算法

排序二叉树非递归查找:最坏时间复杂度O(1)-nice

//在二叉排序树中查找值为key 的结点
BSTNode* BST_Search(BSTNode* T, BSTDataType key) {
	BSTNode* cur = T;
	while (cur != NULL) {//指针空则结束循环
		if (key == cur->data) return cur;
		else if (key < cur->data)cur = cur->left;//小于查找左子树
		else cur = cur->right;//大于查找右子树
	}
	return cur;//此时cur就是NULL 
}

排序二叉树递归查找:比根节点大,就到右子树查找。比根节点小,就到左子树查找。排序二叉树的递归查找最坏时间复杂度为O(n),最坏情况就是递归到排序二叉树的最大深度

//在二叉排序树中查找值为key 的结点
BSTNode* BST_Search(BSTNode* root, BSTDataType key) {
	if (!root)return NULL;
	else if (key == root->data)return root;
	else if (key < root->data) return BST_Search(root->left,key);
	else  return BST_Search(root->right, key);
}

2. 二叉排序树的插入

若原二叉排序树为空,则直接插入结点;否则,若关键字k小于根结点值,则插入到左子树,若关键字k大于根结点值,则插入到右子树

可以发现插入的位置一定是叶子节点的下方!二叉排序树的插入就是二叉排序树不断向下延申的过程,不会出现中间插入的情况。同一组节点可以形成不同的二叉排序树的结构,但其中序遍历一定都是递增数列。

二叉排序树的插入算法有递归和非递归。

二叉排序树递归插入算法:最坏空间复杂度为O(n)

//插入节点
bool insert(BSTNode*& root, BSTDataType data) {
	if (root == NULL) {
		root = (BSTNode*)malloc(sizeof(BSTNode));
		root->data = data;
		root->left = NULL;
		root->right = NULL;
		return true;
	}
	else if (data == root->data) {
		return false;//树中存在下个相同关键字结点,插入失败 
	}
	else if (data < root->data) {
		insert(root->left, data);
	}
	else {
		insert(root->right, data);
	}
}

二叉排序树非递归插入算法:

//插入节点
bool insert(BSTNode*& root, BSTDataType data) {
	//空树直接填充
	if (!root) {
		root = (BSTNode*)malloc(sizeof(BSTNode));
		root->left = NULL;
		root->right = NULL;
		root->data = data;
		return true;
	}

    //非空二叉排序树需要找到,需要插入位置的根结点!
	BSTNode* cur = root;
	while (true) {
		if (data < cur->data) {
			if (cur->left == NULL) {
				BSTNode* temp = (BSTNode*)malloc(sizeof(BSTNode));
				temp->left = NULL;
				temp->right = NULL;
				temp->data = data;
				cur->left = temp;
				return true;
			}
			else cur = cur->left;
		}
		else if (data > cur->data) {
			if (cur->right == NULL) {
				BSTNode* temp = (BSTNode*)malloc(sizeof(BSTNode));
				temp->left = NULL;
				temp->right = NULL;
				temp->data = data;
				cur->right = temp;
				return true;
			}
			else cur = cur->right;
		}
		else {
			return false;//树中存在相等的结点,插入失败
		}
	}
}

3. 二叉排序树的构造

实际上就是根据数值,不断进行二叉树插入操作的过程。所以这里需要引用上面二叉排序树的插入函数

参考代码如下:

void creatBSTree(BSTNode*& root,int* array,int arrayLength){
    root=NULL;
    for(int i=0;i<arrayLength;i++){
    	insert(root,array[i]);
    }
}

值得注意,不同的序列构建的二叉排序树可能一样,也可能不一样。但这些二叉排序树的中序遍历一定都是一样的。

image-20221113153128573

4. 二叉排序树的删除

先搜索找到目标结点-(前面右谈过二叉排序树的查找函数)

插入的宗旨就是不会破坏二叉排序树的性质(左子树节点值<根节点值<右子树节点值)

  1. 若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。

  2. 若结点z只有左子树或只有右子树,则让z的子树成为z父结点的子树,替代z的位置。

    image-20221113154547625
  3. 若结点z有左、右两棵子树,则令结点z的直接后继(或直接前驱)替代结点z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。

    • 令结点z的直接后继(或直接前驱)替代z结点?实际上就是让右子树中最小的值(直接后继)覆盖结点z或者左子树中最大值(直接前驱)覆盖结点z任然满足二叉排序树的特性。
    • 因为结点z的直接后继或直接前驱,分别是右子树中最左下的元素,和左子树中最右下元素。不可能同时又左右子树,所以就能回归前两种情况了。
    image-20221113160128447

5. 查找效率分析

查找长度――在查找运算中,需要对比关键字的次数称为查找长度,反映了查找操作时间复杂度

5.1 查找成功的平均查找长度ASL (Average Search Length)

每一个结点的查找长度之和除以结点总数=ASL

对于n个节点的二叉树,二叉树的最小高度是⌊log2n⌋+1,最大高度为n,当二叉排序树高度接近于⌊log2n⌋+1,该二叉排序树查找成功的查找效率最高。

5.2 查找失败的平均查找长度ASL (Average Search Length)

对于查找失败就是指针最后停留在了空指针域,计算停留在每一个空指针域的查找长度之和除以空指针域总数(3 * 7+4 * 2)/9=3.22:

image-20221113170034734

对于n个节点的二叉树,二叉树的最小高度是⌊log2n⌋+1,最大高度为n,当二叉排序树高度接近于⌊log2n⌋+1,该二叉排序树查找失败的查找效率最高。

高度接近⌊log2n⌋+1的二叉排序树查找成功和查找失败的查找效率都是最高的,也就是平衡二叉树

平衡二叉树AVL

平衡二叉树(Balanced Binary Tree),简称平衡树(AVL树)――树上任一结点的左子树和右子树的高度之差不超过1。(AVL是科学家命名)

结点的平衡因子=左子树高度-右子树高度。平衡二叉树的平衡因子值为0,-1,1。只要任意结点的平衡因子大于1,就不是平衡二叉树。

image-20221116092003538

参考代码:

struct AVLNode{
	int key;
	int balance;
	AVLNode *left,*right;
}AVLNode,*AVLTree;

当二叉排序树达到平衡时,查找效率最高。对于n个节点的排序二叉树其高度最小为⌊log2n⌋+1,所以对应的AVL二叉树的查找效率为log2n,那么二叉排序树插入新节点如何保持平衡?

1. 二叉树的插入

image-20221116093317185

可以看到每插入一个新节点,查找路径上所有结点的平衡因子都可能受到影响。对此我们的策略是调整最小不平衡子树。所谓的最小不平衡子树就是从插入点往回找到第一个不平衡的结点,以该节点构成的子树就是最小不平衡子树。对上述二叉树的调整如下:

image-20221116093946118

可以发现调整完最小不平衡二叉树,其余所有节点都平衡

2. 调整最小不平衡子树

只要将最小不平衡子树调整平衡,那么其他祖先结点都将恢复平衡。那么为什么?

对于一颗平衡二叉树,如果插入一个结点破坏了平衡。是因为最小平衡二叉树对比插入前高度增加了一!导致其祖先结点对应的子树全部增加一,使得平衡因子异常,我们所做的调整就是==回复最小不平衡子树的高度==,这样祖先结点相应子树高度也就回复了,排序树重新平衡!

我们先抽象出来最小平衡二叉树的模型:平衡二叉树的左右子树高度相差小于等于1,但对于高度差等于零平衡二叉树插入结点不会破坏平衡。我们考虑的是插入结点后能破坏平衡的模型,所以就得到了左右子树高度差相差1的最小平衡树,当我们进行LL的方式插入,可以看到平衡性收到破坏。

image-20221116141254385

我们要做的就是通过调整,让树恢复平衡并且保持排序二叉树的特点。

二叉排序树的特性:左子树结点值<根结点值<右子树结点值

对于可能导致平衡二叉树被破坏的插入操作有四种:

image-20221116094219460

下面我们来分别讨论这四种情况

2.1 LL左孩子的左孩子

LL平衡旋转(右单旋转)。由于在结点A的左孩子的左子树上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡。

调整方式为:左孩子右上旋。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

image-20221116142839986

代码实现其实就是调整了三个指针,LL插入平衡调整如下:

实现右下旋操作,f指向根结点,p指向左孩子,gf指向根节点的双亲结点

修改三个指针,注意顺序:

f->lchild=p->rchild;
gf->lchild=p;//或者gf->rchild=p;
p->rchild=f;
image-20221116143848627

2.2 RR右孩子的右孩子

RR平衡旋转(左单旋转)。由于在结点A的右孩子的右子树上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡。

调整方式为:右孩子左上旋。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树

image-20221116142610558

代码实现其实就是调整了三个指针,RR插入平衡调整如下:

实现右下旋操作,f指向根结点,p指向右孩子,gf指向根节点的双亲结点

修改三个指针,注意顺序:

f->rchild=p->lchild;
p->lchild=f;
gf->lchild=p;//gf->rchild=p;
image-20221116145126270

2.3 LR左孩子的右孩子

image-20221116151147902

LR平衡旋转(先左后右双旋转)。由于在A的左孩子的右子树上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡

调整犯法如下:左孩子的右孩子,先左上旋后右上旋。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。

image-20221116151507696

代码实现就是左旋和右旋,上面讲过了,可以封装一个方法,分别用于左旋右旋,参数传的是根结点的双亲。上面讲解的是插入左孩子的右子树的右子树,对于插入左孩子的右子树的左子树的处理方式也是一样的。就是下面的这种情况:

image-20221116152213704

2.4 RL右孩子的左孩子

image-20221116152424098

RL平衡旋转(先右后左双旋转)。由于在A的右孩子的左子树上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡.

调整的方式是:右孩子的左孩子,先右上旋转后左上旋转。先将A结点的右孩子B的左子树的根结点c向右上旋转提升到B结点的位置,然后再把该c结点向左上旋转提升到A结点的位置

image-20221116152707581

代码实现就是右孩子右上旋,自己左上旋。上面讲的是插入右孩子的左子树的左子树,至于插入右孩子左子树的右子树处理也是一样的,就是下面的这种情况:

image-20221116153006262

2.5 总结

只有左孩子才能右旋,只有右孩子才能左旋。而具体左旋右旋操作固定:

image-20221116162232284

对于调整策略

image-20221116162413561

2.6 案例

实际问题我们应该如何寻找最小不平衡子树呢?首先排序树插入征程插入就行,然后沿着二叉排序树查找路径寻找,最后一个平衡因子异常的就是对应的最小不平衡子树的根结点

例1

image-20221116164522373

RR型,就是右孩子的右孩子插入调整问题,方法就是右孩子左上旋(此类左孩子右孩子都是相对于最小不平衡二叉树的根节点来说的。孩子变爹,爹变孩子)

image-20221116164721872

例二

image-20221116165224292

RL型,右孩子的左孩子,先右上旋再左上,对应为:

image-20221116170620922

例三

image-20221116170657956

LR型,左孩子的右孩子,先左上旋再右上旋

image-20221116171009519

3. 查找效率分析

深度为h的平衡二叉树中含有最少的结点数假设为nh表示

当h=0,n0=0;

当h=1,n1=1;

当h=2,n2=2;

递归:nh=nh-1+nh-2+1

(高度为h的平衡二叉树对应的最少结点数为根结点数加左子树节点数和右子树的节点数)

所以相应的n3=1+n2+n1=4,n4=1+n3+n2=7、、、、有点类似斐波那契数列~

对于高度的h的排序树,查找一个结点最多只需要查找h次。所以9个结点的平衡二叉树高度为4,所以查找长度最大为4

对于n个结点,最大平衡二叉树的数量级为log2n,所以节点数为n的平衡二叉树的查找效率为O(log2n)

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

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

相关文章

nginx实战

目录反向代理去掉前缀场景1&#xff1a;去掉一层前缀场景2&#xff1a;去掉两层前缀返回固定json增加后缀映射情况一情况二情况三情况四正向代理负载均衡正则匹配~ /item/(\d)反向代理 通过Nginx我们可以实现反向代理&#xff0c;这也是我在项目中使用到的一个功能&#xff0c…

20221122非累加的m3u8的ts切片列表的补全步骤

20221122非累加的m3u8的ts切片列表的补全步骤 2022/11/22 19:07 https://apppy87xs3b3950.h5.xiaoeknow.com/v2/course/alive/l_6374b6d0e4b0edc794f61031?type2&app_idapppy87xs3b3950&availabletrue&share_user_idu_61333670aabd8_rizBVXTg2F&share_type5&a…

从零开始的图像语义分割:FCN快速复现教程(Pytorch+CityScapes数据集)

从零开始的图像语义分割&#xff1a;FCN复现教程&#xff08;PytorchCityScapes数据集&#xff09;前言一、图像分割开山之作FCN二、代码及数据集获取1.源项目代码2.CityScapes数据集三、代码复现1.数据预处理2.代码修改3.运行结果总结参考网站前言 摆了两周&#xff0c;突然觉…

【第五部分 | JS WebAPI】6:PC端网页特效与本地存储

目录 | 概述 | PC端网页特效之三大系列 1-1 elementObj . offsetXXX 属性 1-2 elementObj . style 和 offset 的区别 1-3 案例&#xff1a;获取鼠标在某个盒子内的位置 2-1 elementObj . clientXXX 属性 3-1 elementObj . scrollXXX 属性 三大系列总结 | 动画函数封装 …

LeetCode1005. K 次取反后最大化的数组和

1 题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数组 可能的最大和 。 示例 1&a…

弹簧(压簧)力度计算与设计

弹簧&#xff08;压簧&#xff09;力度计算与设计弹簧的种类什么是弹性系数弹簧的材料常用材料与用途弹性系数与哪些因素有关弹簧力度设计与计算弹簧收尾设计弹簧是一种利用弹性来工作的机械零件。一般用弹簧钢制成。利用它的弹性可以控制机件的运动、缓和冲击或震动、储蓄能量…

[附源码]java毕业设计校园疫情防控管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

BMN:Boundary-matching network for temporal action proposal generation

Video Analysis 相关领域解读之Temporal Action Detection(时序行为检测) - 知乎本文投稿于 极视角 公众号&#xff0c;链接为 文章链接. 上一篇 Video Analysis相关领域解读之Action Recognition(行为识别) - 知乎专栏介绍了 Action Recognition 领域的研究进展。Action Recog…

转铁蛋白修饰纳米载体(纳米颗粒,介孔硅,四氧化三铁,二氧化硅等)

转铁蛋白又名运铁蛋白&#xff08;Transferrin&#xff0c;TRF、Tf&#xff09;&#xff0c;负责运载由消化管吸收的铁和由红细胞降解释放的铁。以三价铁复合物&#xff08;Tf-Fe3&#xff09;的形式进入骨髓中&#xff0c;供成熟红细胞的生成。转铁蛋白主要存在于血浆中&#…

vue 动态表单优秀案例

不同的下拉框 就会显示不同的表单&#xff0c;表单配置是灵活匹配的&#xff0c;还有就是 一定要知道都有哪些类型的数据做到兼容起来。 app.vue <template><a-select v-model:value"FormDataSelect" :options"FormDataSelectList" /><a-fo…

相控阵天线(五):稀疏阵列(概率密度稀疏法、多阶密度加权法、迭代傅里叶(IFT)法)

目录简介稀疏线阵概率密度稀疏法多阶密度加权法迭代傅里叶(IFT)综合法对称分布稀疏阵列建模仿真简介 稀疏阵是在不明显改变阵列波束宽度的情况下去掉一些阵元&#xff0c;可以用满阵列的几分之一的阵元构造一个减低了增益的高方向性阵列&#xff0c;符合大型阵列设计中降低成本…

【C++】哈希算法

目录 1.哈希映射 1.1哈希的概念 1.2哈希冲突 1.3哈希函数 1.31直接定值法 1.32除留余数法 2.解决哈希冲突 2.1闭散列法 2.11线性探测 2.12二次探测 3代码实现 3.1状态&#xff1a; 3.2创建哈希节点类 3.21哈希表扩容&#xff1a; 3.3数据插入 3.4查找与删除 3.…

数据可视化之设计经验分享:轻松三步教你学会制作数据可视化大屏思路

当看到屏幕上一个个炫酷&#xff0c;具有科技感的数据大屏时&#xff0c;很多人都会好奇这是怎么做出来的。自己在制作大屏时明明按着需求做了&#xff0c;可是做出来后总是觉得画面不好看&#xff0c;不够炫&#xff0c;感觉很糟糕。 那要如何才能设计那样的数据可视化大屏呢…

JS 的新一代日期/时间 API Temporal

众所周知&#xff0c;JS的Date是出了名的难用&#xff0c;一直以来我们都在使用momentjs&#xff0c;dayjs等第三方库来处理日期和时间格式&#xff0c;于是 TC39 组织开始了对 Date 的升级改造&#xff0c;他们找到了 moment.js 库的作者&#xff0c;Maggie &#xff0c;由她来…

【深度学习】实验5答案:滴滴出行-交通场景目标检测

DL_class 学堂在线《深度学习》实验课代码报告&#xff08;其中实验1和实验6有配套PPT&#xff09;&#xff0c;授课老师为胡晓林老师。课程链接&#xff1a;https://www.xuetangx.com/training/DP080910033751/619488?channeli.area.manual_search。 持续更新中。 所有代码…

代码随想录刷题| 01背包理论基础 LeetCode 416. 分割等和子集

目录 01背包理论基础 二维dp数组 1、确定dp数组以及下标的含义 2、确定递推公式 3、dp数组如何初始化 4、确定遍历顺序 5、打印dp数组 最终代码 一维dp数组 1、确定dp数组的定义 2、确定递推公式 3、初始化dp数组 4、遍历顺序 5、打印dp数组 最终代码 416. 分割…

一次搞懂SpringBoot核心原理:自动配置、事件驱动、Condition

前言 SpringBoot是Spring的包装&#xff0c;通过自动配置使得SpringBoot可以做到开箱即用&#xff0c;上手成本非常低&#xff0c;但是学习其实现原理的成本大大增加&#xff0c;需要先了解熟悉Spring原理。如果还不清楚Spring原理的&#xff0c;可以先查看博主之前的文章&…

Vue实现简易购物车功能

用Vue写一个列表案例&#xff0c;页面布局什么的dom&#xff0c;不需要自己事先全部排好&#xff0c;而是通过li遍历&#xff0c;把数据遍历出来&#xff1b;先定义好div标签&#xff0c;li根据数组的长度datalist进行遍历&#xff0c;图片的链接要用“&#xff1a;”&#xff…

算法设计与分析 SCAU8597 石子划分问题

8597 石子划分问题 时间限制:1000MS 代码长度限制:10KB 提交次数:0 通过次数:0 题型: 编程题 语言: G;GCC;VC;JAVA Description 给定n个石子&#xff0c;其重量分别为a1,a2,a3,…,an。 要求将其划分为m份&#xff0c;每一份的划分费用定义为这份石子中最大重量与最小重量差…

nRF52832闪存FDS使用(SDK17.1.0)

陈拓 2022/10/29-2022/11/22 1. 简介 对于Nordic芯片内部FLASH存储管理有两种方式&#xff0c;FS (Flash Storage)和FDS (Flash Data Storage) 。FS是FDS的底层实现&#xff0c;FDS是对FS的封装&#xff0c;使用更容易。 Flash Data Storage&#xff08;FDS&#xff09;模块是…