【数据结构与算法】平衡二叉树(AVL树)

news2024/10/7 12:17:39

平衡二叉树(AVL树)

给你一个数列{1,2,3,4,5,6},要求创建二叉排序树(BST),并分析问题所在。

在这里插入图片描述

BST 存在的问题分析

  1. 左子树全部为空,从形式上看,更像一个单链表。
  2. 插入速度没有影响。
  3. 查询速度明显降低(因为需要依次比较),不能发挥 BST 的优势,因为每次还需要比较左子树,其查询速度比单链表还慢。
  4. 解决方案:平衡二叉树(AVL)

基本介绍

  1. 平衡二叉树也叫平衡二叉搜索树(Self - balancing binary search tree)又被称为 AVL 树,可以保证查询速率较高
  2. 具有一下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不能超过 1,并且左右两个子树都是一棵平衡二叉树,平衡二叉树的常用实现方法有:红黑树、AVL、替罪羊树、Treap、伸展树等。

应用案例 - 左旋转

给你一个数列{4,3,6,5,7,8},构建成一棵平衡二叉树。

问题:

当插入 8 时,rightHeight() - leftHight() > 1 成立,此时,不再是一棵 AVL 树了。

思路 - 左旋转:

  1. 创建一个新的节点 newNode(以 4 这个值创建),值等于当前根节点的值;
  2. 把新节点的左子树设置为当前节点的左子树;
  3. 把新节点的右子树设置为当前节点右子树的左子树;
  4. 把当前节点的值换为右子节点的值;
  5. 把当前节点的右子树设置为右子树的右子树;
  6. 把当前节点的左子树设置为新节点。
    在这里插入图片描述

代码实现:

public class AVLTreeDemo {
    public static void main(String[] args) {
        int[] arr = {4, 3, 6, 5, 7, 8};
        // 创建一个 AVLTree
        AVLTree avlTree = new AVLTree();
        // 添加节点
        for (int i = 0; i < arr.length; i++) {
            avlTree.add(new Node(arr[i]));
        }

        // 遍历
        System.out.println("中序遍历");
        avlTree.infixOrder();
        System.out.println("左子树:" + avlTree.getRoot().leftHeight());
        System.out.println("右子树:" + avlTree.getRoot().rightHeight());
        System.out.println("树:" + avlTree.getRoot().height());
    }
}

// 创建 AVLTree
class AVLTree {
    private Node root;

    /**
     * 查找要删除的节点
     *
     * @param value 要删除的节点的值
     * @return 如果找到,返回节点,否则,返回 null
     */
    public Node search(int value) {
        if (root == null) {
            return null;
        } else {
            return root.search(value);
        }
    }

    /**
     * 得到以 node 为节点的最小节点的值,并删除该值
     *
     * @param node 传入的节点
     * @return 返回的是以 node 为根节点的二叉排序树的最小节点的值
     */
    public int delRightTreeMin(Node node) {
        Node target = node;
        // 循环查找左节点,就会找到最小值
        while (target.left != null) {
            target = target.left;
        }
        // 这时 target 就指向了最小节点
        // 删除最小节点
        delNode(target.value);
        return target.value;
    }

    /**
     * 得到以 node 为节点的最大节点的值,并删除该值
     *
     * @param node 传入的节点
     * @return 返回的是以 node 为根节点的二叉排序树的最大节点的值
     */
    public int delLiftTreeMax(Node node) {
        Node target = node;
        // 循环查找右节点,就会找到最小值
        while (target.right != null) {
            target = target.right;
        }
        // 这时 target 就指向了最大节点
        // 删除最大节点
        delNode(target.value);
        return target.value;
    }

