【数据结构】万字长文图解+代码实现AVL树

目录

一、概念

二、图解

1.图解插入

2.图解右单旋

 3.图解左单旋

4.图解右左双旋

 5.图解左右双旋

6.验证是否是AVL树

 三、代码实现

一、概念

AVL树是一种高度平衡的二叉搜索树,得名于其发明者的名字(G. M. Adelson-Velskii和E. M. Landis)。

以下是关于AVL树的一些关键概念:

  1. 定义:AVL树是一种特殊的二叉搜索树,其中任何节点的左右子树的高度差绝对值不会超过1。如果任何时候节点的左右子树高度差超过1,就会通过一系列的旋转操作来恢复平衡。
  2. 特点
    • AVL树的左右子树的高度差的绝对值不超过1。
    • 每个节点的左子树和右子树也都是AVL树。
    • 每个节点都有一个平衡因子,它是左子树的高度减去右子树的高度。
  1. 平衡操作:为了保持树的平衡,AVL树在节点插入或删除后可能会变得不平衡。这时,它会通过旋转来重新平衡,旋转包括单旋转和双旋转两种基本类型。这些旋转确保了树在任何时候都满足平衡条件。
  2. 性能优势:由于AVL树始终保持平衡,因此它的搜索效率较高。在平均和最坏的情况下,搜索、插入和删除操作的时间复杂度都是O(log n),其中n是树中节点的数量。这使得AVL树非常适合用于需要频繁查找和更新操作的场景。

综上所述,AVL树以其严格的平衡条件和高效的旋转操作,保证了在各种操作下都能维持较高的性能,因此在需要快速查找和更新的数据结构应用中非常受欢迎。二叉搜索树在极端情况下数据有序查找速度会变成O(n),为了解决这一问题就有了AVL树,AVL树是一颗高度平衡的二叉搜索树,每棵树的左子树与右子树高度差不会超过1,这样降低了树的高度提升查找效率。适合静态数据的查找,不适合频繁的插入和删除

二、图解
1.图解插入

图解插入

首先我们有如下这样一颗AVL树,我们要在这颗树中插入1,80,90这三个元素,与二叉搜索树插入相同,我们首先需要将该节点进行插入。

在插入之前首先我们如何在代码中判断该节点是否平衡呢,我们需要在每个节点中维持一个平衡因子字段

平衡因子等于右子树子树高度减去左子树高度,由于AVL树左右子树的高度差不会超过2,所以当平衡因子等于2或者-2时这颗树就不平衡了。如上述图中各个节点的平衡因子如下

向上述AVL树中插入1时,首先我们需要按照二叉搜索树插入时一样进行插入

【数据结构】图解二叉搜索树的新增、搜索、删除-CSDN博客

在插入后这个时候我们通过观察发现此时这颗AVL树不平衡了,那么我们先要对这颗树上的节点进行平衡因子的更新,我们从插入的位置开始向上进行遍历,如果当前节点是父亲节点的左孩子就让父亲节点平衡因子--,因为是在左子树进行了插入而右子树不变 bf = 右子树高度- 左子树高度,左子树+1相当于 bf = 右子树高度- (左子树高度 + 1)= bf--,同理如果当前节点是父亲节点的右子树那么就让父亲节点的bf++

这个时候我们在更新平衡因子的过程中发现,有节点平衡因子为-2或者2不平衡了,这个时候我们就需要根据具体情况进行旋转来维持平衡。AVL树的四种旋转策略包括左单旋转、右单旋转、左双旋转和右双旋转

下面将通过图解来展示四种旋转方式

2.图解右单旋

上面在插入1,调整平衡因子的过程中我们发现此处平衡因子为-2,所以我们需要对该节点进行旋转调整,当前平衡因子为-2说明此时左树比较高,我们可以通过右旋让左树的高度降低

如上图所示,此时cur节点中也是左子树比右子树高所以我们只需进行右单旋

右单旋操作如下:我们要将父亲节点的左孩子指向左孩子的右孩子,然后将左孩子指向父亲。同时需要注意修改每个节点的父亲节点

旋转后如图所示

*******subLR此处指向的是44******

1.我们在这里旋转的父亲节点就是根节点,所以我们要更新根节点的指向,但是如果我们更新的是子树呢,我们需要记录这个旋转子树的父亲节点,再完成更新后需要将这颗子树新的根节点赋值给原来父亲节点的父亲节点的左或右

2.如果我们进行旋转的节点它是它父亲节点的右孩子,那么我们再旋转完成后需要将旋转节点的父亲节点的右孩子指向旋转后的树

