二叉平衡树(左单旋,右单旋,左右双旋、右左双旋)

一、AVL树(二叉平衡树:高度平衡的二叉搜索树)

0、二叉平衡树

左右子树高度差不超过1的二叉搜索树。

public class AVLTree{static class AVLTreeNode {public TreeNode left = null; // 节点的左孩子public TreeNode right = null; // 节点的右孩子public TreeNode parent = null; // 节点的双亲public int val = 0;public int bf = 0; // 当前节点的平衡因子=右子树高度-左子树的高度public TreeNode(int val) {this.val = val;}}public TreeNode root;//插入函数等....}
// 将AVLTreeNode定义为AVLTree的静态内部类

1、查找

二叉平衡树的查找和二叉搜索树的方法是一样的,因为它们具有相同的结构特点——右孩子val值小于根节点val,根节点val小于左孩子val。

2、插入

二叉搜索树的插入一定是插入到叶子节点的位置。
二叉平衡树将节点插入到叶子节点之后,要维护左右子树的平衡因此可能还要进行旋转操作。

  1. 先将数据插入到AVL树当中(和二叉搜索数一样)
  2. 插入进去后,根据平衡因子来进行对树的调整

3、插入后无需旋转的情况:

注意各节点bf值得变化

image.png

4、右单旋&左单旋:

对parent进行右单旋就是把parent.left提拔成根节点
注意各节点bf值得变化
image.png
对parent进行左单旋就是把parent.right提拔成根节点
注意各节点bf值得变化
image.png

补充:相对与根节点各个节点的名称,后续得图会用到这些名称

image.png

image.png
image.png

树parent是pParent(parent.parent)的一棵子树,对parent进行旋转后需要将新的根节点的parent指针指向pParent.

image.png
image.png

//检查 当前是不是就是根节点
if(parent == root) {root = subL;// subL.parent等于parent,subL提拔成了根节点,所以要将subL.parent设置为null,subL.parent = null;
}else {//不是根节点,判断这棵子树是左子树还是右子树if(pParent.left == parent) {pParent.left = subL;}else {pParent.right = subL;}subL.parent = pParent;
}

5、左右双旋 &右左双旋

