Java 数据结构篇-实现 AVL 树的核心方法

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
 

文章目录

        1.0 AVL 树的说明

        2.0 AVL 树的成员变量及其构造方法

        3.0 实现 AVL 树的核心方法

        3.1 获取当前节点的高度 height(AVLNode node)

        3.2 更新当前节点的高度 updateHeight(AVLNode node)

        3.3 平衡因子 bf(AVLNode node)

        3.4 对失衡节点旋转 rotate(AVLNode node)

        3.5 检查节点是否平衡,重新平衡 balance(AVLNode node) 

        3.6 插入、更新节点 put(int key, Object value)

        3.7 删除节点 remove (AVLNode node)

        4.0 实现 AVLTree 核心方法的完整代码


 

        1.0 AVL 树的说明

        AVL树是一种自平衡二叉搜索树,它的名称来源于它的发明者 Adelson-Velsky 和 Landis 。在AVL树中,任何节点的两个子树的高度最多相差 1,这使得AVL树能够保持相对平衡,从而保证了树的查找、插入和删除操作的时间复杂度都是 O(log n) 

        AVL树的平衡性是通过对节点进行旋转操作来实现的,包括左旋右旋左右旋右左旋。当插入或删除节点后破坏了AVL树的平衡性时,就会进行相应的旋转操作来保持树的平衡。

        也就是说, AVL 树是一种特殊的自平衡二叉搜索树。

        

        2.0 AVL 树的成员变量及其构造方法

        (1)构造 AVLNode 内部类变量 :

                int key 关键字:通过关键字来比较每个节点的大小。

                Object value 值:通过该变量存放值。

                AVLNode left:引用左孩子节点。

                AVLNode right:引用右孩子节点。

                int height 高度:表示当前节点的高度,默认初始化为 1 。

        (2)AVLNode 内部类构造方法:

                重载两个内部类的构造方法分别为:参数为 key,value 的构造方法、参数为 key,value,left,right 的构造方法。

        (3)构造 AVLTree 外部类 :

                AVLNode root:表示该树的头节点。

代码如下:

public class AVLTree {

    AVLNode root = null;

    static class AVLNode {
        int key;
        Object value;
        AVLNode left;
        AVLNode right;
        int height = 1;