3.如果我们进行旋转的节点它是它父亲节点的左孩子,那么我们再旋转完成后需要将旋转节点的父亲节点的左孩子指向旋转后的树

在完成上述操作后我们观察此图发现,我们还需要修改旋转后的平衡因子

这个时候我们就完成了右旋,但是需要注意的是,subLR有可能是为空的,在上述代码中subLR为空唯一影响的就是修改subRL的父亲节点,所以在此处我们需要加判断处理

 3.图解左单旋

我们完成了上述插入1的操作,并且通过右单旋维护了AVL树的平衡,此时我们再向上面树中插入80和90

首先我们按照二叉搜索树的插入方式进行插入

然后此时我们需要进行平衡因子的更新

更新过程中一直处于平衡状态,所以不需要进行旋转调整,接着我们插入90

开始调整平衡因子

父亲节点的右子树高,孩子节点中右子树也高所以我们只需进行左单旋转即可

左单旋操作如下:parent节点的left指向右孩子的左孩子也就是subRL,parent右孩子的左指针指向parent,同时parent的父亲指针指向subR,subRL的指针也指向parent,前提是subRL不为空

旋转后如图所示

此时在旋转完这个子树后我们需要将旋转节点的父亲节点指向旋转后新的根节点

与上面右旋转类似需要判断是父亲节点的左节点还是右节点或者是根节点

最后修改subR与parent的平衡因子即可

4.图解右左双旋

在下面这颗AVL树的基础上我们插入27

依旧按照二叉搜索树的规则插入

更新平衡因子

遇到2说明此时这颗parent指向的子树是不平衡的,那么我们观察发现parent的平衡因子为2说明右树高,但是cur指向的parent的右孩子平衡因子是-1说明左树高,这个时候我们需要先对cur进行一次右旋然后再对parent进行左选,也就是说我们需要进行右左双旋。先对cur进行右旋

然后对parent进行左旋转

接下来我们只需调整平衡因子即可,平衡因子的调节需要根据subRL的因子来进行判断所修改哪些节点的平衡因子

此时subRL的平衡因子是-1,我们要进行如下更新

下图中subRL平衡因子为1,进行如下更新

 5.图解左右双旋

在下面这颗AVL树中插入16

按照二叉搜索树的规则进行插入

开始调整平衡因子

先将cur节点进行左旋,然后再将parent进行右旋

在将parent进行右旋

最后我们需要根据subLR的平衡因子进行平衡因子调整,此时subLR是-1

下图中subLR的平衡因子为1

6.验证是否是AVL树

1.采用层序遍历

public void level(TreeNode root) {
        List<List<TreeNode>> ans = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<TreeNode> row = new ArrayList<>();
            while (size-- != 0) {
                TreeNode top = queue.poll();
                row.add(top);
                if (top != null) queue.offer(top.left);
                if (top != null) queue.offer(top.right);
            }
            ans.add(row);
        }


        for (List<TreeNode> x : ans) {
            for (TreeNode val : x) {
                if (val != null)
                    System.out.print(val.val + " ");
                else
                    System.out.print("null ");
            }
            System.out.println();
        }
    }

2.判断是否是平衡树

 private int maxDepth(TreeNode root) {
        // 1. 如果根节点为空则高度为0
        if (root == null) {
            return 0;
        }

        // 2. 计算左右子树高度
        int left = maxDepth(root.left);
        int right = maxDepth(root.right);
        // 3. 判断是否满足平衡二叉树
        if (left != -1 && right != -1 &&Math.abs(left - right) <= 1) {
            return Math.max(left,right) + 1;
        } else {
            // 3.1 不满足则返回-1
            return -1;
        }
    }

    public boolean isBalanced(TreeNode root) {
        // 1. 如果根节点为空则返回真
        if (root == null) {
            return true;
        }

        // 2. 根据求高度时返回值判断
        return maxDepth(root) >= 0;
    }

 三、代码实现