  1. 注意指针指向
  2. 注意维护bf

右左双旋过程图:

image.png
image.png

左右双旋过程图:

image.png
image.png

下面这两幅图介绍了左右双旋时如何维护个节点的bf值(右左双选的不想画了,太累了)

image.png
image.png

/*** 左右双旋* @param parent*/
private void rotateLR(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;int bf = subLR.bf;rotateLeft(parent.left);rotateRight(parent);// 这个规律很重要,中间状态的bf值不重要,根据初始状态的bf值来修改平衡状态的bf值if(bf == -1) {parent.bf = 1;subL.bf = 0;subLR.bf = 0;}else if(bf == 1){parent.bf = 0;subL.bf = -1;subLR.bf = 0;}
}
/*** 右左双旋* @param parent*/
private void rotateRL(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;int bf = subRL.bf;rotateRight(parent.right);rotateLeft(parent);// 这个规律很重要,中间状态的bf值不重要,根据初始状态的bf值来修改平衡状态的bf值if(bf == 1) {parent.bf = -1;subR.bf = 0;subRL.bf = 0;}else if(bf == -1){parent.bf = 0;subR.bf = 1;subRL.bf = 0;}
}

6、总代码

package org.example;/*** @Author 12629* @Description:*/
public class AVLTree {static class TreeNode {public int val;public int bf;//平衡因子public TreeNode left;//左孩子的引用public TreeNode right;//右子树的引用public TreeNode parent;//父亲节点的引用public TreeNode(int val) {this.val = val;}}public TreeNode root;//根节点public boolean insert(int val) {TreeNode node = new TreeNode(val);if(root == null) {root = node;return true;}TreeNode parent = null;TreeNode cur = root;while (cur != null) {if(cur.val < val) {parent = cur;cur = cur.right;}else if(cur.val == val) {return false;}else {parent = cur;cur = cur.left;}}//cur == nullif(parent.val < val) {parent.right = node;}else {parent.left = node;}//node.parent = parent;cur = node;// 平衡因子 的修改while (parent != null) {//先看cur是parent的左还是右  决定平衡因子是++还是--if(cur == parent.right) {//如果是右树,那么右树高度增加 平衡因子++parent.bf++;}else {//如果是左树,那么左树高度增加 平衡因子--parent.bf--;}//检查当前的平衡因子 是不是绝对值 1  0  -1if(parent.bf == 0) {//说明已经平衡了break;}else if(parent.bf == 1 || parent.bf == -1) {//继续向上去修改平衡因子cur = parent;parent = cur.parent;}else {//右树高-》需要降低右树的高度if(parent.bf == 2) {if(cur.bf == 1) {//左旋rotateLeft(parent);}else {//cur.bf == -1rotateRL(parent);}}else {//parent.bf == -2 左树高-》需要降低左树的高度if(cur.bf == -1) {//右旋rotateRight(parent);}else {//cur.bf == 1rotateLR(parent);}}//上述代码走完就平衡了break;}}return true;}private void rotateRL(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;int bf = subRL.bf;rotateRight(parent.right);rotateLeft(parent);if(bf == 1) {parent.bf = -1;subR.bf = 0;subRL.bf = 0;}else if(bf == -1){parent.bf = 0;subR.bf = 1;subRL.bf = 0;}}/*** 左右双旋* @param parent*/private void rotateLR(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;int bf = subLR.bf;rotateLeft(parent.left);rotateRight(parent);if(bf == -1) {parent.bf = 1;subL.bf = 0;subLR.bf = 0;}else if(bf == 1){parent.bf = 0;subL.bf = -1;subLR.bf = 0;}}/*** 左单旋* @param parent*/private void rotateLeft(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;parent.right = subRL;subR.left = parent;if(subRL != null) {subRL.parent = parent;}TreeNode pParent = parent.parent;parent.parent = subR;if(root == parent) {root = subR;root.parent = null;}else {if(pParent.left == parent) {pParent.left = subR;}else {pParent.right = subR;}subR.parent = pParent;}subR.bf = parent.bf = 0;}/*** 右单旋* @param parent*/private void rotateRight(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;parent.left = subLR;subL.right = parent;//没有subLRif(subLR != null) {subLR.parent = parent;}//必须先记录TreeNode pParent = parent.parent;parent.parent = subL;//检查 当前是不是就是根节点if(parent == root) {root = subL;// subL.parent等于parent,subL提拔成了根节点,所以要将subL.parent设置为null.subL.parent = null;}else {//不是根节点,判断这棵子树是左子树还是右子树if(pParent.left == parent) {pParent.left = subL;}else {pParent.right = subL;}subL.parent = pParent;}subL.bf = 0;parent.bf = 0;}//中序遍历的结果是有序的 就能说明当前树 一定是AVL树吗?  不一定的private boolean inorder(TreeNode root){return inorderHelper(root,Long.MIN_VALUE);}private boolean inorderHelper(TreeNode root,long pre) {if(root == null) {return true;}if(!inorderHelper(root.left,pre)) {return false;}if(pre < root.val){pre = root.val;if(!inorderHelper(root.right,pre)){return false;}return true;}return false;}private int height(TreeNode root) {if(root == null) {return 0;}int leftH = height(root.left);int rightH = height(root.right);return leftH > rightH ? leftH+1 : rightH+1;}public boolean isBalanced(TreeNode root) {if(root == null) {return true;}int leftH = height(root.left);int rightH = height(root.right);if(rightH-leftH != root.bf) {System.out.println("这个节点:"+root.val+" 平衡因子异常");return false;}return Math.abs(leftH-rightH) <= 1&& isBalanced(root.left)&& isBalanced(root.right)&&inorder(root);// 不仅要左右平衡,还要排序}
}

二叉平衡树的适用和不适用场景如下:
适用场景:

  1. 动态数据集合:
    • 当需要频繁地对数据集合进行插入、删除和查找操作时,二叉平衡树是一个很好的选择。它能够保持O(log n)的时间复杂度,避免了普通二叉搜索树在极端情况下退化为链表的问题。
  2. 需要保持数据有序性:
    • 二叉平衡树能够维护数据的有序性,同时也具有较高的查找效率。这在需要保持数据有序性并进行快速查找的场景中非常适用,例如索引数据库、缓存系统等。
  3. 需要高效的范围查询:
    • 由于二叉平衡树能够维护数据的有序性,因此可以很高效地进行范围查询,例如查找某个区间内的所有元素。这在一些需要范围查询的应用中很有用,如地理信息系统、网络路由表管理等。

不适用场景:

