【树】平衡二叉搜索树的介绍与构建

news2024/11/25 2:53:16

二叉平衡搜索树

  • 一、平衡二叉搜索树的概述
    • 1. 平衡二叉树的性质
    • 2. 平衡二叉树的最小节点数(公式及其原理)
      • a. 树高度和深度的区别
      • b. 原理
  • 二、平衡二叉树的创建和调整
    • 1. 节点
    • 2. 旋转
      • 四种姿态
      • a. LL旋转
      • b. RR旋转
      • c. LR旋转
      • d. RL旋转
    • 2. 节点的插入
    • 3. 节点的删除
    • 4. 中序遍历

一、平衡二叉搜索树的概述

平衡二叉树总称应该为平衡二叉查找树,也可称AVL树(满足平衡条件的二叉查找树),也就是说平衡二叉查找树的前提是二叉搜索树(二叉搜索树汇总)。

二叉搜索树进行插入、删除、查找操作时,时间复杂度是 O(logn),但当这棵二叉搜索树为斜树时,那么时间复杂度会引来最坏的结果 O(n)。

在这里插入图片描述
当尽可能的将树俩边保持平衡时,这时复杂度会引来最好的结果。

1. 平衡二叉树的性质

平衡二叉树(Balanced Binary Tree)具有以下性质:

  1. 要么是空树要么左右两个子树的高度差的绝对值不超过1;
  2. 左右子树也都是一棵平衡二叉树;
  3. 每个节点都有一个平衡因子(Balanced Factor),任意一个节点的平衡因子的值为 -1、0、1,计算公式是 左子树高度 - 右子树高度

2. 平衡二叉树的最小节点数(公式及其原理)

设 Nh 是高度为 h 的平衡二叉树的最小结点数

==》Nh = Nh-1 + Nh-2 + 1

学的时候会发现总把它和斐波那契数列放在一起去进行理解。斐波那契数列又可以用分治和递归的思想去解决,而最小节点数用分治思想是不好理解的,递归反而容易理解些,可以理解为由上至下。

a. 树高度和深度的区别

理解最小节点数怎么来的之前得先理解树高度和深度的区别:

定义:

高度:结点到叶子节点最长简单路径的条数;
深度:根节点到该节点的最长简单路径边的条数。
注意:这里的条数规定是 根结点的深度和 叶子结点的高度 是为 0。

区别:

深度是从顶到该节点,高度是从低到该节点。

b. 原理

根据平衡二叉树的定义可知:左右子结点高度差的绝对值<=1,那么对于高度为 h 的平衡二叉树无非就三种情况:

  1. 左结点 h-1 的高度,右结点 h-2 的高度
  2. 右结点 h-1 的高度,左结点 h-2 的高度
  3. 左右结点都为 h-1 的高度。

对于左右子节点来说无非就是新的根节点、新的平衡二叉树,想得到最小节点数,那肯定左右子结点新构成的平衡二叉树的结点也应该满足最小的节点数,且情况为 一个子树为 h-1 的高度, 一个子树为 h-2 的高度。

在这里插入图片描述

注意:这里的高度是以平衡二叉树的根节点为目标结点,Ans(Nh)表示最后结果也就是最小节点数。

(递归 + 公式 更容易理解些,看得懂就行)

二、平衡二叉树的创建和调整

1. 节点

建立一个AVLTree 类,表示平衡二叉搜索树类,由于该二叉树是由节点组成的,那在该类内部有个节点内部类 AVLTreeNode类,该二叉树也是二叉搜索树,由于需要对节点值进行比较,所以也运用了泛型节点对象只用实现了Comparable接口的类对象

public class AVLTree<T extends Comparable<T>> {

    private AVLTreeNode<T> mRoot;
    
    class AVLTreeNode<T extends Comparable<T>>{
        public int height;
        public AVLTreeNode<T> left;
        public AVLTreeNode<T> right;
        T val;

        public AVLTreeNode() {}

        public AVLTreeNode(AVLTreeNode<T> left, AVLTreeNode<T> right, T val) {
            this.left = left;
            this.right = right;
            this.val = val;
        }
    }
}

2. 旋转

四种姿态

当对 AVL 树进行插入、删除操作时,可能会使得 AVL 树失去平衡。这种失去平衡概括为四种姿态:LL(左左)LR(左右)RR(右右)RL(右左)