    /**
     * 删除节点
     *
     * @param value 要删除节点的值
     */
    public void delNode(int value) {
        if (root == null) {
            return;
        } else {
            // 1. 需要先去找到要删除的节点
            Node targetNode = search(value);
            // 如果没有找到要删除的节点
            if (targetNode == null) {
                return;
            }
            // 如果我们发现当前这棵二叉排序树只有一个节点
            if (root.left == null && root.right == null) {
                root = null;
                return;
            }
            // 去找到 targetNode 的父节点
            Node parent = searchParent(value);
            // 第一种情况
            // 如果要删除节点是叶子结点
            if (targetNode.left == null && targetNode.right == null) {
                // 判断 targetNode 是父节点的左子节点还是右子节点
                if (parent.left != null && parent.left.value == value) { // 是左子节点
                    parent.left = null;
                } else if (parent.right != null && parent.right.value == value) { // 是右子节点
                    parent.right = null;
                }
            } else if (targetNode.left != null && targetNode.right != null) {
                // 第三种情况
                // 如果要删除的节点是有两棵子树的节点
                // 向右子树找最小值
//                targetNode.value = delRightTreeMin(targetNode.right);
                // 向左子树找最大值
                targetNode.value = delLiftTreeMax(targetNode.left);
            } else {
                // 第二种情况
                // 如果要删除的节点是只有一棵子树的的节点
                // 如果要删除的节点有左子节点
                if (targetNode.left != null) {
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子节点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else { // 如果 targetNode 是 parent 的右子节点
                            parent.right = targetNode.left;
                        }
                    } else {
                        root = targetNode.left;
                    }
                } else { // 如果要删除的节点有右子节点
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子节点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else { // 如果 targetNode 是 parent 的右子节点
                            parent.right = targetNode.right;
                        }
                    } else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }

    /**
     * 查找要删除节点的父节点
     *
     * @param value 要删除节点的值
     * @return 如果找到,放回父节点,否则,返回 null
     */
    public Node searchParent(int value) {
        if (root == null) {
            return null;
        } else {
            return root.searchParent(value);
        }
    }

    /**
     * 添加节点的方法
     *
     * @param node 需要添加的节点
     */
    public void add(Node node) {
        // 如果 root 为空,则直接让 root 指向 node
        if (root == null) {
            root = node;
        } else {
            root.add(node);
        }
    }

    /**
     * 中序遍历
     */
    public void infixOrder() {
        if (root != null) {
            root.infixOrder();
        } else {
            System.out.println("二叉排序树为空,不能遍历");
        }
    }

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }
}

// 创建 Node 节点
class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    /**
     * 左子树的高度
     *
     * @return 返回左子树的高度
     */
    public int leftHeight() {
        return left == null ? 0 : left.height();
    }

    /**
     * 右子树的高度
     *
     * @return 返回右子树的高度
     */
    public int rightHeight() {
        return right == null ? 0 : right.height();
    }

    /**
     * 得到以当前节点为根节点的树的高度
     *
     * @return 返回当前节点的高度
     */
    public int height() {
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }

    /**
     * 左旋转
     */
    private void leftRotate() {
        // 1. 创建一个新的节点 newNode(以 4 这个值创建),值等于当前根节点的值;
        Node newNode = new Node(value);
        // 2. 把新节点的左子树设置为当前节点的左子树;
        newNode.left = left;
        // 3. 把新节点的右子树设置为当前节点右子树的左子树;
        newNode.right = right.left;
        // 4. 把当前节点的值换为右子节点的值;
        value = right.value;
        // 5. 把当前节点的右子树设置为右子树的右子树;
        right = right.right;
        // 6. 把当前节点的左子树设置为新节点。
        left = newNode;
    }

    /**
     * 查找要删除的节点
     *
     * @param value 希望删除的节点的值
     * @return 如果找到返回该节点,否则,返回 null
     */
    public Node search(int value) {
        if (value == this.value) { // 找到该节点
            return this;
        } else if (value < this.value) { // 如果查找的节点小于当前节点,向左子树递归查找
            if (this.left == null) {
                return null;
            }
            return this.left.search(value);
        } else { // 如果查找的节点大于或等于当前节点,向右子树递归查找
            if (this.right == null) {
                return null;
            }
            return this.right.search(value);
        }
    }

    /**
     * 查找要删除节点的父节点
     *
     * @param value 要找到的节点的值
     * @return 返回的是要删除节点的父节点,如果没有就返回 null
     */
    public Node searchParent(int value) {
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
            return this;
        } else {
            // 如果查找的这个值小于当前节点的值,并且当前节点的左子节点不为空
            if (value < this.value && this.left != null) {
                return this.left.searchParent(value); // 向左子树递归查找
            } else if (value >= this.value && this.right != null) { // 如果查找的这个值大于等于当前节点的值,并且当前节点的右子节点不为空
                return this.right.searchParent(value); // 向右子树递归查找
            } else {
                return null;
            }
        }
    }

    /**
     * 添加节点的方法
     * 通过递归的方式添加节点,注意需要满足二叉排序树的要求
     *
     * @param node 需要添加的节点
     */
    public void add(Node node) {
        if (node == null) {
            return;
        }
        // 判断出入节点的值和当前子树的根节点的关系
        if (node.value < this.value) {
            // 如果当前节点的左子节点为 null
            if (this.left == null) {
                this.left = node;
            } else {
                // 递归向左子树添加
                this.left.add(node);
            }
        } else {
            // 如果当前节点的右子节点为 null
            if (this.right == null) {
                this.right = node;
            } else {
                // 递归向右子树添加
                this.right.add(node);
            }
        }
        // 当添加完一个节点后,如果 右子树的高度 - 左子树的高度 > 1,左旋转
        if (rightHeight() - leftHeight() > 1) {
            leftRotate(); // 左旋转
        }
    }

    /**
     * 中序遍历
     */
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }
}

