java数据结构YZP专栏-----二叉树 平衡二叉树(AVL)

主文章(数据结构的索引目录—进不去就说明我还没写完)
https://blog.csdn.net/grd_java/article/details/122377505
模拟数据结构的网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
源码(码云):https://gitee.com/yin_zhipeng/data_structures_and_algorithms_in_java.git
平衡二叉树,就是更完善的二叉排序树,代码也是在二叉排序基础上添加了一些逻辑
  1. 平衡二叉树(AVL),又名平衡二叉搜索树,保证查询效率较高
  2. 它是一课空树或它的左右两个子树的高度差的绝对值小于等于1,并且左右两个子树都是一课平衡二叉树。满足这些特点就是平衡二叉树。(下图就是一课树,右子树高度比左子树高2,不满足,所以进行左旋转操作,后面会讲)
    在这里插入图片描述
  3. 常用实现算法有红黑树、AVL算法、替罪羊树、Treap、伸展树等
为什么要有平衡二叉树

在这里插入图片描述

  1. 当我们要将一个递增的序列转换成二叉排序树时,那么会变成一个效率很低的单链表,因为需要判断左右子树
  2. 而平衡二叉树可以解决此类问题
左旋转(以右子树比左子树高度,高1以上,比如2,就需要左旋转,反之右旋转)

在这里插入图片描述

右旋转

在这里插入图片描述

双旋转
  1. 某些序列,无法通过左旋转,或右旋转,转变为平衡二叉树,此时需要双旋转
    在这里插入图片描述
AVL
// 平衡二叉搜索树(AVL树):允许重复值
class AVL {
    Node root;

    // 平衡二叉搜索树结点
    class Node {
        int val;
        Node parent;
        Node left;
        Node right;
        int size;
        int height;

        public Node(int val) {
            this(val, null);
        }

        public Node(int val, Node parent) {
            this(val, parent, null, null);
        }

        public Node(int val, Node parent, Node left, Node right) {
            this.val = val;
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.height = 0; // 结点高度:以node为根节点的子树的高度(高度定义:叶结点的高度是0)
            this.size = 1; // 结点元素数:以node为根节点的子树的节点总数
        }
    }

    public AVL(List<Integer> vals) {
        if (vals != null) {
            this.root = build(vals, 0, vals.size() - 1, null);
        }
    }

    // 根据vals[l:r]构造平衡二叉搜索树 -> 返回根结点
    private Node build(List<Integer> vals, int l, int r, Node parent) {
        int m = (l + r) >> 1;
        Node node = new Node(vals.get(m), parent);
        if (l <= m - 1) {
            node.left = build(vals, l, m - 1, node);
        }
        if (m + 1 <= r) {
            node.right = build(vals, m + 1, r, node);
        }
        recompute(node);
        return node;
    }

    // 返回二叉搜索树中第k小的元素
    public int kthSmallest(int k) {
        Node node = root;
        while (node != null) {
            int left = getSize(node.left);
            if (left < k - 1) {
                node = node.right;
                k -= left + 1;
            } else if (left == k - 1) {
                break;
            } else {
                node = node.left;
            }
        }
        return node.val;
    }

    public void insert(int v) {
        if (root == null) {
            root = new Node(v);
        } else {
            // 计算新结点的添加位置
            Node node = subtreeSearch(root, v);
            boolean isAddLeft = v <= node.val; // 是否将新结点添加到node的左子结点
            if (node.val == v) { // 如果值为v的结点已存在
                if (node.left != null) { // 值为v的结点存在左子结点,则添加到其左子树的最右侧
                    node = subtreeLast(node.left);
                    isAddLeft = false;
                } else { // 值为v的结点不存在左子结点,则添加到其左子结点
                    isAddLeft = true;
                }
            }

            // 添加新结点
            Node leaf = new Node(v, node);
            if (isAddLeft) {
                node.left = leaf;
            } else {
                node.right = leaf;
            }

            rebalance(leaf);
        }
    }

