数据结构--AVL树

一、二叉搜索树(Binary Search Tree, BST)

基本性质

  1. 对于树中的每个节点,其左子树中的所有节点值均小于该节点值。
  2. 其右子树中的所有节点值均大于该节点值。
  3. 左右子树也分别是二叉搜索树。

极端场景

在极端情况下,如插入节点顺序为升序或降序时,二叉搜索树会退化为链表结构。例如,依次插入 1, 2, 3, 4, 5,此时树的形状为一条从根节点开始不断向右延伸的链。这种退化导致树的高度变为 O (n)(n 为节点数),原本二叉搜索树查找、插入、删除操作平均时间复杂度为 O (log n),此时会退化为 O (n),效率大幅降低。

二、AVL 树(Adelson-Velsky and Landis Tree)

定义与特点

AVL 树是一种自平衡的二叉搜索树。它在向二叉搜索树中插入新节点时,通过特定的旋转操作,保证每个节点的左右子树高度之差的绝对值不超过 1。这使得 AVL 树在动态插入和删除节点过程中,始终保持较为平衡的状态,从而保证基本操作的时间复杂度稳定在 O (log n)。

平衡因子(Balance Factor)

  1. 定义:平衡因子 = 右子树高度 - 左子树高度。
  2. AVL 树的平衡条件:对于 AVL 树中的每一个节点,其平衡因子的绝对值不超过 1。即,| 右子树高度 - 左子树高度 | ≤ 1。当插入或删除节点导致某个节点的平衡因子绝对值大于 1 时,AVL 树会进行相应的旋转操作(左单旋、右单旋、双旋(先左旋后右旋和先右旋后左旋))来重新平衡树结构,确保树始终满足 AVL 树的性质。

 三、AVL 树 的实现

设计结构

1.节点结构

template<class K, class V>
class AVLTreeNode {
public:pair<K, V> _kv;              // 键值对AVLTreeNode<K, V>* _left;     // 左子节点AVLTreeNode<K, V>* _right;    // 右子节点AVLTreeNode<K, V>* _parent;   // 父节点(关键,用于回溯调整平衡)int _bf;                      // 平衡因子(右子树高度 - 左子树高度)AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};

2.树结构

template<class K, class V>
class AVLTree {typedef AVLTreeNode<K, V> Node;
public://增删改查操作//......
private:Node* _root = nullptr;  // 根节点
};

操作函数

插入

1.查找插入位置

  • 若树为空,直接创建根节点。
  • 否则,从根节点开始遍历查找:

    bool insert(const pair<K, V>& kv) {if (_root == nullptr) {      // 空树直接插入_root = new Node(kv);return true;}Node* cur = _root, *parent = nullptr;while (cur) {                // 向下搜索插入位置parent = cur;if (kv.first < cur->_kv.first) cur = cur->_left;else if (kv.first > cur->_kv.first) cur = cur->_right;else return false;       // 重复键不允许插入}

2.插入新节点

  • 创建新节点 cur,根据键值大小链接到父节点的左或右。

        cur = new Node(kv);cur->_parent = parent;       // 绑定父节点if (kv.first < parent->_kv.first) parent->_left = cur;     // 插入到左子树else parent->_right = cur;    // 插入到右子树
  • 设置 cur->_parent = parent(关键,后续调整依赖父指针)。

3.更新平衡因子

  • 从插入点的父节点向上回溯,调整每个祖先节点的平衡因子:

    • 插入在左子树:parent->_bf--

    • 插入在右子树:parent->_bf++

  • 根据平衡因子决定是否继续调整或旋转:

    • _bf == 0:停止调整(子树高度未变)。

    • |_bf| == 1:继续向上调整。

    • |_bf| == 2:触发旋转(需判断旋转类型)。

    while (parent) {             // 从父节点向上回溯调整if (cur == parent->_left) parent->_bf--;else parent->_bf++;if (parent->_bf == 0) break;     // 高度不变,无需调整else if (abs(parent->_bf) == 1) { // 高度变化,继续向上cur = parent;parent = parent->_parent;}else if (abs(parent->_bf) == 2) { // 需要旋转if (parent->_bf == 2 && cur->_bf == 1) rotateL(parent);        // 左单旋else if (parent->_bf == -2 && cur->_bf == -1) rotateR(parent);        // 右单旋else if (parent->_bf == 2 && cur->_bf == -1) rotateRL(parent);       //右-左旋else if (parent->_bf == -2 && cur->_bf == 1) rotateLR(parent);       //左-右旋break;}}return true;
}

左单旋(rotateL

  • 确定关键节点

    • parent:失衡节点(_bf == 2)。

    • curparent的右子节点(_bf == 1)。

    • curleftcur的左子节点。

    • grandParentparent的父节点。

  • 调整指针关系

1.处理curleft

  • parent->_right = curleft:将parent的右子指向curleft
  • curleft存在:设置curleft->_parent = parent

2.建立curparent关系

  • cur->_left = parentcur的左子指向原父节点。
  • parent->_parent = cur:原父节点的父指针指向cur

3.链接到原树

  • cur->_parent = grandParentcur的父指针指向原祖父。
  • grandParent为空:更新根节点为cur
  • 否则:根据parent在原树的位置,将grandParent的左/右子指向cur

  • 更新平衡因子

    • parent->_bf = 0

    • cur->_bf = 0

完整代码

void rotateL(Node* parent) {Node* grandParent = parent->_parent;Node* cur = parent->_right, * curleft = cur->_left;parent->_right = curleft;if (curleft) {curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;cur->_parent = grandParent;if (grandParent == nullptr) {_root = cur;}else {if (parent == grandParent->_left) {grandParent->_left = cur;}else {grandParent->_right = cur;}}parent->_bf = cur->_bf = 0;
}

右单旋 (rotateR) 

  • 参考图:
    • 确定关键节点,调节指针关系:
    • 链接到原树 :
  • 复现代码:
    void rotateR(Node* parent) {Node* grandParent = parent->_parent;Node* cur = parent->_left, * curright = cur->_right;parent->_left = curright;if (curright) {curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (grandParent == nullptr) {_root = cur;cur->_parent = nullptr;}else {if (grandParent->_left == parent) {grandParent->_left = cur;}else {grandParent->_right = cur;}cur->_parent = grandParent;}parent->_bf = cur->_bf = 0;
    }

左-右双旋 (rotateLR)

适用场景
父节点平衡因子为 -2,左子节点平衡因子为 1(左子树的右子树过高)。

步骤

1.定位关键节点

  • parent:失衡节点(平衡因子 -2)。

  • curparent 的左子节点(平衡因子 1)。

  • currightcur 的右子节点(需记录其原始平衡因子 tmpbf)。

2.执行双旋

  • 先左旋:对 cur 调用左单旋(rotateL),使 curright 成为新的左子树根。

  • 再右旋:对 parent 调用右单旋(rotateR),使 curright 成为新的根节点。

3.调整平衡因子

 

  • tmpbf == 1

    • parent->_bf = 0cur->_bf = -1(左子树高度减少)。

    • curright->_bf = 0

  • tmpbf == -1

    • parent->_bf = 1(右子树高度增加)。

    • cur->_bf = 0curright->_bf = 0

  • tmpbf == 0:所有相关节点平衡因子置 0

完整代码

void rotateLR(Node* parent) {Node* cur = parent->_left, * curright = cur->_right;int tmpbf = curright->_bf;rotateL(parent->_left);rotateR(parent);if (tmpbf == -1) {cur->_bf = 0;curright->_bf = 0;parent->_bf = 1;}else if (tmpbf == 1) {parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else if (tmpbf == 0) {parent->_bf = cur->_bf = curright->_bf = 0;}else {assert(false);}
}

 

右-左双旋(rotateRL) 

适用场景
父节点平衡因子为 2,右子节点平衡因子为 -1(右子树的左子树过高)。

步骤

1.定位关键节点

  • parent:失衡节点(平衡因子 2)。

  • curparent 的右子节点(平衡因子 -1)。

  • curleftcur 的左子节点(需记录其原始平衡因子 tmpbf)。

2.执行双旋

  • 先右旋:对 cur 调用右单旋(rotateR),使 curleft 成为新的右子树根。

  • 再左旋:对 parent 调用左单旋(rotateL),使 curleft 成为新的根节点。

3.调整平衡因子

  • tmpbf == -1

    • parent->_bf = 0cur->_bf = 1(右子树高度增加)。

    • curleft->_bf = 0

  • tmpbf == 1

    • parent->_bf = -1(左子树高度减少)。

    • cur->_bf = 0curleft->_bf = 0

  • tmpbf == 0:所有相关节点平衡因子置 0

补充:在 AVL 树的双旋转操作(如rotateRLrotateLR)完成后需要调整平衡因子的原因?

在左单旋(rotateL)和右单旋(rotateR)操作中,旋转完成后直接将相关节点的平衡因子置为 0。这是因为单旋转操作在特定的失衡情况下进行,能够一步将树调整为平衡状态,旋转后相关节点的左右子树高度差重新达到平衡,所以可以简单地将平衡因子置为 0。

双旋转(rotateRL 和 rotateLR)是由两次单旋转组合而成,情况更为复杂。双旋转操作会改变多个节点的左右子树结构,旋转后不同节点的左右子树高度变化不能简单地通过将平衡因子置为 0 来处理,需要根据旋转前子节点的平衡因子情况来具体调整。(具体如何调整合一参考 rotateRL 和 rotateLR  的图解和实现)

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言——链表

大神文献&#xff1a;https://blog.csdn.net/weixin_73588765/article/details/128356985 目录 一、链表概念 1. 什么是链表&#xff1f; 1.1 链表的构成 2. 链表和数组的区别 数组的特点&#xff1a; 链表的特点&#xff1a; 二者对比&#xff1a; 二…

Qt:多线程

目录 初识Qt多线程 QThread常用API QThread的使用 Qt中的锁 条件变量和信号量 初识Qt多线程 Qt 多线程 和 Linux 中的线程本质是一个东西 Linux 中学过的 多线程 APl&#xff0c;Linux 系统提供的 pthread 库 Qt 中针对系统提供的线程 API 重新封装了 C11 中&#xff0c;…

如何用Kimi生成PPT?秒出PPT更高效!

做PPT是不是总是让你头疼&#xff1f;&#x1f629; 快速制作出专业的PPT&#xff0c;今天我们要推荐两款超级好用的AI工具——Kimi 和 秒出PPT&#xff01;我们来看看哪一款更适合你吧&#xff01;&#x1f680; &#x1f947; Kimi&#xff1a;让PPT制作更轻松 Kimi的生成效…

计算机毕业设计SpringBoot+Vue.js车辆管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

机器学习4-PCA降维

1 降维 在数据处理过程中&#xff0c;会碰到维度爆炸&#xff0c;维度灾难的情况&#xff0c;为了得到更精简更有价值的信息&#xff0c;我们需要进一步处理&#xff0c;用的方法就是降维。 降维有两种方式&#xff1a;特征抽取、特征选择 特征抽取&#xff1a;就是特征映射…

探秘沃尔什-哈达玛变换(WHT)原理

沃尔什-哈达玛变换&#xff08;WHT&#xff09;起源 起源与命名&#xff08;20世纪早期&#xff09; 数学基础&#xff1a;该变换的理论基础由法国数学家雅克哈达玛&#xff08;Jacques Hadamard&#xff09;在1893年提出&#xff0c;其核心是哈达玛矩阵的构造。扩展与命名&…

使用 vxe-table 导出 excel,支持带数值、货币、图片等带格式导出

使用 vxe-table 导出 excel&#xff0c;支持带数值、货币、图片等带格式导出&#xff0c;通过官方自动的导出插件 plugin-export-xlsx 实现导出功能 查看官网&#xff1a;https://vxetable.cn gitbub&#xff1a;https://github.com/x-extends/vxe-table gitee&#xff1a;htt…

uniapp实现的个人中心页面(仿小红书)

采用 uniapp 实现的一款仿小红书个人中心页面模板&#xff0c;支持vue2、vue3, 同时适配H5、小程序等多端多应用。 简约美观大方 可到插件市场下载尝试&#xff1a; https://ext.dcloud.net.cn/plugin?id22516 示例

步进电机软件细分算法解析与实践指南

1. 步进电机细分技术概述 步进电机是一种将电脉冲信号转换为角位移的执行机构&#xff0c;其基本运动单位为步距角。传统步进电机的步距角通常为 1.8&#xff08;对应 200 步 / 转&#xff09;&#xff0c;但在高精度定位场景下&#xff0c;这种分辨率已无法满足需求。细分技术…

tauri-plugin-shell插件将_blank的a标签用浏览器打开了,,,解决办法

不要使用这个插件&#xff0c;这个插件默认会将网页中a标签为_blank的使用默认浏览器打开&#xff0c;但是这种做法在我的程序里不是很友好&#xff0c;我需要自定义这种行为&#xff0c;当我点击我自己的链接的时候&#xff0c;使用默认浏览器打开&#xff0c;当点击别的链接的…

ESP8266 NodeMCU 与 Atmega16 微控制器连接以发送电子邮件

NodeMCU ESP8266 AVR 微控制器 ATmega16 的接口 Atmega16 是一款低成本的 8 位微控制器,比以前版本的微控制器具有更多的 GPIO。它具有所有常用的通信协议,如 UART、USART、SPI 和 I2C。由于其广泛的社区支持和简单性,它在机器人、汽车和自动化行业有广泛的应用。 Atmega1…

C++查看动态库导出哪些函数以及动态库导出形式

1、查看动态库导出哪些函数 1.1、在 Windows 和 Linux 上&#xff0c;可以使用不同的方法来查看动态库&#xff08;.dll 或 .so&#xff09;导出的函数 Windows&#xff1a;使用 dumpbin&#xff1a;Windows 提供了 dumpbin 工具&#xff08;Visual Studio 自带&#xff09;&…

【使用hexo模板创建个人博客网站】

使用hexo模板创建个人博客网站 环境准备node安装hexo安装ssh配置 使用hexo命令搭建个人博客网站hexo命令 部署到github创建仓库修改_config.yml文件 编写博客主题扩展 环境准备 node安装 进入node官网安装node.js 使用node -v检查是否安装成功 安装成功后应该出现如上界面 …

【Linux】http 协议

目录 一、http协议 &#xff08;一&#xff09;http 协议的概念 &#xff08;二&#xff09;URL的组成 &#xff08;三&#xff09;urlencode 和 urldecode 二、http 的协议格式 &#xff08;一&#xff09;http 请求方法 &#xff08;二&#xff09;http 响应状态码 &a…

什么是时序数据库?有哪些时序数据库?常见的运用场景有哪些?

时序数据库 什么是时序数据库&#xff1f; 时序数据库&#xff08;Time Series Database, TSDB&#xff09;是专门针对时间序列数据&#xff08;按时间顺序记录的数据点&#xff09;进行存储和管理的数据库。这类数据通常包含时间戳&#xff08;Timestamp&#xff09;和对应的…

【Linux】冯诺依曼体系与操作系统理解

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 目录 前言 一、冯诺依曼体系结构 二、操作系统 1. 操作系统的概念 2. 操作系统存在的意义 3. 操作系统的管理方式 4. 补充&#xff1a;理解系统调用…

Unity HDR颜色、基础颜色、强度强度、HDR面板Intensity之间的相互转换

目录 前言&#xff1a; 一、UnityHDR面板的规律 二、HDR与基础颜色转换&#xff0c;HDR强度获取&#xff0c;输入设置强度获取 1.基础色->HDR颜色 2.HDR颜色->基础色 3.获取HDR颜色在面板中的强度 4.获取HDR颜色在面板设置输入时的强度 前言&#xff1a; HDR&#…

c++进阶--map和set的使用

大家好&#xff0c;昨天我们学习了二叉搜索树&#xff0c;今天我们来学习一下map和set容器的使用。 目录 1. map和set的使⽤ 1.1 序列式容器和关联式容器 2. set系列的使⽤ 2.1 参考文档 2.2 set类的介绍 2.3 set的构造和迭代器 2.4 set的增删查 2.5 insert和迭代器…

Kylin麒麟操作系统服务部署 | NFS服务部署

以下所使用的环境为&#xff1a; 虚拟化软件&#xff1a;VMware Workstation 17 Pro 麒麟系统版本&#xff1a;Kylin-Server-V10-SP3-2403-Release-20240426-x86_64 一、 NFS服务概述 NFS&#xff08;Network File System&#xff09;&#xff0c;即网络文件系统。是一种使用于…

FPGA之USB通信实战:基于FX2芯片的Slave FIFO回环测试详解

FPGA之Usb数据传输 Usb 通信 你也许会有疑问&#xff0c;明明有这么多通信方式和数据传输&#xff08;SPI、I2C、UART、以太网&#xff09;为什么偏偏使用USB呢? 原因有很多&#xff0c;如下&#xff1a; 1. 高速数据传输能力 高带宽&#xff1a;USB接口提供了较高的数据传…