应用案例 - 右旋转

给你一个数列{10,12,8,9,7,6},构建成一棵平衡二叉树。

问题:

当插入 6 时,leftHight() - rightHeight() > 1 成立,此时,不再是一棵 AVL 树了。

思路 - 左旋转:

  1. 创建一个新的节点 newNode(以 10 这个值创建),值等于当前根节点的值;
  2. 把新节点的右子树设置为当前节点的右子树;
  3. 把新节点的左子树设置为当前节点左子树的右子树;
  4. 把当前节点的值换为左子节点的值;
  5. 把当前节点的左子树设置为左子树的左子树;
  6. 把当前节点的右子树设置为新节点。

在这里插入图片描述

代码实现:

/**
 * 右旋转
 */
private void rightRotate() {
    // 1. 创建一个新的节点 newNode(以 10 这个值创建),值等于当前根节点的值;
    Node newNode = new Node(value);
    // 2. 把新节点的右子树设置为当前节点的右子树;
    newNode.right = right;
    // 3. 把新节点的左子树设置为当前节点左子树的右子树;
    newNode.left = left.right;
    // 4. 把当前节点的值换为左子节点的值;
    value = left.value;
    // 5. 把当前节点的左子树设置为左子树的左子树;
    left = left.left;
    // 6. 把当前节点的右子树设置为新节点。
    right = newNode;
}

应用案例 - 双旋转

给你一个数列{10,11,7,6,8,9},构建成一棵平衡二叉树。

问题:

当插入 9 时,leftHight() - rightHeight() > 1 成立,此时,不再是一棵 AVL 树了。但是,当进行了右旋转后发现,它依旧不是一棵 AVL 树。

在这里插入图片描述

思路:

  1. 当符合右旋转条件时
  2. 如果它的左子树的右子树高度大于它的左子树的左子树的高度
  3. 先对当前这个节点的左节点进行左旋转
  4. 再对当前节点进行右旋转的操作即可

在这里插入图片描述

代码实现:

/**
 * 添加节点的方法
 * 通过递归的方式添加节点,注意需要满足二叉排序树的要求
 *
 * @param node 需要添加的节点
 */