    // 删除值为v的结点 -> 返回是否成功删除结点
    public boolean delete(int v) {
        if (root == null) {
            return false;
        }

        Node node = subtreeSearch(root, v);
        if (node.val != v) { // 没有找到需要删除的结点
            return false;
        }

        // 处理当前结点既有左子树也有右子树的情况
        // 若左子树比右子树高度低,则将当前结点替换为右子树最左侧的结点,并移除右子树最左侧的结点
        // 若右子树比左子树高度低,则将当前结点替换为左子树最右侧的结点,并移除左子树最右侧的结点
        if (node.left != null && node.right != null) {
            Node replacement = null;
            if (node.left.height <= node.right.height) {
                replacement = subtreeFirst(node.right);
            } else {
                replacement = subtreeLast(node.left);
            }
            node.val = replacement.val;
            node = replacement;
        }

        Node parent = node.parent;
        delete(node);
        rebalance(parent);
        return true;
    }

    // 删除结点p并用它的子结点代替它,结点p至多只能有1个子结点
    private void delete(Node node) {
        if (node.left != null && node.right != null) {
            return;
            // throw new Exception("Node has two children");
        }
        Node child = node.left != null ? node.left : node.right;
        if (child != null) {
            child.parent = node.parent;
        }
        if (node == root) {
            root = child;
        } else {
            Node parent = node.parent;
            if (node == parent.left) {
                parent.left = child;
            } else {
                parent.right = child;
            }
        }
        node.parent = node;
    }

    // 在以node为根结点的子树中搜索值为v的结点,如果没有值为v的结点,则返回值为v的结点应该在的位置的父结点
    private Node subtreeSearch(Node node, int v) {
        if (node.val < v && node.right != null) {
            return subtreeSearch(node.right, v);
        } else if (node.val > v && node.left != null) {
            return subtreeSearch(node.left, v);
        } else {
            return node;
        }
    }

    // 重新计算node结点的高度和元素数
    private void recompute(Node node) {
        node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
        node.size = 1 + getSize(node.left) + getSize(node.right);
    }

    // 从node结点开始(含node结点)逐个向上重新平衡二叉树,并更新结点高度和元素数
    private void rebalance(Node node) {
        while (node != null) {
            int oldHeight = node.height, oldSize = node.size;
            if (!isBalanced(node)) {
                node = restructure(tallGrandchild(node));
                recompute(node.left);
                recompute(node.right);
            }
            recompute(node);
            if (node.height == oldHeight && node.size == oldSize) {
                node = null; // 如果结点高度和元素数都没有变化则不需要再继续向上调整
            } else {
                node = node.parent;
            }
        }
    }

    // 判断node结点是否平衡
    private boolean isBalanced(Node node) {
        return Math.abs(getHeight(node.left) - getHeight(node.right)) <= 1;
    }

    // 获取node结点更高的子树
    private Node tallChild(Node node) {
        if (getHeight(node.left) > getHeight(node.right)) {
            return node.left;
        } else {
            return node.right;
        }
    }

    // 获取node结点更高的子树中的更高的子树
    private Node tallGrandchild(Node node) {
        Node child = tallChild(node);
        return tallChild(child);
    }

    // 重新连接父结点和子结点(子结点允许为空)
    private static void relink(Node parent, Node child, boolean isLeft) {
        if (isLeft) {
            parent.left = child;
        } else {
            parent.right = child;
        }
        if (child != null) {
            child.parent = parent;
        }
    }

    // 旋转操作
    private void rotate(Node node) {
        Node parent = node.parent;
        Node grandparent = parent.parent;
        if (grandparent == null) {
            root = node;
            node.parent = null;
        } else {
            relink(grandparent, node, parent == grandparent.left);
        }

        if (node == parent.left) {
            relink(parent, node.right, true);
            relink(node, parent, false);
        } else {
            relink(parent, node.left, false);
            relink(node, parent, true);
        }
    }

    // trinode操作
    private Node restructure(Node node) {
        Node parent = node.parent;
        Node grandparent = parent.parent;

        if ((node == parent.right) == (parent == grandparent.right)) { // 处理需要一次旋转的情况
            rotate(parent);
            return parent;
        } else { // 处理需要两次旋转的情况:第1次旋转后即成为需要一次旋转的情况
            rotate(node);
            rotate(node);
            return node;
        }
    }

