AVL树详解

news2025/2/4 2:57:39

1.AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

因此,两位俄罗斯的数学家发明了一种解决上述问题的方法:

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过
1 (需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

如何识别一颗二叉树是AVL树?

  • 它的左右子树都是AVL树
  • 左右子树高度之差的绝对值不超过1

如果一颗二叉树是高度平衡的,它就是AVL树,如果它有n个节点,其高度可以保持在O(log2^n),搜索的时间复杂度O(log2^n)。

2.AVL树的优缺点:

优点:

查找效率高,查找的时间复杂度O(log2^N)

缺点:

插入:AVL必须要保证严格平衡,插入一次数据就可能需要(左单旋,右单旋,左右双旋,右左双旋)

删除:和二叉搜索树删除一样,然后再更新平衡因子,出现不平衡,在进行旋转

所以说,AVL树的插入,删除代价大

AVL树有缺点,所以才构造了红黑树

AVL树适用场景:固定不变的文件查找

3.AVL树的实现 

package test;

class TreeNode {
    int val;
    int bf;
    TreeNode left;
    TreeNode right;
    TreeNode parent;

    public TreeNode(int val) {
        this.val = val;
    }
}

public class AVLTree {
    public TreeNode root;

    public boolean insert(int val) {
        TreeNode node = new TreeNode(val);
        if (root == null) {
            root = node;
            return true;
        }

        TreeNode pre = null;
        TreeNode cur = root;
        while (cur != null) {
            if (cur.val < val) {
                pre = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                pre = cur;
                cur = cur.left;
            } else {
                return false;
            }
        }

        if (pre.val < val) {
            pre.right = node;
        } else {
            pre.left = node;
        }
        //插入新元素后根据平衡因子进行对树的调整
        node.parent = pre;
        cur = node;

        //调节平衡因子
        while (pre != null) {
            //先看cur是parent的左还是右,决定平衡因子++还是--
            if (cur == pre.right) {
                //如果是右树,那么右树高度增加,平衡因子++
                pre.bf++;
            } else {
                //如果是左树,那么右树高度增加,平衡因子--
                pre.bf--;
            }

            //检查当前的平衡因子 是不是绝对值 1 0 -1
            if (pre.bf == 0) {
                //刚插入就平衡,不用继续向上调整
                break;
            } else if (pre.bf == 1 || pre.bf == -1) {
                //插入之后打破平衡,继续向上调整
                cur = pre;
                pre = cur.parent;
            } else {
                if (pre.bf == 2) {//右树高,左旋
                    if (cur.bf == 1) {
                        //左单旋
                        rotateLeft(pre);
                    } else {
                        //cur.bf == -1
                        rotateRL(pre);
                    }
                } else {
                    //pre.bf == -2 左树高,需要降低左树的高度
                    if (cur.bf == -1) {
                        //右单旋
                        rotateRight(pre);
                    } else {
                        //cur.bf == 1
                        //左右双旋
                        rotateLR(pre);
                    }
                }
                break;
            }
        }
        return true;
    }

    //右左双旋
    public void rotateRL(TreeNode pre) {
        TreeNode subR = pre.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

        rotateRight(pre.right);
        rotateLeft(pre);

        if (bf == 1) {
            pre.bf = -1;
            subR.bf = 0;
            subRL.bf = 0;
        } else if (bf == -1) {
            pre.bf = 0;
            subR.bf = 1;
            subRL.bf = 0;
        }
    }

    //左右双旋
    public void rotateLR(TreeNode pre) {
        TreeNode subL = pre.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;
        rotateLeft(pre.left);
        rotateRight(pre);
        if (bf == -1) {
            subL.bf = 0;
            subLR.bf = 0;
            pre.bf = 1;
        } else if (bf == 1) {
            subL.bf = -1;
            subLR.bf = 0;
            pre.bf = 0;
        }
    }

    //左单旋
    public void rotateLeft(TreeNode pre) {
        TreeNode subR = pre.right;
        TreeNode subRL = subR.left;

        pre.right = subRL;
        subR.left = pre;

        if (subRL != null) {
            subRL.parent = pre;
        }

        TreeNode pPre = pre.parent;
        pre.parent = subR;

        if (root == pre) {
            root = subR;
            root.parent = null;
        } else {
            if (pPre.left == pre) {
                pPre.left = subR;
            } else {
                pPre.right = subR;
            }
            subR.parent = pPre;
        }
        subR.bf = 0;
        pre.bf = 0;
    }

    //右单旋
    public void rotateRight(TreeNode pre) {
        TreeNode subL = pre.left;
        TreeNode subLR = subL.right;
        pre.left = subLR;
        subL.right = pre;
        //没有subLR的时候
        if (subLR != null) {
            subLR.parent = pre;
        }
        TreeNode pPre = pre.parent;
        pre.parent = subL;
        //检查当前是不是根节点
        if (pre == root) {
            root = subL;
            root.parent = null;
        } else {
            //不是根节点,判断这棵树是左子树还是右子树
            if (pPre.left == pre) {
                pPre.left = subL;
            } else {
                pPre.right = subL;
            }
            subL.parent = pPre;
        }
        subL.bf = 0;
        pre.bf = 0;
    }

    //验证是不是AVL树
    public boolean isBalance(TreeNode root) {
        if (root == null) {
            return true;
        }
        int leftH = height(root.left);
        int rightH = height(root.right);
        if (rightH - leftH != root.bf) {
            System.out.println(root.val + " 平衡因子异常");
            return false;
        }

        return Math.abs(leftH - rightH) <= 1 && isBalance(root.left) && isBalance(root.right);
    }

    //中序遍历
    public void inorder(TreeNode root) {
        if (root == null) {
            return;
        }
        inorder(root.left);
        System.out.println(" " + root.val + " ");
        inorder(root.right);
    }

    public int height(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftH = height(root.left);
        int rightH = height(root.right);
        return leftH > rightH ? leftH + 1 : rightH + 1;
    }

    public static void main(String[] args) {
//        int[] array1 = {16, 3, 7, 11, 9, 26, 18, 14, 15};
        int[] array2 = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
        AVLTree avlTree = new AVLTree();
//        for (int i = 0; i < array1.length; i++) {
//            avlTree.insert(array1[i]);
//        }
        for (int i = 0; i < array2.length; i++) {
            avlTree.insert(array2[i]);
        }
        System.out.println(avlTree.isBalance(avlTree.root));
    }
}

未完待续~
 

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

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

相关文章

内网穿透软件对比——cpolar : 花生壳(下)

系列文章 内网穿透软件对比——cpolar : 花生壳&#xff08;上&#xff09;内网穿透软件对比——cpolar : 花生壳&#xff08;中&#xff09;内网穿透软件对比——cpolar : 花生壳&#xff08;下&#xff09; 文章目录系列文章1. 前言2. 对比内容2.1.TCP协议功能及操作对比2.1…

【JavaEE】多线程(二)Thread 类及常见方法

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【JavaEE】 ✈️✈️本篇内容:Thread类再剖析&#xff01; &#x1f680;&#x1f680;代码存放仓库gitee&#xff1a;JavaEE初阶代码存放&#xff01; ⛵⛵作者简…

对记录做横向分栏

【问题】 Hi i have a single list of employees. Each employee will have his name and salary. i have given the list of employees to a table but it appears as follows Employee Name Salary harish 3000 kiran 4000 Emili 6000 h…

Jenkins(2)— 配置webhook触发器

1、webhook介绍 Gitee WebHook触发器 的功能是帮助用户 push 代码后&#xff0c;自动回调一个您设定的 http 地址。例如我们可以通过添加webhook触发器来实现这样一个功能&#xff1a; 每当开发push代码到提测分支后&#xff0c;自动触发jenkins构建&#xff0c;运行自动化测…

闲暇之际敲敲代码,记录Leetcode刷题03

文章目录前言一、删除链表中的节点1.1 问题描述1.2 思路分析二、反转链表2.1 问题描述2.2 思路分析前言 利用闲暇之际敲敲代码&#xff0c;提升编程技能及提高算法能力。 一、删除链表中的节点 1.1 问题描述 有一个单链表的 head&#xff0c;我们想删除它其中的一个节点 no…

Tensorflow基础入门超全总结

1.1 TensorFlow介绍 深度学习框架TensorFlow一经发布&#xff0c;就受到了广泛的关注&#xff0c;并在计算机视觉、音频处理、推荐系统和自然语言处理等场景下都被大面积推广使用&#xff0c;现在已发布2.3.0版本&#xff0c;接下来我们深入浅出的介绍Tensorflow的相关应用。 …

记录一个阿里云Android端文件上传的BUG

背景 Android移动端需要接入阿里云视频点播模块下的一个客户端上传的SDK。需要将移动端本地文件上传至阿里云服务器。 问题描述 调用方法及业务逻辑不赘述&#xff0c;贴张官方图&#xff0c;选择了上传地址和凭证的方法进行上传&#xff0c;并且是后台集成点播服务端SDK并调用…

关于哈希表

package com.javase.map.hashmap;import java.util.HashMap; import java.util.Map; import java.util.Set;/*** 关于HashMap&#xff1a;* 1.HashMap集合的底层是哈希表/散列表的数据结构。* 2.哈希表是数组和单向链表的结合体&#xff0c;充分发挥了它们各自的优点。…

【机器学习】三种主要集成学习思想简介

集成学习 集成学习通过训练多个分类器,然后将其组合起来,从而达到更好的预测性能,提高分类器的泛化能力。 目前集成学习有3个主要框架:bagging、boosting、stacking。 bagging套袋法 bagging是并行集成学习方法的最著名代表,其算法过程如下: 从原始样本集中抽取训练…

【云原生进阶之容器】第一章Docker核心技术1.8节——DockerFile解析

1 Dockfile详解 1.1 什么是Dockerfile 首先通过一张图来了解 Docker 镜像、容器和 Dockerfile 三者之间的关系。 通过上图可以看出使用 Dockerfile 定义镜像,运行镜像启动容器。 Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文…

技术分享 | 黑盒测试方法论—场景法

场景法就是模拟用户操作软件时的场景&#xff0c;主要用于测试系统的业务流程。 测试不能只关注某个控件的边界值、等价类是否满足要求&#xff0c;也要关注它的主要功能和业务流程是否正确实现&#xff0c;这时就需要使用场景法来完成。 场景法 用例场景是用来描述流经用例路…

OpenHarmony 物联网设备开发环境搭建

前言 我们介绍的是华为官方推荐的 Windows + Ubuntu 混合开发的环境,使用Windows平台的DevEco Device Tool 进行可视化界面进行相关操作,通过远程连接的方式对接Ubuntu下的DevEco Device Tool,然后对Ubuntu下的源码进行开发、编译、烧录等操作。 目前官方不支持Mac系统,所…

【docker】什么是容器数据卷?

docker的理念回顾 将应用和环境打包成一个镜像&#xff01; 数据&#xff1f;如果数据都在容器中&#xff0c;那么我们容器删除&#xff0c;数据就会丢失&#xff01;需求&#xff1a;数据可以持久化 MySQL&#xff0c;容器删了&#xff0c;删库跑路&#xff01;需求&#x…

测试工具平台 MeterSphere 分享

一、官网地址 meterSphere 二、安装方式 Linux安装 默认账号密码&#xff1a; URL: http://$LOCAL_IP:8081用户名: admin初始密码: metersphere三、nginx配置 直接上配置 location / {proxy_pass http://localhost:8081;client_max_body_size 1000m;#access_log off;# 配…

SPDK代码结构浅析

最近这三周时间一直因为工作的需要在研究SPDK移植到RISCV平台上&#xff0c;在编译通过的时候&#xff0c;也顺带把SPDK的代码粗粗过了一遍&#xff0c;顺便做了一点笔记。 SPDK (Storage Performance Development Kit)其实就是在用户空间&#xff0c;采用轮询的方式无锁的NVM…

通达信下单接口获取指数成份股的步骤分享

通达信下单接口获取指数成份股的步骤分享&#xff1a; ContextInfo.get_sector() 接口&#xff1a;https://gitee.com/metatradeapi 用法&#xff1a; ContextInfo.get_sector(sector, realtime) 释义&#xff1a; 获取板块成份股&#xff0c;只支持取指数成份股 参数&…

DOM学习笔记(坚持~~~~)

1.DOM简介 1.1什么是DOM 文档对象模型简称DOM&#xff0c;W3C组织推荐的处理可扩展标记语言的标准编程接口&#xff0c;通过这些DOM接口可以改变网页的内容&#xff0c;结构和样式。 1.2 DOM树 文档&#xff1a;一个页面就是一个文档&#xff0c;DOM中使用document表示 元素&…

Python+Qt身体特征识别人数统计源码窗体程序

程序示例精选 PythonQt身体特征识别人数统计 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PPythonQt身体特征识别人数统计》编写代码&#xff0c;功能包括了相片&#xff0c;摄像头身体识别…

Javaweb中的Request(请求)和Response(响应)

目录 一、概念 二、请求&#xff08;Request&#xff09; 1.例子简介 2.Request继承体系 3.Request获取请求数据 &#xff08;1&#xff09;请求行 &#xff08;2&#xff09;请求头 &#xff08;3&#xff09;请求体 4.优化请求体参数的获取 5.解决请求参数乱码问…

POSIX Timer

一、特点&#xff1a; 1、使用 POSIX Timer&#xff0c;一个进程可以创建任意多个 Timer。 2、setitmer 计时器时间到达时&#xff0c;可以有多种通知方式&#xff0c;比如信号&#xff0c;或者启动线程。 3、POSIX Timer 则可以使用实时信号。 4、POSIX Timer 是针对有实时要…