public void add(Node node) {
    if (node == null) {
        return;
    }
    // 判断出入节点的值和当前子树的根节点的关系
    if (node.value < this.value) {
        // 如果当前节点的左子节点为 null
        if (this.left == null) {
            this.left = node;
        } else {
            // 递归向左子树添加
            this.left.add(node);
        }
    } else {
        // 如果当前节点的右子节点为 null
        if (this.right == null) {
            this.right = node;
        } else {
            // 递归向右子树添加
            this.right.add(node);
        }
    }
    // 当添加完一个节点后,如果 右子树的高度 - 左子树的高度 > 1,左旋转
    if (rightHeight() - leftHeight() > 1) {
        // 如果它的右子树的左子树高度大于它的右子树的右子树的高度
        if (right != null && right.leftHeight() > right.rightHeight()) {
            // 先对当前这个节点的右节点进行右旋转
            right.rightRotate();
            // 再对当前节点进行左旋转的操作即可
            leftRotate();
        } else {
            // 直接进行左旋转
            leftRotate();
        }
        return;
    }
    // 当添加完一个节点后,如果 左子树的高度 - 右子树的高度 > 1,右旋转
    if (leftHeight() - rightHeight() > 1) {
        // 如果它的左子树的右子树高度大于它的左子树的左子树的高度
        if (left != null && left.rightHeight() > left.leftHeight()) {
            // 先对当前这个节点的左节点进行左旋转
            left.leftRotate();
            // 再对当前节点进行右旋转的操作即可
            rightRotate();
        } else {
            // 直接进行右旋转
            rightRotate();
        }
    }
}

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

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

相关文章

pg实现月累计

获取每月累计数据&#xff1a; ​​​ SELECT a.month, SUM(b.total) AS total FROM ( SELECT month, SUM(sum) AS total FROM ( SELECT to_char(date("Joinin"),YYYY-MM) AS month , COUNT(*) AS sum FROM "APP_HR_Staff_Basic_Info" GROUP BY month ) …

Vue中使用uuid生成唯一ID(脚手架创建自带的)

1.utils 说明&#xff1a;一般封装工具函数。 // 单例模式 import { v4 as uuidv4 } from uuid; // 要生成一个随机的字符串&#xff0c;且每次执行不能发生变化 // 游客身份还要持久存储 function getUUID(){// 先从本地获取uuid&#xff0c;本地存储里面是否有let uuid_tok…

淘宝整店商品如何批量获取?获取淘宝店铺所有商品接口item_search_shop

在竞争日益激烈的电商行业&#xff0c;不少商家出于以下的考虑&#xff0c;想要实现一键批量获取淘宝店铺的所有商品。 竞争分析&#xff1a;通过获取某个店铺内的所有商品信息&#xff0c;可以对竞争对手的产品进行全面的了解和分析。可以了解到对手的产品种类、价格、销量等情…

git和github学习

一、什么是git和github? 二、学会使用github desktop应用程序 初始使用&#xff1a; 一开始我们是新账户&#xff0c;里面是没有仓库的&#xff0c;需要手动创建一个仓库。此时&#xff0c;这个仓库是创建在本地仓库里面&#xff0c;需要用到push命令&#xff08;就是那个pub…

【C++进阶】:异常

异常 一.异常的概念二.基本使用三.异常重新抛出四.异常规范五.异常安全六.异常的优缺点 一.异常的概念 c语言 传统的错误处理机制&#xff1a; 1. 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。…

PCL 大规模点云显示

文章目录 一、什么是LOD?二、基本思想三、LOD算法常见的实现方式四、PCL库中LOD的实现官方代码结果展示参考文献:随着三维激光扫描技术的发展,我们目前能采集到 海量的点云数据,但是如何将千万甚至上亿级别的点云进行流畅显示,一直以来都是困扰业界的一大难题。尤其在早期…

volte端到端问题分析(一)

1、MME专载保持功能验证 **描述&#xff1a;**当无线环境较差时&#xff0c;有可能由于“Radio_Connection_with_UE_Lost” 原因造成的VoLTE通话掉话&#xff0c;如果UE发生RRC重建成功&#xff0c;手机将不会掉话。 对MME1202进行功能验证&#xff1a;开启后&#xff0c;MME专…

react-virtualized可视化区域渲染的使用

介绍 github地址&#xff1a;https://github.com/bvaughn/react-virtualized 实例网址&#xff1a;react-virtualized如果体积太大&#xff0c;可以参考用react-window。 使用 安装&#xff1a; yarn add react-virtualized。在项目入口文件index.js中导入样式文件&#xff…

Linux下QtCreator勾选Use root user后出现error while loading shared libraries的问题