package avl;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Test {
    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 void insert(int val) {
        // 1. 先进行插入
        // 1.1 如果根节点为空直接创建一个节点让root指向它
        if (root == null) {
            root = new TreeNode(val);
            return;
        }
        // 1.2 此时根节点不为空,遍历AVL树找到合适的插入位置
        TreeNode cur = root, parent = null;
        while (cur != null) {
            // 1.2.1 如果当前节点值比待插入的值小那么就去右子树中寻找合适的插入位置
            if (cur.val < val) {
                // 1.2.2 记录父亲节点
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                // 1.2.3 如果当前节点值比待插入的值大那么就去左子树中寻找合适的插入位置
                parent = cur;
                cur = cur.left;
            } else {
                // 1.2.4 如果有相同的元素则直接返回
                return;
            }
        }
        // 1.3 找到合适的位置
        TreeNode node = new TreeNode(val);
        if (parent.val > val) {
            // 1.3.1 如果待插入值比父亲节点小就插入到左边
            parent.left = node;
        } else {
            // 1.3.2 如果待插入值比父亲节点大就插入到右边
            parent.right = node;
        }
        // 1.4 将插入节点的父亲节点进行赋值
        node.parent = parent;

        // 2. 进行平衡因子的修改
        cur = node;
        while (parent != null) {
            // 2.1 判断父亲节点的平衡因子变化情况
            if (cur == parent.left) {
                parent.bf--;
            } else {
                parent.bf++;
            }

            // 2.2 对当前父亲节点的平衡因子进行判断
            if (parent.bf == 0) {
                // 2.2.1 此时说明已经平衡不再需要调整
                break;
            } else if (parent.bf == -1 || parent.bf == 1) {
                // 2.2.2 此时说明当前父亲节点所在的子树平衡但是父亲节点的父亲平衡是未知的,继续遍历
                cur = parent;
                parent = cur.parent;
            } else {
                // 2.2.3 此时说明当前树已经不平衡,我们需要通过旋转来调整

                // 3. 进行旋转
                if (parent.bf == -2) {
                    // 3.1 左树高
                    if (cur.bf == -1) {
                        // 3.1.1 进行右旋:    parent.bf = -2   cur.bf = -1
                        rotateRight(parent);
                    } else {
                        // 3.1.2 进行左右双旋: parent.bf = -2   cur.bf = 1
                        rotateLeftRight(parent);
                    }
                } else {
                    // 3.2 右树高
                    if (cur.bf == 1) {
                        // 3.2.1 进行左旋:    parent.bf = 2   cur.bf = 1
                        rotateLeft(parent);
                    } else {
                        // 3.2.2 进行右左双旋: parent.bf = 2   cur.bf = -1
                        rotateRightLeft(parent);
                    }
                }
                break;
            }
        }
    }

    private void rotateLeftRight(TreeNode parent) {
        // 1. 记录待右旋后左旋的节点
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        // 2. 双旋
        rotateLeft(parent.left);
        rotateRight(parent);

        // 3. 更新平衡因子
        if (bf == -1) {
            parent.bf = 1;
            subL.bf = subLR.bf = 0;
        } else if (bf == 1) {
            subLR.bf = parent.bf = 0;
            subL.bf = -1;
        }
    }

    private void rotateRightLeft(TreeNode parent) {
        // 1. 记录待右旋后左旋的节点
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;
            
        // 2. 双旋
        rotateRight(parent.right);
        rotateLeft(parent);

        // 3. 更新平衡因子
        if (bf == -1) {
            subR.bf = parent.bf = 0;
            subRL.bf = 1;
        } else if (bf == 1) {
            parent.bf = -1;
            subR.bf = subRL.bf = 0;
        }
    }

    private void rotateLeft(TreeNode parent) {
        // 1. 记录需要节点的位置
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;

        // 2. 开始旋转
        parent.right = subRL;
        subR.left = parent;
        if (subRL != null) subRL.parent = parent;
        TreeNode pParent = parent.parent;
        parent.parent = subR;

        // 3. 将原来节点旋转后的子树与其父亲建立联系
        if (parent == root) {
            root = subR;
            subR.parent = null;
        } else if (parent == pParent.right) {
            pParent.right = subR;
            subR.parent = pParent;
        } else {
            pParent.left = subR;
            subR.parent = pParent;
        }

        // 4. 调整平衡因子
        subR.bf = 0;
        parent.bf = 0;
    }

    private void rotateRight(TreeNode parent) {
        // 1. 记录需要节点的位置
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        // 2. 开始旋转
        parent.left = subLR;
        subL.right = parent;
        if (subLR != null)  subLR.parent = parent;
        TreeNode pParent = parent.parent;  // 在修改parent的parent之前先记录下来后面会使用
        parent.parent = subL;

        // 3. 将原来节点旋转后的子树与其父亲建立联系
        if (parent == root) {
            root = subL;
            subL.parent = null;
        } else if (parent == pParent.left) {
            pParent.left = subL;
            subL.parent = pParent;
        } else {
            pParent.right = subL;
            subL.parent = pParent;
        }

        // 4. 调整平衡因子
        subL.bf = 0;
        parent.bf = 0;
    }

    public static void main(String[] args) {
        Test tree = new Test();
        tree.insert(1);
        tree.insert(11);
        tree.insert(33);
        tree.insert(80);
        tree.insert(55);

        tree.level(tree.root);
        System.out.println(tree.isBalanced(tree.root));
    }

    /**
     * 下面代码为验证是否是AVL树
     * @param root
     */
    public void level(TreeNode root) {
        List<List<TreeNode>> ans = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<TreeNode> row = new ArrayList<>();
            while (size-- != 0) {
                TreeNode top = queue.poll();
                row.add(top);
                if (top != null) queue.offer(top.left);
                if (top != null) queue.offer(top.right);
            }
            ans.add(row);
        }


        for (List<TreeNode> x : ans) {
            for (TreeNode val : x) {
                if (val != null)
                    System.out.print(val.val + " ");
                else
                    System.out.print("null ");
            }
            System.out.println();
        }
    }

    private int maxDepth(TreeNode root) {
        // 1. 如果根节点为空则高度为0
        if (root == null) {
            return 0;
        }

        // 2. 计算左右子树高度
        int left = maxDepth(root.left);
        int right = maxDepth(root.right);
        // 3. 判断是否满足平衡二叉树
        if (left != -1 && right != -1 &&Math.abs(left - right) <= 1) {
            return Math.max(left,right) + 1;
        } else {
            // 3.1 不满足则返回-1
            return -1;
        }
    }

    public boolean isBalanced(TreeNode root) {
        // 1. 如果根节点为空则返回真
        if (root == null) {
            return true;
        }

        // 2. 根据求高度时返回值判断
        return maxDepth(root) >= 0;
    }
}

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

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