  1. 数据集合变化较小:
    • 如果数据集合的变化(插入、删除)很少,使用普通的二叉搜索树可能更加简单高效,因为不需要维护平衡性。
  2. 内存使用要求苛刻:
    • 二叉平衡树需要存储额外的平衡信息(如高度、平衡因子),会占用更多的内存。如果内存使用非常受限,可能需要选择其他更简单的数据结构。
  3. 对写操作要求极高:
    • 由于需要进行平衡操作,二叉平衡树的写操作(插入和删除)会稍微慢于普通的二叉搜索树。如果对写操作的性能要求极高,可能需要考虑其他数据结构。

总的来说,二叉平衡树是一种非常实用的数据结构,在需要高效管理动态有序数据集合的场景中表现优秀。但在某些特定的应用需求中,可能需要根据具体情况来权衡选择适合的数据结构。

** 画图不易,求赞。有不理解的地方可以私信我,必回复! **

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

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

相关文章

Java 中的异常处理机制是如何工作的?请解释 try-catch-finally 的基本用法?

Java中的异常处理机制是确保程序稳健性的重要组成部分&#xff0c;它允许程序在遇到错误或异常情况时&#xff0c;能够优雅地处理问题&#xff0c;而不是直接崩溃。 这一机制的核心在于使用try-catch-finally结构&#xff0c;以及通过throw和throws关键字来抛出和声明异常。 …

基于Transformer的端到端的目标检测 | 读论文

本文正在参加 人工智能创作者扶持计划 提及到计算机视觉的目标检测&#xff0c;我们一般会最先想到卷积神经网络&#xff08;CNN&#xff09;&#xff0c;因为这算是目标检测领域的开山之作了&#xff0c;在很长的一段时间里人们都折服于卷积神经网络在图像处理领域的优势&…

Vue2.0和Vue3.0的区别?

Vue.js 3.0 相较于 Vue.js 2.0 在多个方面进行了改进和优化&#xff0c;主要包括以下几点&#xff1a; 性能提升&#xff1a; Vue 3.0 使用了新的响应式系统&#xff0c;称为“Proxy-based”&#xff0c;相比于 Vue 2.0 的“Object.defineProperty”&#xff0c;更加高效。 编…

【深度学习基础】安装包报错——MAC M3-MAX芯片安装scikit-learn库报错。

目录 一、问题描述二、解决方法 一、问题描述 首先想安装scikit-learn库在mac终端显示顺利安装完成&#xff0c;但是测试的时候报错如下所示&#xff1a; /opt/anaconda3/envs/dtc/bin/python /Users/chenfaquan/PycharmProjects/TimeSeries/data_create.py Traceback (most…

论文 | REACT: SYNERGIZING REASONING AND ACTING INLANGUAGE MODELS

本文首先认为&#xff0c;到目前为止&#xff0c;LLM 在语言理解方面令人印象深刻&#xff0c;它们已被用来生成 CoT&#xff08;思想链&#xff09;来解决一些问题&#xff0c;它们也被用于执行和计划生成。 尽管这两者是分开研究的&#xff0c;但本文旨在以交错的方式将推理…

JSP入门基础

JSP入门基础 软件开发环境这门课程的复习资料 Web开发技术概述 URL的组成部分 协议、主机DNS名或IP地址和文件名 Tomcat服务器 Tomcat服务器的默认端口号是8080 概念 软件开发环境是围绕着软件开发的一定目标而组织在一起的一组相关软件工具的有机集合 JSP和HTML的区别…

range()用法

range(n)&#xff1a;是Python中的函数&#xff0c;作用是可以生成 [0, n)之间的正数range(a,b) :生成[a,b)之间的正数数字&#xff0c;不包含brange(start, end, step)&#xff1a;生成的数值规则--- [start, end) 步长是 step,默认 1 详见&#xff1a;http://t.csdnimg.cn/7…

科研入门笔记

自学参考&#xff1a; 沐神论文精读系列 如何读论文 通常&#xff0c;一篇论文的结构为&#xff1a; title标题abstract摘要introduction介绍method方法experiments实验conclusion结论 一篇论文可以考虑读1~3遍 第一遍 海选&#xff1a;标题、摘要、结论&#xff0c;选读方…

SPE连接器技术革新汽车制造业

概述 新的SPE标准在汽车制造业中的应用正日益受到重视&#xff0c;它不仅推动了汽车通信技术的革新&#xff0c;还对汽车性能测试方法产生了深远影响。本文将详细探讨SPE标准在汽车制造业中的应用案例分析&#xff0c;以及它对供应链的挑战与机遇。 SPE标准在汽车制造业中的应…

[leetcode]subarray-product-less-than-k 乘积小于K的子数组

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int numSubarrayProductLessThanK(vector<int>& nums, int k) {if (k 0) {return 0;}int n nums.size();vector<double> logPrefix(n 1);for (int i 0; i < n; i) {logPrefix[i 1] …

揭秘!chatGPT核心技术应用

2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT-3.5&#xff0c;将人工智能的发展推向了一个新的高度。2023年11月7日&#xff0c;OpenAI首届开发者大会被称为“科技界的春晚”&#xff0c;吸引了全球广大…

js 时间单位秒转时分秒方法封装。00:00:00

1.方法封装 /*** 秒数转时分秒*/ function formatSeconds(t) {let h parseInt(${t / 60 / 60})let m parseInt(${t / 60 % 60})let s parseInt(${t % 60})//三元表达式 补零 如果小于10 则在前边进行补零 如果大于10 则不需要补零h h < 10 ? 0 h : hm m < 10 ?…

prometheus回顾(2)--如何使用Grafana对接Prometheus数据源的详细过程,清晰易懂。

文章目录 Grafana简介什么是GrafanaGrafana 能做什么&#xff1f;什么时候我们会用到Grafana?Prometheus有图形化展示&#xff0c;为什么我们还要用Grafana? 环境操作步骤一、Grafana安装二、Grafana数据源Prometheus添加三、Grafana添加数据仪表盘补充、如何查找仪表盘 Graf…

在Linux下直接修改磁盘镜像文件的内容

背景 嵌入式Linux系统通常在调试稳定后&#xff0c;会对磁盘&#xff08;SSD、NVME、SD卡、TF卡&#xff09;做个镜像&#xff0c;通常是.img后缀的文件&#xff0c;以后组装新设备时&#xff0c;就将镜像文件烧录到新磁盘即可&#xff0c;非常简单。 这种方法有个不便之处&a…

Oracle学习笔记

Oracle 一、简介&#xff1a; 特点&#xff1a; 多用户、大事务量的事务处理 数据安全性和完整性控制 支持分布式数据处理 可以移植性 Oracle 19c 安装 登录甲骨文&#xff0c;安装Oracle 解压压缩包 安装 完毕 此处账户&#xff1a;qfedu 密码&#xff1a;wang8218.…

使用Java和Spring Boot实现用户身份验证

使用Java和Spring Boot实现用户身份验证 大家好&#xff0c;我是微赚淘客系统3.0的小编&#xff0c;是个冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在现代Web应用中&#xff0c;用户身份验证是确保应用安全的核心部分。通过身份验证&#xff0c;我们可以识别…

染色法判定二分图

什么是二分图&#xff1f; 二分图&#xff0c;也称作二部图&#xff0c;是图论中的一种特殊模型。在一个无向图G(V,E) 中&#xff0c;如果顶点集合 V 可以被分割成两个互不相交的子集 A 和 B&#xff0c;并且图中的每条边 (i,j) 关联的两个顶点 i 和 j 分别属于这两个不同的顶…

LeetCode(2)合并链表、环形链表的约瑟夫问题、链表分割

一、合并链表 . - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ typedef struct ListNode ListNode; struct ListNode* mergeTwoLists(struct …

C# —— BufferedStream的

1BufferedStream的基本介绍 BufferedStream的主要特点和用途如下&#xff1a; 特点&#xff1a; 提供缓冲区&#xff0c;减少对底层流的直接访问。 提高读写操作的速度&#xff0c;尤其是在处理大量数据时。 可以配置缓冲区大小&#xff0c;以适应不同的应用需求。 用途&…

C++入门基础篇(下)

目录 6.引用 6.1 引用的特性 6.2 const引用 7.指针和引用的关系 8.内联函数 9.nullptr 6.引用 引⽤不是新定义⼀个变量&#xff0c;⽽是给已存在变量取了⼀个别名&#xff0c;编译器不会为引⽤变量开辟内存空间&#xff0c; 它和它引⽤的变量共⽤同⼀块内存空间。比如&a…