文章目录 背景解决办法其他解决办法 背景 在linux下调试程序时&#xff0c;有时候需要取得root权限才能连接操作某些设备。 之前我是通过脚本方式 [在QtCreator中先执行自定义命令再执行程序]来进行的。也就是在脚本中取得权限&#xff0c;脚本内容类似这样&#xff1a; echo…

图书管理借阅系统【Java简易版】Java三大特征封装,继承,多态的综合运用

前言 前几篇文章讲到了Java的基本语法规则&#xff0c;今天我们就用前面学到的数组&#xff0c;类和对象&#xff0c;封装&#xff0c;继承&#xff0c;多态&#xff0c;抽象类&#xff0c;接口等做一个图书管理借阅系统。 文章目录 &#x1f947;1.分析图书管理系统要实现的功…

ClickHouse(十五):Clickhouse MergeTree系列表引擎 - AggregatingMergeTree

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &…

VMnet0 桥接设置

VMnet0 一定要设置为你的硬件物理网卡&#xff0c;不能设置自动&#xff0c;不然后&#xff0c;网线一断&#xff0c;就再也连不上了。必须重启电脑才能连上&#xff0c;这个问题找了很久才找到。 下面有个hyper-V虚拟网卡&#xff0c;如果选自动的话&#xff0c;物理网卡一掉…

关于MySQL中的binlog

介绍 undo log 和 redo log是由Inno DB存储引擎生成的。 在MySQL服务器架构中&#xff0c;分为三层&#xff1a;连接层、服务层&#xff08;server层&#xff09;、执行层&#xff08;存储引擎层&#xff09; bin log 是 binary log的缩写&#xff0c;即二进制日志。 MySQL…

交叉编译详细版总结

1.交叉编译 交叉编译&#xff1a;在一个平台生成另外一个平台可执行的代码。 编译&#xff1a;在一个平台上生成在该平台上的可执行代码。 C51/32 交叉编译发送在Keil&#xff08;集成环境上面&#xff09;&#xff0c;windows上面编写51/32代码 ,不是在windows上面运行 在…

[QT编程系列-41]:Qt QML与Qt widget 深入比较,快速了解它们的区别和应用场合

目录 1. Qt QML与Qt widget之争 1.1 出现顺序 1.2 性能比较 1.3 应用应用领域 1.4 发展趋势 1.5 QT Creator兼容上述两种设计风格 2. 界面描述方式的差别 3. QML和Widgets之间的一些比较 4. 选择QML和Widgets之间的Qt技术时&#xff0c;可以考虑以下几个因素&#xff…

protobuf中zigzag编码原理

前面两篇博客 varint原理 - 正数的编码和解码_YZF_Kevin的博客-CSDN博客 varint原理 - 负数的编码和解码_YZF_Kevin的博客-CSDN博客 我们分析了varint对正数&#xff0c;负数的编码解码方式&#xff0c;也知道了如果用varint表示负数的坑&#xff0c;那就是负数直接占10个字…

代码随想录二刷博客Day3~Day4

707. 设计链表 这道题的解题思路其实就是让我们模拟一个链表的实现&#xff1a; 首先我们先要创建一个内部类作为链表的结点&#xff0c;这个内部类要包含两个元素&#xff0c;一个是val值&#xff0c;一个是指向下一个节点的指针 在构造方法这里我们要初始化一个虚拟头街点…

解决页面是Whitelabel Error Page方法之一

在网上随便一搜都能搜到很多当页面是Whitelabel Error Page时的解决方法&#xff0c;这里就不一一赘述了&#xff0c;如果试过了各种方法都不能解决&#xff0c;可以看看我这个解决方法&#xff0c;看看是不是和我的情况相同 我这个bug出现的前提是&#xff0c;SpringBoot项目…

三种方法实现tab栏切换(CSS方法、JS方法、Vue方法)

一、需求 给下图的静态页面添加tab栏切换效果 二、CSS方法 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"v…

人工智能的未来:探索下一代生成模型

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 生成式 AI 目前能够做什么&#xff0c;以及探索下一波生成式 AI 模型需要克服的当前挑战&#xff1f; 如果你跟上科技世界的步伐&#xff0c;你就会知道生成式人工智能是最热门的话题。我们听到了很多关于…