        public AVLNode(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public AVLNode(int key, Object value, AVLNode left, AVLNode right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

}

        3.0 实现 AVL 树的核心方法

        AVL 树的最核心的方法就是插入、更新、删除操作,因为这些操作都有可能造成二叉搜索树失去平衡。为了解决自平衡的特点,需要每一个插入或者更新、删除操作之后,需要检查是否失去平衡,若失去平衡需要通过左旋、右旋、左右旋、右左旋来重新达到平衡状态;若没有失去平衡,无需任何操作。

        3.1 获取当前节点的高度 height(AVLNode node)

        不能直接通过 node.height 得到当前节点的高度,是因为默认高度为 1,若出现该节点为 null 时,就会出现矛盾,因此需要先判断该节点是否为 null 节点,若为空节点,返回 0 ;若不为 空节点,则返回当前节点 node.height 即可。

代码如下:

    //获取当前节点的高度
    private int height (AVLNode node) {
        return node == null ? 0 : node.height;
    }

 

        3.2 更新当前节点的高度 updateHeight(AVLNode node)

        由于通过删除、插入、旋转都有可能导致当前节点的高度发生改变,所以需要更新高度。实现该方法也很简单,判断当前节点的左右节点的高度,取最大的高度 + 1 就是为当前节点的高度。

代码如下:

    //更新当前的高度
    private void updateHeight (AVLNode node) {
        node.height = Integer.max(height(node.left),height(node.right)) + 1;
    }

        3.3 平衡因子 bf(AVLNode node)

        判断当前节点是否失去平衡,当该节点的左子树的高度 - 右子树的高度 > 1或者 < -1 即失去平衡了。若差值为 1、0、-1,表示没有失去平衡

代码如下:

    //平衡因子
    private int bf (AVLNode node) {
        return  height(node.left) - height(node.right);
    }

        3.4 对失衡节点旋转 rotate(AVLNode node)

        有四种情况:左旋、右旋、左右旋、右左旋

        左旋:需要先拿到失衡节点 node 的右孩子节点 node.right ,将 r = node.right 赋值给 r 。先将 r.left 赋值给 node.right ,即 node.right = r.left 进行 "换爹" 操作,然后再 "上位" r.left = node 。最后,因为旋转会导致当前 node 的节点与上位后的节点 r 的高度都有可能会改变,所以需要及时更新高度,通过 updateHeight(node),updateHeight(r),需要注意的是,更新的顺序不能改变。

        右旋:跟左旋的原理是一样的,需要先拿到失衡节点 node 的左孩子节点 node.left ,将 l = node.left 赋值给 l 。先将 l.right 赋值给 node.left ,即 node.left = l.right 进行 "换爹" 操作,然后再 "上位" l.right = node 。最后,因为旋转会导致当前 node 的节点与上位后的节点 r 的高度都有可能会改变,所以需要及时更新高度,通过 updateHeight(node),updateHeight(l),需要注意的是,更新的顺序不能改变。

        左右旋:通过结合左旋、右旋实现左右旋。先拿到当前节点的左节点 l = node.left,对于 l 节点需要用到左旋的方法进行旋转 leftRotate(l),旋转后需要重新赋值 node.left = leftRotate(l) 。接着对于 node 节点需用用到右旋方法进行旋转 rightRotate(node) 。最后返回 rightRotate(node) 节点即可。

        右左旋:通过结合右旋、左旋实现右左旋。先拿到当前节点的右节点 r = node.right,对于 r 节点需要用到右旋的方法进行旋转 rightRotate(r) ,旋转后需要重新赋值 node.right = rightRotate(r) 。接着对于 node 节点需要用到左旋方法 leftRotate(node) 。最后返回 leftRotate(node) 节点即可。

代码如下:

    //左旋
    private AVLNode leftRotate (AVLNode node) {
        AVLNode r = node.right;
        node.right = r.left;
        r.left = node;
        updateHeight(node);
        updateHeight(r);
        return r;
    }

    //右旋
    private AVLNode rightRotate (AVLNode node) {
        AVLNode l = node.left;
        node.left = l.right;
        l.right = node;
        updateHeight(node);
        updateHeight(l);
        return l;
    }

    //左右旋
    private AVLNode leftRightRotate (AVLNode node) {
        AVLNode l = node.left;
        node.left = leftRotate(l);
        return rightRotate(node);
    }

    //右左旋
    private AVLNode rightLeftRotate (AVLNode node) {
        AVLNode r = node.right;
        node.right = rightRotate(r);
        return leftRotate(node);
    }

        3.5 检查节点是否平衡,重新平衡 balance(AVLNode node) 

        介绍四种失衡状态的树

        LL : 当前节点 node 的左子树的高度 - 右子树的高度 > 1,且 node.left 的左子树的高度 - node.left 的右子树的高度 >= 0 。实现该情况重新平衡,只需要当前节点进行右旋操作即可。

        LR:当前节点 node 的左子树的高度 - 右子树的高度 > 1,且 node.left 的左子树的高度 - node.left 的右子树的高度 < 0 。实现该情况重新平衡,需要进行先将 node.left 节点进行左旋,重新 node.left = leftRotate(node.left),接着对于 node 进行右旋即可,也就是上面已经实现的左右旋方法。

        RL:当前节点 node 的左子树的高度 - 右子树的高度 < -1 ,且 node.right 的左子树的高度 - node.right 的右子树的高度 > 0 。实现该情况重新平衡,需要用到上面实现了的右左旋方法。

        RR:当前节点 node 的左子树的高度 - 右子树的高度 < -1 ,且 node.right 的左子树的高度 - node.right 的右子树的高度 <= 0 。实现该情况重新平衡,只需要左旋一次操作即可。

四种失衡状态图:

代码如下:

    //检查节点是否失衡,重新平衡代码
    private AVLNode balance (AVLNode node) {
        if(node == null) {
            return  null;
        }
        if (bf(node) > 1 && bf(node.left) >= 0) {
            return rightRotate(node);
        } else if (bf(node) > 1 && bf(node.left) < 0) {
            return leftRightRotate(node);

        } else if (bf(node) < -1 && bf(node.right) <= 0) {
            return leftRotate(node);

        }else if (bf(node) < -1 && bf(node.right) > 0) {
            return rightLeftRotate(node);
        }
        return node;
    }

        当 node == null 时,返回 null 即可。

        3.6 插入、更新节点 put(int key, Object value)

        使用递归实现插入、更新节点。两种情况,若没有找到 key 关键字时,而找到空位的地方插入新节点;若找到 key 关键字时,更新该节点的值即可。区别于一般的二叉搜索树,自平衡的二叉搜索树,需要在插入节点后更新当前节点的高度和通过旋转来重新达到平衡。需要注意的是,更新节点的操作是不会改变高度还有破坏平衡。

代码如下:

    //更新
    public AVLNode put (int key, Object value) {
        return doPut(root,key,value);
    }

    private AVLNode doPut(AVLNode node, int key, Object value) {
        if (node == null) {
            return new AVLNode(key,value);
        }
        if (node.key == key) {
            node.value = value;
            return node;
        }
        if (node.key > key) {
            node.left = doPut(node.left,key,value);
        }else {
            node.right = doPut(node.right,key,value);
        }
        updateHeight(node);
        return balance(node);
    }

        3.7 删除节点 remove (AVLNode node)

        使用递归实现删除节点思路:

        (1)node == null

        (2)没有找到 key

        (3)找到 key 1) 没有 2)只有一个孩子 3)有两个孩子

        (4)更新高度

        (5)balance