相关文章

ELK-介绍及Elasticsearch集群搭建

ELK是三个开源软件的缩写&#xff0c;分别为Elasticsearch、Logstash、kibana它们都是开源软件。后来新增了一个FileBeat&#xff0c;它是一个轻量及的日志收集处理工具&#xff0c;因为Logstash由java程序开发&#xff0c;比较消耗内存资源&#xff0c;后来将Logstash使用go语…

微信小程序如何实现下拉刷新

1.首先在你需要实现下拉刷新页面的json文件中写入"enablePullDownRefresh": true。 2.在js文件的onPullDownRefresh() 事件中实现下拉刷新。 实现代码 onPullDownRefresh() {console.log(开始下拉刷新)wx.showNavigationBarLoading()//在标题栏中显示加载图标this.d…

一个将图片转3D的开源项目TripoSR

TripoSR AI是StabilityAI联合发布的图生3D模型&#xff0c;TripoSR是一个快速的3D物体重建模型。TripoSR能够在不到一秒钟的时间内从单张图片生成高质量的3D模型。TripoSR模型的特点是能够快速处理输入&#xff0c;在 NVIDIA A100 GPU 上不到 0.5 秒的时间内生成高质量的 3D 模…

用于无人机路径规划的增强型粒子群优化

摘要&#xff1a; 近年来&#xff0c;元启发式算法被广泛用于解决多目标优化问题&#xff08;MOOPs&#xff09;。粒子群优化&#xff08;PSO&#xff09;由于其在解决MOOPs方面的有效性&#xff0c;在这些算法中得到了普及。然而&#xff0c;PSO的性能高度依赖于其探索和利用能…

解锁安卓开发利器:深度探析ADB【安卓开发】

引言 在安卓开发与维护过程中&#xff0c;我们经常会遇到一些限制&#xff0c;比如无法直接访问某些系统功能&#xff0c;或者在某些定制系统中 受到限制 。为了解决这些问题&#xff0c;我们需要一种有效的工具来管理和调试安卓设备&#xff0c;而这时候ADB&#xff08;Andro…

离线数仓(五)【数据仓库建模】

前言 今天开始正式数据仓库的内容了, 前面我们把生产数据 , 数据上传到 HDFS , Kafka 的通道都已经搭建完毕了, 数据也就正式进入数据仓库了, 解下来的数仓建模是重中之重 , 是将来吃饭的家伙 ! 以及 Hive SQL 必须熟练到像喝水一样 ! 第1章 数据仓库概述 1.1 数据仓库概念 数…

【打工日常】使用docker部署个人实时在线文档协助编辑器

一、Etherpad介绍 Etherpad是一个高度可定制的开源在线编辑器&#xff0c;提供真正实时的协作编辑。放在自己的服务器里面&#xff0c;可以更大程度的保护自己工作的隐私&#xff0c;并且Etherpad允许您实时协作编辑文档&#xff0c;就像在浏览器中运行的实时多人编辑器一样这样…

Linux网络基础2之http

这里是ky233的主页&#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 目录 一、认识URL 1.认识URL 2.urlencode和urldecode 二、HTTP协议格式 1.快速构建http请求和相应的报文格式 三、http demo 1.GET和POST 2.HTTP的状态码 3.http的特征 4.HTTP常见Head…

ARTS Week 20

Algorithm 本周的算法题为 1222. 可以攻击国王的皇后 在一个 下标从 0 开始 的 8 x 8 棋盘上&#xff0c;可能有多个黑皇后和一个白国王。 给你一个二维整数数组 queens&#xff0c;其中 queens[i] [xQueeni, yQueeni] 表示第 i 个黑皇后在棋盘上的位置。还给你一个长度为 2 的…

VARMA(Vector Auto Regressive Moving Average) in Time Series Modelling

what is VARMA? ARIMA是针对单一变量进行建模的方法,当我们需要进行多变量时序建模时,需要使用VAR and VMA and VARMA模型。 VAR:Vector Auto-Regressive,a generalization of the auto-regressive model for multivariate time series where the time series is station…

高吞吐SFTP连接池设计方案

背景 在现代的数据驱动环境中&#xff0c;安全文件传输协议&#xff08;SFTP&#xff09;扮演着至关重要的角色&#xff0c;它提供了一种安全、可靠的文件传输方式。我们目前项目是一个大型数据集成平台&#xff0c;跟上下游有很多文件对接是通过SFTP协议&#xff0c;当需要处…

如何修复advapi32.dll丢失无法启动程序的问题

如果你在运行Windows程序时遇到了“advapi32.dll丢失无法启动程序”的错误消息&#xff0c;那么这意味着你的计算机缺少这个DLL文件。在本文中&#xff0c;我们将提供一些解决方案&#xff0c;帮助你解决这个问题并恢复计算机的正常运行。 一.advapi32.dll丢失电脑的提示 关于…

C语言编译成库文件的要求

keil编译成库文件 在Keil中&#xff0c;将C语言源文件编译成库文件通常需要进行以下步骤&#xff1a; 创建一个新的Keil项目&#xff0c;并将所需的C语言源文件添加到该项目中。 在项目设置中配置编译选项&#xff0c;确保生成的目标文件符合库文件的标准格式。 编译项目&…

React Three Fiber快速入门

https://threejs-journey.com/lessons/what-are-react-and-react-three-fiber#学习笔记 1.基础知识 resize 填充模版 构建第一个场景 we didn’t have to create a scenewe didn’t have to create the webglrenderthe scene is being rendered on each framethe default…

C++字符串操作【超详细】

零.前言 本文将重点围绕C的字符串来展开描述。 其中&#xff0c;对于C/C中字符串的一些区别也做出了回答&#xff0c;并对于C的&#xff08;string库&#xff09;进行了讲解&#xff0c;最后我们给出字符串的不同表达形式。 开发环境&#xff1a; VS2022 一.字符串常量跟字…

leetcode 热题 100_相交链表

题解一&#xff1a; 哈希表&#xff1a;两链表出现的第一个相同的值就是相交节点&#xff0c;因此我们先用哈希记录链表A所有出现过的值&#xff0c;再遍历链表B查找哈希表&#xff0c;找出第一个相同的值即为结果。 import java.util.HashSet;public class Solution {public …

如何使用Hexo搭建个人博客

文章目录 如何使用Hexo搭建个人博客环境搭建连接 Github创建 Github Pages 仓库本地安装 Hexo 博客程序安装 HexoHexo 初始化和本地预览 部署 Hexo 到 GitHub Pages开始使用发布文章网站设置更换主题常用命令 插件安装解决成功上传github但是web不更新不想上传文章处理方式链接…

【解读】OWASP 大语言模型(LLM)安全测评基准V1.0

大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;是指参数量巨大、能够处理海量数据的模型, 此类模型通常具有大规模的参数&#xff0c;使得它们能够处理更复杂的问题&#xff0c;并学习更广泛的知识。自2022 年以来&#xff0c;LLM技术在得到了广泛的应…

ReactNative项目构建分析与思考之react-native-gradle-plugin

前一段时间由于业务需要&#xff0c;接触了下React Native相关的知识&#xff0c;以一个Android开发者的视角&#xff0c;对React Native 项目组织和构建流程有了一些粗浅的认识&#xff0c;同时也对RN混合开发项目如何搭建又了一点小小的思考。 RN环境搭建 RN文档提供了两种…

多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测

多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测 目录 多维时序 | Matlab实现BiGRU-Mutilhead-Attention双向门控循环单元融合多头注意力机制多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.多维时序 …