基础数据结构及算法——AVL树【自平衡二叉搜索树】解决失衡

历史

AVL 树是一种自平衡二叉搜索树,由托尔·哈斯特罗姆在 1960 年提出并在 1962 年发表。它的名字来源于发明者的名字:Adelson-Velsky 和 Landis,他们是苏联数学家,于 1962 年发表了一篇论文,详细介绍了 AVL 树的概念和性质。

概述

在二叉搜索树中,如果插入的元素按照特定的顺序排列,可能会导致树变得非常不平衡,从而降低搜索、插入和删除的效率。为了解决这个问题,AVL 树通过在每个节点中维护一个平衡因子来确保树的平衡。平衡因子是左子树的高度减去右子树的高度。如果平衡因子的绝对值大于等于 2,则通过旋转操作来重新平衡树。

AVL 树是用于存储有序数据的一种重要数据结构,它是二叉搜索树的一种改进和扩展。它不仅能够提高搜索、插入和删除操作的效率,而且还能够确保树的深度始终保持在 O(log n) 的水平。随着计算机技术的不断发展,AVL 树已经成为了许多高效算法和系统中必不可少的一种基础数据结构。

如果一棵二叉搜索树长的不平衡,那么查询的效率会受到影响,如下图
在这里插入图片描述
通过旋转可以让树重新变得平衡,并且不会改变二叉搜索树的性质(即左边仍然小,右边仍然大)
在这里插入图片描述

如何判断失衡?

如果一个节点的左右孩子,高度差超过 1,则此节点失衡,才需要旋转

失衡的四种情况

LL

在这里插入图片描述

  • 失衡节点(图中 8 红色)的 bf > 1,即左边更高
  • 失衡节点的左孩子(图中 6)的 bf >= 0 即左孩子这边也是左边更高或等高
LR

在这里插入图片描述

  • 失衡节点(图中 8)的 bf > 1,即左边更高
  • 失衡节点的左孩子(图中 3 红色)的 bf < 0 即左孩子这边是右边更高
RL

在这里插入图片描述

  • 失衡节点(图中 3)的 bf <-1,即右边更高
  • 失衡节点的右孩子(图中 6 红色)的 bf > 0,即右孩子这边左边更高
RR

在这里插入图片描述

  • 失衡节点(图中 3)的 bf <-1,即右边更高
  • 失衡节点的右孩子(图中 5 黄色)的 bf <= 0,即右孩子这边右边更高或等高

解决失衡

public class AVLTree<K extends Comparable<K>, V> {
    private AVLNode<K, V> root;