在这里插入图片描述

  • LL:Left Left,也称为“左左”。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致“根的左子树的高度”比”根的右子树的高度“大 2,平衡因子 > 1,导致AVL 树失去平衡。

  • LR:Left Right,也称为”左右“。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致”根的左子树的高度“比”根的右子树的高度“大2,平衡因子 > 1,导致AVL 树失去了平衡。

  • RL:Right Left,也称为”右左“。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致”根的右子树的高度“比”根的左子树的高度“大2,平衡因子 < -1,导致AVL 树失去了平衡。

  • RR:Right Right,也称为”右右“。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致”根的右子树高度“比”根的左子树高度“大2,平衡因子 < -1,导致AVL 树失去了平衡。

(上图分别对四种姿态进行了图形展示)

当 AVL 失去平衡之后,可以通过旋转使其恢复平衡。

a. LL旋转

在这里插入图片描述

左边是失去平衡的二叉树,右边是恢复后的 AVL 树。k2 是最小不平衡子树根节点,旋转过程中他扮演着主角。

LL 使得AVL 树失去平衡的 AVL 树,是向右旋转,向右旋转 k1 会去替代k2 的位置,k2会成为k1的右孩子,k1 的右孩子会成为新k2 的左孩子。这样做不仅可以 平衡二叉树的性质再次得到满足。

    /**
     * LL 旋转
     * @param k2 要旋转的最小不平衡子树的根节点
     * @return 返回替代k2的节点,也可以说返回该最小不平衡子树的根节点。
     */
    private AVLTreeNode<T> LLRotation(AVLTreeNode<T> k2){
        AVLTreeNode<T> k1 = k2.left;// 标记节点,用来暂时指向k2 的左孩子
        // 要开始旋转了哦
        k2.left = k1.right;
        k1.right = k2;

        k2.height = Math.max(height(k2.left),height(k2.right)) + 1;
        k1.height = Math.max(height(k1.left),height(k1.right)) + 1;
        return k1;
    }

注意:该方法重新返回(最小不平衡树)根节点是有用的,插入,以及LR和RL旋转都是有用的。

b. RR旋转

在这里插入图片描述
注意:和上面的LL是相反的。代码里面的k2指的是图里的k1,k1指的是图里的k2。

	/**
     * @param k2
     * @return 返回最小不平衡子树的根节点
     */
	private AVLTreeNode<T> RRRotation(AVLTreeNode<T> k2) {
        AVLTreeNode<T> k1 = k2.right;
        k2.right = k1.left;
        k1.left = k2;

        k2.height = Math.max(height(k2.left),height(k2.right)) + 1;
        k1.height = Math.max(height(k1.left),height(k1.right)) + 1;
        return k1;
    }

c. LR旋转

  • 从右往左看,先RR转,在LL转。

在这里插入图片描述

  • 第一次旋转是围绕"k1"进行的"RR旋转",第二次是围绕"k3"进行的"LL旋转"。

代码就很简单啦:

	/**
     * @param root
     * @return 返回最小不平衡子树的根节点
     */
	 private AVLTreeNode<T> LRRotation(AVLTreeNode<T> root) {
        root.left = RRRotation(root.left);
        return LLRotation(root);
    }

d. RL旋转

  • 先 LL 转,再 RR 转咯。

在这里插入图片描述

  • 第一次旋转是围绕"k3"进行的"LL旋转",第二次是围绕"k1"进行的"RR旋转"。
	/**
     * @param root
     * @return 返回最小不平衡子树的根节点
     */
    private AVLTreeNode<T> RLRotation(AVLTreeNode<T> root) {
        root.right = LLRotation(root.right);
        return RRRotation(root);
    }

2. 节点的插入

本应该插入应该在前的,因为它可以更好的测试数据。
但是插入又离不开旋转,所以还是得先解释旋转。

结点的插入呢?和二叉搜索树差不多。多了个AVL树是否还平衡的判断和结点高度的更新。

直接上代码,更清楚:

	// 供用户使用的方法
	public void insert(T key){
        if(key!=null){
            mRoot = insert(mRoot,key);
        }
    }
	// 插入细节
    private AVLTreeNode<T> insert(AVLTreeNode<T> root,T key){
        if(root==null){
            root = new AVLTreeNode<T>(null,null,key);
        }else{
            int cmp = key.compareTo(root.val);
            if(cmp<0){
                root.left = insert(root.left, key);
                // 插入节点如果AVL 树失去平衡,则进行相应的调节
                if(height(root.left) - height(root.right) == 2){
                    // 这节点是插入在左孩子的,要么就是LL 要么就是LR
                    // LL
                    if(key.compareTo(root.left.val)<0){
                        root = LLRotation(root);
                    }else{
                        root = LRRotation(root);
                    }
                }
            }else if(cmp>0){
                root.right = insert(root.right,key);
                // 和上面一样,判断是否失去平衡然后做调节
                if(height(root.right) - height(root.left) == 2){
                    if(key.compareTo(root.right.val)>0) {
                        root = RRRotation(root);
                    }else{
                        root  = RLRotation(root);
                    }
                }
            }else{
                // 这里咱自定义一个异常类 ValOfAVLNodeEqual
                try {
                    throw new ValOfAVLNodeEqual("AVL 树中不支持节点的数据相等");
                } catch (ValOfAVLNodeEqual e) {
                    e.getMessage();
                }
            }
        }
        // 递归回溯的途中需要更新节点的高度
        root.height = Math.max(height(root.left),height(root.right)) + 1;
        // 完美返回
        return root;
    }