    // 返回以node为根结点的子树的第1个元素
    private static Node subtreeFirst(Node node) {
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }

    // 返回以node为根结点的子树的最后1个元素
    private static Node subtreeLast(Node node) {
        while (node.right != null) {
            node = node.right;
        }
        return node;
    }

    // 获取以node为根结点的子树的高度
    private static int getHeight(Node node) {
        return node != null ? node.height : 0;
    }

    // 获取以node为根结点的子树的结点数
    private static int getSize(Node node) {
        return node != null ? node.size : 0;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python onnxruntime推理yolov5和yolov8(最简易版)

支持yolov5和yolov8双模型 其中ChtDeploy中代码如下&#xff1a; import onnxruntime import numpy as np import cv2class ChtDeploy():def __init__(self, img_path, onnx_path, iou_threshold0.45, conf_threshold0.3, detect_w640, detect_h 640):self.img cv2.imread(im…

【LeetCode每日一题】【BFS模版与例题】【二维数组】1293. 网格中的最短路径

BFS基本模版与案例可以参考&#xff1a; 【LeetCode每日一题】【BFS模版与例题】863.二叉树中所有距离为 K 的结点 【LeetCode每日一题】【BFS模版与例题】【二维数组】130被围绕的区域 && 994 腐烂的橘子 思路&#xff1a; 特殊情况&#xff1a; 最短的路径是向下再向…

多维时序 | Matlab实现GRNN广义回归神经网络多变量时间序列预测

文章目录 效果一览文章概述源码设计参考资料效果一览

SpringBoot(详细介绍)

目录 一、简介 1.1、什么是SpringBoot 1.2.、特性 1.3、四大核心 1.4、特点 二、快速入门 2.1、开发流程 2.1.1、创建项目 maven项目 2.1.2、导入场景 场景启动器 2.1.3、主程序 2.1.4、业务 三、Spring Initializr 创建向导 3.1、依赖管理机制 3.1.1、为什么导…

JRebel and XRebel 插件在IDEA中的安装、激活和使用

1、JRebel安装 1、打开idea->setting->plugins->Marketplace 2、搜索插件JRebel and XRebel&#xff0c;点击安装&#xff0c;然后重启idea 如果左侧出现JRebel & XRebel代表已安装 3.离线安装JRebel 根据自己安装的idea版本进行下载电影的jrebel https://plugi…

H5小游戏,象棋

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的,私信本人,发演示地址,可以后再订阅,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、开心消消乐、扑鱼达人、飞机大战等等 <!DOCTYPE html PUBLIC "-//W3C/…

spring学习简单总结(尚硅谷视频

spring学习简单总结 spring-tx-事务注解添加用注解方式进行aop的配置基于注解配置类进行ioc和di的配置 spring-tx-事务注解添加 选择对应事务管理器实现加入到ioc容器 使用注解指定哪些方法需要添加事务 用注解指定方法 用注解方式进行aop的配置 写自己正常的核心业务代…

Python学习 day07(JSON、format()函数)

JSON 各种编程语言存储数据的容器不尽相同&#xff0c;在Python中有字典dict这样的数据类型&#xff0c;而其他语言可能没有对应的字典&#xff0c;为了让不同的语言都能够相互通用的传递数据&#xff0c;JSON就是一种非常良好的中转数据格式&#xff0c;如下&#xff1a; JSON…

前端工具网站合集(持续更新)

综合类网站 那些免费的砖 统计推荐免费工具网站 那些免费的砖 - 优雅地白嫖各种免费资源 (thosefree.com)https://www.thosefree.com/ CSS样式网站 毒蘑菇-配色 CSS 配色&#xff0c;阴影网站 一个好用的配色网站! 毒蘑菇 - 配色 (dumogu.top)https://color.dumogu.top/ …

navicat想把自己库中的表导出给别人的操作

navicat导出表和导入表 导出 右键需要导出的表&#xff0c;选择导出向导 选择csv&#xff0c;然后点击下一步 到这个页面&#xff0c;其实可以不用选择&#xff0c;会自己选择当时选中的表&#xff0c;如果有多张表导出&#xff0c;可以选择其他表。然后点击下一步 到这里是选…

【Django】执行查询——查询JSONField

JSONField 本篇的例子以下面这个模型为基础&#xff1a; from django.db import modelsclass Dog(models.Model):name models.CharField(max_length200)data models.JSONField(nullTrue)def __str__(self):return self.name保存和查询None值 在使用JSONField时&#xff0c…

springboot+jsp汽车配件管理系统idea maven 项目lw

springbootweb汽车配件销售业绩管理系统服务于汽车配件公司业务,实现了客户管理&#xff0c;主要负责对客户相关数据的增删改查方面、渠道管理&#xff0c;主要对渠道信息也就是设备的供应商渠道信息进行管理、项目管理&#xff0c;主要是一些项目信息的记录与整理、销售数据管…

【Photoshop2020版本】零基础笔记(一)

哈喽大家好~最近博客内容换方向了哈哈哈~换成“实用版”了。 今天给大家带来的是 PS 相关内容 其实我也是刚学PS&#xff0c;所以想着自己做笔记还不如发布出去&#xff0c;让大家都能看到&#xff0c;有兴趣的伙伴们&#xff0c;可以跟着我的笔记一块学习&#xff0c;这个专…

Python-Numpy-计算矩阵相乘

向量化矩阵计算公式&#xff1a; """ Title: matrix_calculating Time: 2024/3/6 Author: Michael Jie """import numpy as npw np.array([[1, 2]]) x np.array([[1, 1], [2, 3], [4, 5]]) b 1# w * x b print(w * x b) """…

Cookie属性HttpOnly引起的漏洞解决方案

问题描述&#xff1a; 项目扫描的时候出一个漏洞&#xff0c;Cookie未配置HttpOnly标志。那HttpOnly是什么呢&#xff1f;Httponly是微软对cookie做的扩展。这个主要是解决用户的cookie可能被盗用的问题。在web应用中、JSESSIONID (Cookie)没有设置Httponly属性可能会窃取或操…

越来越多的上市企业进行FTP替代,真相是什么?

FTP协议大家并不陌生&#xff0c;它是用于在网络上进行文件传输的一套标准协议&#xff0c;基于TCP传输&#xff0c;工作在OSI模型的第七层。FTP协议包括两个主要组成部分&#xff1a;FTP服务器和FTP客户端。FTP服务器用于存储文件&#xff0c;而用户则可以使用FTP客户端通过FT…

SORA技术解析

sora文生视频 sora视频生成过程&#xff1a;视频编码加噪降噪视频解码 视频压缩网络实现降维 时空patches统一视频分割 transformer架构凸显“scaling law”的暴力美学 数据资源更为丰富 参考资料&#xff1a; 1.sora技术报告

【三维重建】相移法+格雷码

本篇文章介绍一种稠密点云的获取方式——条纹结构光三维重建算法。 在学习此算法前&#xff0c;我们需要对基于视觉的三维重建算法有一定了解。 需要了解什么是相机模型、相机标定以及三角化的相关知识。 【三维重建】摄像机几何-CSDN博客 【三维重建】摄像机标定&#xff…

服务永不止步!苏州金龙圆满护航2024年春运

为期40天的2024年春运于3月5日正式落下帷幕。今年春运40天全社会跨区域人员流动量预计累计超84亿人次&#xff0c;在流动量创历史新高的同时还遭遇了冻雨、暴雪等多重考验。但是在社会各界通力合作下&#xff0c;各地运输工作仍然有条不紊地进行&#xff0c;最终克服重重困难&a…

[C语言]——scanf和printf介绍

目录 一.printf 1.基本用法 2.占位符 3.占位符列举 4.输出格式 4.1限定宽度 4.2总是显示正负号 4.3限定小数位数 4.4输出部分字符串 二.scanf 1.基本用法 2.scanf的返回值 3.占位符 4.赋值忽略符 一.printf 1.基本用法 printf() 的作⽤是将参数⽂本输出到屏幕。…