代码如下:

    //删除
    public AVLNode remove (int key) {
        return doRemove(root,key);
    }
    private AVLNode doRemove (AVLNode node,int key) {
        if (node == null) {
            return null;
        }
        if (node.key > key) {
            node.left = doRemove(node.left,key);
        } else if (node.key < key) {
            node.right = doRemove(node.right,key);
        }else {

            if (node.left == null && node.right == null) {
                return null;
            } else if (node.right == null) {
                node = node.left;
            } else if (node.left == null) {
                node = node.right;
            }else {
                AVLNode p = node.right;
                while (p.left != null) {
                    p = p.left;
                }
                p.right = doRemove(node.right,p.key);
                p.left = node.left;
                node = p;
            }
        }
        updateHeight(node);
        return balance(node);
    }

        4.0 实现 AVLTree 核心方法的完整代码

public class AVLTree {

    AVLNode root = null;

    static class AVLNode {
        int key;
        Object value;
        AVLNode left;
        AVLNode right;
        int height = 1;

        public AVLNode(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public AVLNode(int key, Object value, AVLNode left, AVLNode right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }


    //获取当前节点的高度
    private int height (AVLNode node) {
        return node == null ? 0 : node.height;
    }

    //更新当前的高度
    private void updateHeight (AVLNode node) {
        node.height = Integer.max(height(node.left),height(node.right)) + 1;
    }

    //平衡因子
    private int bf (AVLNode node) {
        return  height(node.left) - height(node.right);
    }

    //左旋
    private AVLNode leftRotate (AVLNode node) {
        AVLNode r = node.right;
        node.right = r.left;
        r.left = node;
        updateHeight(node);
        updateHeight(r);
        return r;
    }

    //右旋
    private AVLNode rightRotate (AVLNode node) {
        AVLNode l = node.left;
        node.left = l.right;
        l.right = node;
        updateHeight(node);
        updateHeight(l);
        return l;
    }

    //左右旋
    private AVLNode leftRightRotate (AVLNode node) {
        AVLNode l = node.left;
        node.left = leftRotate(l);
        return rightRotate(node);
    }

    //右左旋
    private AVLNode rightLeftRotate (AVLNode node) {
        AVLNode r = node.right;
        node.right = rightRotate(r);
        return leftRotate(node);
    }

    //检查节点是否失衡,重新平衡代码
    private AVLNode balance (AVLNode node) {
        if(node == null) {
            return  null;
        }
        if (bf(node) > 1 && bf(node.left) >= 0) {
            return rightRotate(node);
        } else if (bf(node) > 1 && bf(node.left) < 0) {
            return leftRightRotate(node);

        } else if (bf(node) < -1 && bf(node.right) <= 0) {
            return leftRotate(node);

        }else if (bf(node) < -1 && bf(node.right) > 0) {
            return rightLeftRotate(node);
        }
        return node;
    }

    //更新
    public AVLNode put (int key, Object value) {
        return doPut(root,key,value);
    }

    private AVLNode doPut(AVLNode node, int key, Object value) {
        if (node == null) {
            return new AVLNode(key,value);
        }
        if (node.key == key) {
            node.value = value;
            return node;
        }
        if (node.key > key) {
            node.left = doPut(node.left,key,value);
        }else {
            node.right = doPut(node.right,key,value);
        }
        updateHeight(node);
        return balance(node);
    }

    //删除
    public AVLNode remove (int key) {
        return doRemove(root,key);
    }
    private AVLNode doRemove (AVLNode node,int key) {
        if (node == null) {
            return null;
        }
        if (node.key > key) {
            node.left = doRemove(node.left,key);
        } else if (node.key < key) {
            node.right = doRemove(node.right,key);
        }else {

            if (node.left == null && node.right == null) {
                return null;
            } else if (node.right == null) {
                node = node.left;
            } else if (node.left == null) {
                node = node.right;
            }else {
                AVLNode p = node.right;
                while (p.left != null) {
                    p = p.left;
                }
                p.right = doRemove(node.right,p.key);
                p.left = node.left;
                node = p;
            }
        }
        updateHeight(node);
        return balance(node);
    }
}

 

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

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

相关文章

软件安全测评需要关注哪些?湖南CMA、CNAS软件测试公司推荐

在当今信息化的社会&#xff0c;软件安全问题日益凸显&#xff0c;给个人和企业的数据安全造成了极大的威胁。为了保障软件的安全性&#xff0c;软件安全测评应运而生。 软件安全测评是通过对软件系统的评估&#xff0c;发现其中存在的安全漏洞和风险&#xff0c;为软件的开发…

大模型 RAG 问答技术架构及核心模块盘点:从 Embedding、prompt-embedding 到 Reranker

对于RAG而言&#xff0c;2023年已经出现了很多工作&#xff0c;草台班子有了一堆&#xff0c;架构也初步走通&#xff0c;2024年应该会围绕搜索增强做更多的优化工作。 因此我们今天来系统回顾下RAG中的模块&#xff0c;包括一些架构&#xff0c;文本嵌入embedding等&#xff…

MATLAB根据数据拟合曲线

MATLAB根据数据拟合曲线 MATLAB根据数据拟合曲线视频观看 MATLAB根据数据拟合曲线 x1[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,6…

深入浅出Android dmabuf_dump工具

dmabuf是什么&#xff1f; 可以参考我之前写的一篇文章&#xff0c;在这篇文章中有介绍dma_buf&#xff1a;BufferManager_驱动的buffermanager-CSDN博客 dmabuf_dump工具介绍(基于Android 14) dmabuf_dump是一个可执行文件&#xff0c;接收参数调用libdmabufinfo.a的接口完成…

C#,入门教程(15)——类(class)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(14)——字符串与其他数据类型的转换https://blog.csdn.net/beijinghorn/article/details/124004562 物以类聚&#xff0c;凡物必类。 类的使用&#xff0c;须遵循几个简单的原则&#xff1a; &#xff08;1&#xff09;能类则类&a…

宏集案例丨宏集PC Runtime软件助推食品行业生产线数字化革新

来源&#xff1a;宏集科技 工业物联网 宏集案例丨宏集PC Runtime软件助推食品行业生产线数字化革新 原文链接&#xff1a;https://mp.weixin.qq.com/s/DwzVzifUiidNr-FT3Zfzpg 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 01 前言 近年来&#xff0c;中国食品行业…

想进入游戏开发领域,应该先学习C++编程还是C#编程?

想进入游戏开发领域&#xff0c;应该先学习C编程还是C#编程&#xff1f; 当你决心踏入游戏开发者的行列时&#xff0c;最先迎接你的将是引擎的选择。引擎是游戏的心脏&#xff0c;所有精彩的画面和内容都是脉脉游戏血液从引擎中流淌而出。Unity、Unreal Engine、Cocos等引擎盛…

牛客字符串

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

存储卷(数据卷)—主要是nfs方式挂载

1、定义 容器内的目录和宿主机的目录进行挂载 容器在系统上的生命周期是短暂的&#xff0c;一旦容器被删除&#xff0c;数据会丢失。k8s基于控制器创建的pod&#xff0c;delete相当于重启&#xff0c;容器的状态会恢复到原始状态。一旦回到原始状态&#xff0c;后天编辑的文件…

二叉树的层序遍历(C++详解)

文章目录 写在前面解题思路具体做法 写在前面 本篇文章使用C实现了二叉树的层序遍历。在实现二叉树层序遍历时&#xff0c;一般情况下&#xff0c;大家可能直接输出遍历结果。然而&#xff0c;在解决在线评测&#xff08;OJ&#xff09;问题时&#xff0c;有时要求将每一层的遍…

这7个设计素材网站太好用了,特别是第一款!

网页设计师在使用网页设计素材时&#xff0c;会优先考虑那些免费优质的网页设计素材网站。找到一个免费优质的网页设计素材网站并不容易。有些网站要么需要开设材料网站的会员&#xff0c;要么设计素材质量差。即时设计整理总结了 7 个免费的网页设计素材网站&#xff0c;对 “…

GENMARK控制器维修SMALL SMC4092

晶圆转移机器人SMALL CONTROLLER控制器维修 SMC1100 半导体设备机械臂GENMARK控制器维修 eSensor特点&#xff1a; &#xff08;1&#xff09;基于DNA杂交和电化学检测原理&#xff1b; &#xff08;2&#xff09;电化学传感检测&#xff0c;并非荧光或光学检测。 电子信号的…

中国光伏展

中国光伏展是中国最大的光伏产业展览会&#xff0c;每年在国内举办一次。该展览会汇集了国内外光伏行业的领先企业和专业人士&#xff0c;展示最新的光伏技术、产品和解决方案。 中国光伏展旨在促进光伏行业的发展和创新&#xff0c;提升光伏产业的国际竞争力。展览会涵盖了光伏…

一、Sharding-JDBC系列01:整合SpringBoot实现分库分表,读写分离

目录 一、概述 二、案例演示-水平分表 (1)、创建springboot工程 (2)、创建数据库和数据表 (3)、application.yaml配置分片规则 (4)、测试数据插入、查询操作 4.1、插入-控制台SQL日志 4.2、查询-控制台SQL日志 三、案例演示-水平分库 (1)、创建数据库和数据表 (2…

React07-路由管理器react-router-dom(v6)

react-router 是一个流行的用于 React 应用程序路由的库。它使我们能够轻松定义应用程序的路由&#xff0c;并将它们映射到特定的组件&#xff0c;这样可以很容易地创建复杂的单页面应用&#xff0c;并管理应用程序的不同视图。 react-router 是基于 React 构建的&#xff0c;…

离线安装telnet-server

telnet下载地址&#xff1a; https://vault.centos.org/ 需要下载telnet 和 telnet-server 确认自己的服务器版本&#xff0c;我这里使用的是&#xff08;Red Hat Enterprise Linux Server release 7.0 (Maipo)&#xff09;对应的是Centos 7.0,所有到 https://vault.centos.or…

平面光波导_三层均匀平面光波导_射线分析法

平面光波导_三层均匀平面光波导_射线分析法 三层均匀平面光波导&#xff1a; 折射率沿 x x x 方向有变化&#xff0c;沿 y y y、 z z z 方向没有变化三层&#xff1a;芯区( n 1 n_1 n1​) > > > 衬底( n 2 n_2 n2​) ≥ \geq ≥ 包层( n 3 n_3 n3​)包层通常为空…

许战海矩阵战略洞察:如何解决3亿调味品企业朱老六的增长难题

​长春市朱老六食品股份有限公司&#xff0c;是一家以生产腐乳、酸菜等生物发酵品为主的民营企业。 创始人团队自1991年起开发腐乳产品&#xff0c;并于1997年创立“朱老六”品牌。公司专研地道东北味&#xff0c;有着多年的专业经验和深厚的行业积淀&#xff0c;2019年腐乳产…

创建ROS模型与小机器人地图规划

1、打开自己的VM系统 2、安装小机器人的安装包&#xff0c;输入如下命令&#xff0c;回车输入密码(自己设的)&#xff1a; sudo apt install ros-noetic-turtlebot3-simulations ros-noetic-turtlebot3-slam ros-noetic-turtlebot3-navigation 提示我之前安装过了 3、用rosla…

Redis相关报错信息:Could not connect to Redis at 127.0.0.1:6379: 由于目标计算机积极拒绝,无法连接。

报错信息&#xff1a; Could not connect to Redis at 127.0.0.1:6379: 由于目标计算机积极拒绝&#xff0c;无法连接。 报错原因&#xff1a; 访问不到Redis服务 解决方案&#xff1a; 将Redis服务打开&#xff01; 使用cmd命令行打开本机服务管理&#xff1a; services…