3. 节点的删除

删除和BST 一样分三种情况:

  1. 左右孩子都存在;
  2. 单个孩子存在;
  3. 无孩子。

什么查找代码,寻找左子树最大值节点。。。很多重复代码,不想写了。

可以看看这个的删除操作:

平衡二叉树删除操作

4. 中序遍历

有了上面的噔噔噔一些操作后,来个遍历收尾。

返回一个LIst 集合,可以操作可以输出。完美~

private List<T> valList = new ArrayList<>();
    
    /**
     * 咱来个中序遍历,有序
     */
    public List<T> orderTraversal(){
        valList.clear();
        orderTraversal(mRoot);
        return valList;
    }

    public void orderTraversal(AVLTreeNode<T> node){
        if(node!=null){
            orderTraversal(node.left);
            valList.add(node.val);
            orderTraversal(node.right);
        }
    }

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

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

相关文章

[转]深度学习 Transformer架构解析

原文链接&#xff1a;https://blog.csdn.net/mengxianglong123/article/details/1262614791.1 Transformer的诞生2018年10月&#xff0c;Google发出一篇论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》, BERT模型横空出世, 并横扫N…

分享5款后台收到的推荐最多的软件

最近后台收到好多小伙伴的私信&#xff0c;今天继续推荐五款小工具&#xff0c;都是免费使用的&#xff0c;大家可以去试试看。 1.视频压缩——Moo0 VideoMinimizer 一款完全免费的视频压缩软件&#xff0c;能够有效的将视频压缩到最小&#xff0c;同时还不改变视频画质很清晰…

基于SpringBoot的房屋租赁管理系统的设计与实现

基于SpringBoot的房屋租赁管理系统的设计与实现 1 绪论 1.1 课题来源 随着社会的不断发展以及大家生活水平的提高&#xff0c;越来越多的年轻人选择在大城市发展。在大城市发展就意味着要在外面有一处安身的地方。在租房的过程中&#xff0c;大家也面临着各种各样的问题&…

Java程序设计-基于Java高校社团管理系统

摘 要功能需求系统的功能实现摘 要 当前&#xff0c;大多数高校的社团信息管理都是采用纸质档案的方式来管理的&#xff0c;这样不仅不能长期的保存信息&#xff0c;而且在数据的查找上带来很大的不方便。在目前的网络技术和计算机技术的普及和信息管理的迅速发展&#xff0c;…

【MySQL】你知道的MySQL中的集合函数有哪些呢?

集合函数排名AVG&#xff08;&#xff09;函数COUNT()函数SUM&#xff08;&#xff09;函数MAX&#xff08;&#xff09;函数 和 MIN&#xff08;&#xff09;函数总结大家好&#xff0c;我是小冷。 上一篇写了 看看ChatGPT是如何回答面试官的问题的&#xff1f; 地址是&#x…

MySQL数据库06——条件查询(WHERE)

MySQL条件查询&#xff0c;主要是对数据库里面的数据按照一定条件进行筛选&#xff0c;主要依靠的是WHERE语句进行。 先来了解一下基础的条件运算。 关系运算符 逻辑运算符 逻辑运算符优先级&#xff1a;NOT>AND>OR&#xff0c;关系运算符>逻辑运算符 SQL特殊运算符…

公司常用的Project管理工具

目录 1.svn 2.reviewBoard 3.禅道 4.瘦终端 1.svn svn主要是对代码的管理&#xff0c;保证代码的同步开发。 svn的搭建方法https://www.cnblogs.com/ftx3q/p/15340160.html 2.reviewBoard reviewBoard代码审查工具&#xff0c;所有工程师写的代码上传到reviewBoard&#x…

前端将本地代码项目上传到gitee上

文章目录前言一、gitee建立仓库&#xff1f;1.登入并新建账号2.填写仓库信息3.传项目前言 因为以前跟B站做了一些项目&#xff0c;想上传到gitee仓库保留。所以写下此文章。前端项目 一、gitee建立仓库&#xff1f; 1.登入并新建账号 2.填写仓库信息 readme文件是否选择&#…

C++类基础(十)