    //左旋,把要旋转的节点 -> 新的根节点
    private AVLNode<K, V> leftRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 2:
         *      2                   4
         *     / \                 / \
         *    1   4         ->    2   5
         *       / \             / \   \
         *      3   5           1  3    6
         *           \
         *            6
         */
        AVLNode<K, V> right = node.right;       //4节点
        AVLNode<K, V> rightLeft = right.left;   //3节点
        right.left = node;                      //设置4节点的左子树为2节点
        node.right = rightLeft;                 //设置2节点的右子树为3节点
        updateHeight(node);                     //更新树的高度
        updateHeight(right);                    //更新树的高度
        return right;
    }

    //右旋,把要旋转的节点 -> 新的根节点
    private AVLNode<K, V> rightRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 8:
         *       8              6
         *      / \            / \
         *     6   9    ->    5   8
         *    / \                / \
         *   5  7               7  9
         */
        AVLNode<K, V> left = node.left;         //6节点
        AVLNode<K, V> leftRight = left.right;   //7节点
        left.right = node;                      //设置6节点的右子树为8节点
        node.left = leftRight;                  //设置8节点的左子树为7节点
        updateHeight(node);                     //更新树的高度
        updateHeight(left);                     //更新树的高度
        return left;
    }

    //先左旋再右旋
    private AVLNode<K, V> leftRightRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 6:
         *          6                           6                       4
         *         / \                         / \                     / \
         *        2   7                       4   7                   2   6
         *       / \           左旋后->       / \        右旋后->    / \  / \
         *      1   4                       2  5                   1  3  5  7
         *         / \                     / \
         *        3   5                   1   3
         */
        node.left = leftRotate(node.left);//节点2进行左旋,赋值给6的左子树
        return rightRotate(node);//节点6右旋
    }

    //先右旋再左旋
    private AVLNode<K, V> rightLeftRotate(AVLNode<K, V> node) {
        /**
         * 以node节点为 6:
         *          2                           2                           4
         *         / \                         / \                         / \
         *        1   6                       1   4                       2   6
         *           / \         右旋后->         / \        左旋后->    / \  / \
         *          4   7                      3    6                  1  3  5  7
         *         / \                             / \
         *        3   5                           5   7
         */
        node.right = rightRotate(node.right);
        return leftRotate(node);
    }

    //获取节点的高度
    private int height(AVLNode<K, V> node) {
        return node == null ? 0 : node.height;
    }

    //根据节点高度(新增,删除,旋转)
    private void updateHeight(AVLNode<K, V> node) {
        node.height = Integer.max(height(node.left), height(node.right)) + 1;
    }

    //平衡因子,左子树的高度 - 右子树的高度
    private int balanceFactor(AVLNode<K, V> node) {
        return height(node.left) - height(node.right);
    }

    //检查节点是否是平衡的二叉树,如果不是就旋转
    private AVLNode<K, V> balance(AVLNode<K, V> node) {
        if (node == null) {
            return null;
        }
        int height = balanceFactor(node);//如果返回0,-1,1表示是平衡的,否则不是
        if (height > 1 && balanceFactor(node.left) >= 0) {//右旋,要考虑=0的情况
            return rightRotate(node);
        } else if (height > 1 && balanceFactor(node.left) < 0) {//先左旋后右旋
            return leftRightRotate(node);
        } else if (height < -1 && balanceFactor(node.right) <= 0) {//左旋,要考虑=0的情况
            return leftRotate(node);
        } else if (height < -1 && balanceFactor(node.right) > 0) {//先右旋后左旋
            return rightLeftRotate(node);
        }
        return node;
    }

    //添加
    public void put(K key, V value) {
        root = doPut(root, key, value);
    }

    private AVLNode<K, V> doPut(AVLNode<K, V> node, K key, V value) {
        //1.找到空位, 创建新节点
        if (node == null) {
            return new AVLNode<>(key, value);
        }
        //2.key 已存在, 更新
        if (key.compareTo(node.key) == 0) {
            node.value = value;
            return node;
        }
        //3.继续查找
        if (key.compareTo(node.key) < 0) {
            node.left = doPut(node.left, key, value);
        } else {
            node.right = doPut(node.right, key, value);
        }
        updateHeight(node);
        return balance(node);
    }

    //删除
    public void remove(K key) {
        root = doRemove(root, key);
    }

    //递归执行,返回删除后的节点
    private AVLNode<K, V> doRemove(AVLNode<K, V> node, K key) {
        //1.node == null
        if (node == null) {
            return null;
        }
        //2.没有找到key
        if (key.compareTo(node.key) > 0) {
            node.right = doRemove(node.right, key);
        } else if (key.compareTo(node.key) < 0) {
            node.left = doRemove(node.left, key);
        } else {
            /**
             * 找到key,删除操作分为四种情况
             * 1.删除节点没有左子树,将右子树托孤给parent
             * 2.删除节点没有右子树,将左子树托孤给parent
             * 3.删除节点没有左右子树,将null托孤给parent
             * 4.删除节点有左右子树,将后继节点托孤给parent
             *      1.找删除节点的后继节点
             *      2.处理后继节点
             *      3.后继节点取代删除的节点
             */
            if (node.left == null && node.right == null) {
                return null;
            } else if (node.left == null) {
                node = node.right;
            } else if (node.right == null) {
                node = node.left;
            } else {
                AVLNode<K, V> p = node.right;
                while (p.left != null) {
                    p = p.left;
                }
                p.right = doRemove(node.right, p.key);
                p.left = node.left;
                node = p;
            }
        }
        //4.更新高度
        updateHeight(node);
        //5.平衡节点
        return balance(node);
    }

    //查询
    public V get(K key) {
        AVLNode<K, V> pointer = root;
        while (pointer != null) {
            K k = pointer.key;
            if (key.compareTo(k) > 0) {
                pointer = pointer.right;
            } else if (key.compareTo(k) < 0) {
                pointer = pointer.left;
            } else {
                return pointer.value;
            }
        }
        return null;
    }

    private static class AVLNode<K, V> {
        private int height = 1;//节点的高度,默认是1
        private K key;
        private V value;
        private AVLNode<K, V> left;
        private AVLNode<K, V> right;

        public AVLNode(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
}

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

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

相关文章

VoLTE 微信令:SBC 功能篇之 超长呼叫释放信令流程

目录 1. SBC 的位置及超长呼叫释放功能简介 2. VoLTE 超长通话,SBC 释放呼叫流程 博主wx:yuanlai45_csdn 博主qq:2777137742 想要 深入学习 5GC IMS 等通信知识(加入 51学通信),或者想要 cpp 方向修改简历,模拟面试,学习指导都可以添加博主低价指导哈。 1. SBC 的位置及…

使用 Python 的 BeautifulSoup(bs4)解析复杂 HTML

使用 Python 的 BeautifulSoup&#xff08;bs4&#xff09;解析复杂 HTML&#xff1a;详解与示例 在 Web 开发和数据分析中&#xff0c;解析 HTML 是一个常见的任务&#xff0c;尤其是当你需要从网页中提取数据时。Python 提供了多个库来处理 HTML&#xff0c;其中最受欢迎的就…

华尚实业集团家居产业园总部中心项目奠基仪式成功举办

金秋风景如画&#xff0c;十月天高云淡。良辰阳光灿烂&#xff0c;吉时热闹非凡。2024年10月23日上午&#xff0c;华尚实业集团家居产业园总部中心项目奠基仪式在增城经济技术开发区宁西园区项目现场隆重举行&#xff0c;标志着华尚实业集团家居产业园总部中心建设正式拉开帷幕…

基于Java语言的充电桩管理系统

介绍 云快充协议云快充1.5协议云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充电桩系统桩直连协议 软件架构 1、提供云快充底层桩直连协议&#xff0c;版本为云快充1.5&#xff0c;对于没有对接过充电桩系统的开发者尤为合适&#xff1b; 2、包含&#xff1a;启…

安卓项目复制修改包名称打包失败处理——android studio

处理方法 将资源包名称直接替换为新的包名称&#xff0c;不管错误直接生成。

skynet的cluster集群

集群的使用 现在的游戏服务器框架中&#xff0c;分布式是一种常见的需求。一个游戏服务器组通常可以分成网关服务器、登录服务器、逻辑服务器、跨服服务器等等。 在skynet中&#xff0c;我们可以通过cluster来组建一个集群&#xff0c;实现分布式的部署。 示例 我们先来看一…

Win11安装基于WSL2的Ubuntu

1. 概述 趁着还没有完全忘记&#xff0c;详细记录一下在Win11下安装基于WSL2的Ubuntu的详细过程。不得不说WSL2现在被微软开发的比较强大了&#xff0c;还是很值得安装和使用的&#xff0c;笔者就通过WSL2安装的Ubuntu成功搭建了ROS环境。 2. 详论 2.1 子系统安装 在Win11搜…

在Debian上安装向日葵

说明&#xff1a; 因为之前服务器上安装了 PVE (Proxmox VE)&#xff0c;之前是用 Proxmox VE 进行服务器资源管理的。出于某些原因&#xff0c;现在不再通过 PVE构建的虚拟机来使用计算资源&#xff0c;而是通过 PVE 自带的 Debian 系统直接使用虚拟机资源&#xff08;因为积…

NVR接入录像回放平台EasyCVR视频融合平台语音对讲配置

国标GB28181视频平台EasyCVR视频融合平台可拓展性强、视频能力灵活&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制、语音对讲、智能分析接入等功能。其中&#xff0c;在语音对讲方面&#xff0c;NVR接入录像回放平台目前…

JavaEE初阶---多线程(三)---内存可见性/单例模式/wait,notify的使用解决线程饿死问题

文章目录 1.volatile关键字1.1保证内存的可见性--引入1.2保证内存的可见性--分析1.3保证内存的可见性--解决1.4内存可见性-JMM内存模型 2.notify和wait介绍2.1作用一&#xff1a;控制调度顺序2.2作用二&#xff1a;避免线程饿死2.3notify和notifyAll区分 3.单例模式--经典设计模…

数据库编程 SQLITE3 Linux环境

永久存储程序数据有两种方式&#xff1a; 用文件存储用数据库存储 对于多条记录的存储而言&#xff0c;采用文件时&#xff0c;插入、删除、查找的效率都会很差&#xff0c;为了提高这些操作的效率&#xff0c;有计算机科学家设计出了数据库存储方式 一、数据库 用来管理数据…

【Android】多渠道打包配置

目录 简介打包配置签名配置渠道配置配置打包出来的App名称正式包与测试包配置 打包方式开发工具打包命令行打包 优缺点 简介 多渠道打包 是指在打包一个 Android 应用时&#xff0c;一次编译生成多个 APK 文件&#xff0c;每个 APK 文件针对一个特定的渠道。不同的渠道可能代表…

Prompt提示词设计:如何让你的AI对话更智能?

Prompt设计&#xff1a;如何让你的AI对话更智能&#xff1f; 在人工智能的世界里&#xff0c;Prompt&#xff08;提示词&#xff09;就像是一把钥匙&#xff0c;能够解锁AI的潜力&#xff0c;让它更好地理解和响应你的需求。今天&#xff0c;我们就来聊聊如何通过精心设计的Pr…

厂房区域人员进出人数统计-实施方案

1.1 现状分析 传统的人流量统计方法往往依赖于人工计数或简单的视频监控系统&#xff0c;这些方法不仅效率低下&#xff0c;而且容易出错&#xff0c;无法满足现代仓库管理的需求。因此&#xff0c;我厂区决定引入先进的智能监控系统&#xff0c;通过集成高清摄像头、GPU服务器…

【Unity】仓库逻辑:拾取物体进仓库和扔掉物品

需求说明 目标&#xff1a;实现玩家移动过程中&#xff0c;拾取物体&#xff0c;物体被放入仓库&#xff1b;点击仓库中物体&#xff0c;重新扔回3D场景中逻辑。 逻辑分析&#xff1a; 需要玩家可以移动&#xff1b;需要检测玩家和物体的碰撞&#xff0c;并摧毁物体&#xf…

css知识点梳理2

1. 选择器拓展 在 CSS 中&#xff0c;可以根据选择器的类型把选择器分为基础选择器和复合选择器&#xff0c;复合选择器是建立在基础选择器之上&#xff0c;对基本选择器进行组合形成的。 ​ 复合选择器是由两个或多个基础选择器&#xff0c;通过不同的方式组合而成的&#xf…

【Flask】一、安装与第一个测试程序

目录 Flask简介 安装Flask 安装pip&#xff08;Python包管理器&#xff09; 使用pip安装Flask 验证安装 创建Flask程序 创建应用 运行 访问测试 Flask简介 Flask是一个用Python编写的轻量级Web应用框架。它被设计为易于使用和扩展&#xff0c;使其成为构建简单网站或复…

[项目][boost搜索引擎#4] cpp-httplib使用 | log.hpp | 前端 | 测试及总结

目录 编写http_server模块 1. 引入cpp-httplib到项目中 2. cpp-httplib的使用介绍 3. 正式编写http_server 九、添加日志到项目中 十、编写前端模块 十一. 详解传 gitee 十二、项目总结 项目的扩展 写在前面 项目 gitee 已经上传啦 &#xff08;还是决定将学校和个人…

网络编程基础-Reactor线程模型-原理剖析

1、Reactor基本概念 Reactor线程模型其实是一种设计模式&#xff0c;其核心思想就是将输入多路复用和事件派发相结合&#xff0c;从而减少系统中活跃线程的数量。 像我们之前讲到的文章网络编程基础-IO模型深入理解_网络io-CSDN博客提到了其中网络IO模型&#xff08;BIO、NIO…

asp.net core 入口 验证token,但有的接口要跳过验证

asp.net core 入口 验证token,但有的接口要跳过验证 在ASP.NET Core中&#xff0c;你可以使用中间件来验证token&#xff0c;并为特定的接口创建一个属性来标记是否跳过验证。以下是一个简化的例子&#xff1a; 创建一个自定义属性来标记是否跳过验证&#xff1a; public clas…