运算符重载 struct Str {int val 3; }; Str Add(Str x, Str y) {Str z;z.val x.val y.val;return z; } int main() {int val1 2;int val2 3;int val3 val1 val2; //分别对val1和val2求值然后相加Str x;Str y;Str z Add(x, y); //同样的功能&#xff0c;但是写法冗杂St…

【GPLT 二阶题目集】L2-004 这是二叉搜索树吗?

参考文章&#xff1a;L2-004. 这是二叉搜索树吗&#xff1f;-PAT团体程序设计天梯赛GPLT 作者&#xff1a;柳婼&#xff08;非常感谢!!!&#xff09; 一棵二叉搜索树可被递归地定义为具有下列性质的二叉树&#xff1a;对于任一结点&#xff0c; 其左子树中所有结点的键值小于…

基于时间序列的 基-2 FFT算法程序

gitee链接 &#xff1a;基于时间序列的 基-2 FFT算法程序 我的 gitee 程序目前没有公开&#xff0c;目前仅是给自己的程序做一个备份的目的。 但是大家可以使用我博客贴出来的程序&#xff0c;二者是一样的。 文章目录1.程序使用方法2.代码3.验证1.程序使用方法 1.先补零至2的…

html(二)基础标签

一 HTML中的注释 重点&#xff1a; 在哪写注释? 注释的形式? vs code和webstorm都可以通过 ctrl / 进行单行注释和取消注释 ① html中注释的形式 1) html文档中单行和多行注释是"<!-- -->" -->html2) 在html文档中,script标签…

volatile 关键字

1.volatile 能保证内存可见性 volatile 修饰的变量, 能够保证 "内存可见性". 代码在写入 volatile 修饰的变量的时候, 改变线程工作内存中volatile变量副本的值将改变后的副本的值从工作内存刷新到主内存 代码在读取 volatile 修饰的变量的时候 从主内存中读取vol…

为什么B站中的弹幕可以不遮挡人物

上班逛B站时摸鱼时&#xff0c;看到了满屏的弹幕&#xff0c;而且还不挡脸&#xff0c;突然心血来潮来看看它是怎么实现的&#xff1f; 不难发现弹幕其实它就是有一个蒙版层div&#xff0c;遮挡在视频组件的上方&#xff0c;z-index层级设置的比较高&#xff08;这里是11&…

史上最全最详细的Instagram 欢迎消息引流及示例

史上最全最详细的Instagram 欢迎消息引流及示例&#xff01;关键词&#xff1a; Instagram 欢迎消息SaleSmartly&#xff08;ss客服&#xff09; 寻找 Instagram 欢迎消息示例&#xff0c;您可以用于您的业务。在本文中&#xff0c;我们将介绍Instagram欢迎消息的基础知识和好处…

window11安装node、nvm、nrm

一、安装nvm 下载nvm安装包&#xff0c;window11建议使用exe安装包 Releases coreybutler/nvm-windows GitHub 下载后双击安装 切记&#xff01;切记&#xff01;切记&#xff01; 安装nvm和nodejs的目录设置一定不要有特殊符号或者空格&#xff0c;设置一个连续的只有英文…

UMI 创建react目录介绍及配置

UMI 生成react项目目录介绍及配置 react项目目录介绍umi多种配置方案运行时配置app.ts 的使用 1、umi创建的项目目录大致如下 ├─package.json 配置依赖以及启动打包所需的命令 ├─.umirc.ts 配置文件&#xff0c;包含 umi 内置功能和插件的配置 ├── dist 打包后生成的…

情人节送什么礼物?四款情人节潮流数码好物推荐

情人节是一个特别的日子&#xff0c;是表达爱意和祝福的机会&#xff0c;如果您正在寻找一件特别的礼物&#xff0c;下面这篇文章不容错过。 推荐1&#xff1a;南卡小音舱蓝牙耳机&#xff08;299元&#xff09; 作为最能表达仪式感和诚意的礼物&#xff0c;精致和实用是很重要…

Spring中Bean的作用域问题

文章目录一、通过案例来简单体会一下Bean的作用域问题二、作用域定义三、Bean的作用域分类singletonprototyperequestsessionapplication&#xff08;了解&#xff09;singleton&#xff08;单例作用域&#xff09; 和 application &#xff08;全局作用域&#xff09;的区别we…

马尔科夫预测

一、模型介绍 天气有以下几种状态&#xff1a;晴天、雨天、阴天若已知天气当前处于某种状态&#xff0c;则天气未来的状态只与现在有关&#xff0c;与过去无关注意&#xff0c;天气的状态是随机的&#xff0c;只能求明天处于某一种状态的概率描述这种随机现象的模型&#xff0…