当前位置: 首页 > news >正文

AVL树实现

1.AVL的概念

    1.AVL树属于二叉搜索树的一种,但它不同与普通的二叉搜索树还具有以下的性质:

    每一个根的左右子树的高度差的绝对值不超过1。AVL树是通过高度差去控制平衡的,所以又称作为平衡二叉搜索树。

    2.AVL树实现我们引入了一个平衡因子的概念,每一个结点都有它的平衡因子,所谓平衡因子即结点的右子树的高度减去左子树的高度,AVL树的平衡因子有0/1/-1三种,如果平衡因子不属于这三种之一便不是AVL树。

    3.AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在logN,那么增删查改的效率也可以控制在O(logN)

   下图就为典型的AVL树:

2.AVL树的实现 

   2.1AVL树的结构

 

template<class K, class V>

struct AVLTreeNode

{

// 需要parent指针,后续更新平衡因子可以看到

     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)

     {}

};

template<class K, class V>

class AVLTree

 {

       typedef AVLTreeNode<K, V> Node;

public:

    bool Insert(const pair<K, V>& kv)

 {

    if (_root == nullptr)

{

    _root = new Node(kv);

   return true;

 }

Node* parent = nullptr;

Node* cur = _root;

while (cur)

 {

if (cur->_kv.first < kv.first)

 {

    parent = cur;

    cur = cur->_right;

 }

else if (cur->_kv.first > kv.first)

 {

    parent = cur;

    cur = cur->_left;

 }

else

  {

    return false;

  }

}

  cur = new Node(kv);

if (parent->_kv.first < kv.first)

{

  parent->_right = cur;

}

else

{

  parent->_left = cur;

}

// 链接父亲

cur->_parent = parent;

// 控制平衡

// 更新平衡因子

while (parent)

{

if (cur == parent->_left)

    parent->_bf--;

else

     parent->_bf++;

if (parent->_bf == 0)

{

    break;

}

else if (parent->_bf == 1 || parent->_bf == -1)

{

    cur = parent;

    parent = parent->_parent;

}

else if (parent->_bf == 2 || parent->_bf == -2)

{

   // 旋转

    break;

}

else

{

    assert(false);

 }

}

   return true;

}

private:

   Node* _root = nullptr;

};

上面为一个完整的AVL的结构,接下来就是对AVL树的深层次了解

   2.2  AVL树的插入 

      2.2.1 AVL树插入一个值的过程

1.插入一个值按二叉搜索树的规则进行插入

2.如果插入后平衡因子都符合AVL树的规则插入就结束

3.如果不符合AVL树的规则,就更新平衡因子,对不平衡子树进行旋转

     2.2.2 平衡因子更新

更新的原则:

1. 平衡因子 = 0/1/-1;

2.插入的结点在右子树则parent的平衡因子++,在左子树则parent的平衡因子--

更新停止条件:

1.更新后parent的平衡因子等于0,更新前的平衡因子为1或者-1,说明一边高一边低,而插入的新结点插入在低的一边,插入后parent所在的子树高度不变,不会影响parent的父亲结点的平衡因子,更新结束。

2.更新后parent的平衡因子等于1或者-1,说明在更新前parent的平衡因子等于0,parent的左右子树一样高,但插入新结点后高度增加1了,就会影响parent的父结点,如果此时父结点不符合AVL树的规则就要去继续更新。

3.更新后parent的平衡因子等于2或者-2,说明更新前parent的平衡因子等于1或者-1,而新插入的结点恰好在较高的子树上。破坏了平衡,parent所在的子树不符合平衡要求,则需要旋转处理。

旋转的目标有两个:        1.把parent子树旋转平衡

                                        2.降低parent子树的高度,恢复到插入结点以前的高度,所以旋转后插入                                             结束。

 2.3 旋转

     2.3.1 旋转的原则

1.保持搜索树的原则

2.让旋转的树从不满足变平衡,其次降低旋转树的高度

旋转总共分为四种:左单旋/右单旋/左右双旋/右左双旋

      2.3.2 右单旋

在上图中就是右单旋的流程图, 有a/b/c抽象为三颗高度为h的子树(h>=0),a/b/c均符合AVL树的要求,10可能是整数的根也可能是一个整颗树局部的子树的根,该图也只是右单旋的形态之一,但易于理解。

如上图所示:在a点插入了一个新结点导致10的平衡因子变为了-2,为让其符合平衡规则,需要让10的过高的的左子树右旋,如第三步所示,从而控制两颗树的平衡。

核心步骤:因为5 < b子树的值 < 10,将b子树作为10结点的左子树,然后将5变成这颗新树的根,符合了搜索树的规则,控制了平衡,同时这颗树恢复到了之前的h+2,符合旋转原则。

以下的情况就不细讲了,根据第一张的思维可以理解下面的情况:

  2.3.3 右单旋代码的实现 

void RotateR(Node* parent)

     Node* subL = parent->_left;

     Node* subLR = subL->right;

     Node*Pparent = parent ->_parent;

    

     parent ->left = subLR;

     if(subLR)

     subLR->_parent = parent;

     parent ->_parent = subL;

     if(Pparent ==nullptr)

    {

        _root = subL;

        subL->_parent =nullptr;

    }

    else

     {

          if (parent ==Pparent->_left)
         {
             Pparent->_left = subL;
         }
        else
        {
           Pparent->_right = subL;
        }
          subL->_parent = Pparent;
     }

      subL->_parent = subL->_bf = 0;

}

2.3.4 左单旋 

  左单旋与右单旋类似,只是从原本过高的左子树的右旋变成现在的过高的右子树的左旋。

其他的情况也和右子树的情况类似这里就不一一列举了

 2.3.5 左单旋代码的实现

void RotateRL(Node* parent)

{
      Node*subR = parent->right;

      Node*subRL = parent->left;

      Node*Pparent = parent->_parent;

    

     parent ->_right =  subRL;

     if(subRL)

     subRL->_parent = parent;

     subR->_left =  parent;

     parent->_parent = subR;

     

    if(Pparent == nullptr)

  {

      _root = subR;

     subR->_parent = nullptr;

  }

  else if(Pparent->_left = parent)

 {

       Pparent ->_left = subR;

  }

  else 

  {

      Pparent ->_right = subR;

  } 

  subR->_parent = Pparent;

  parent->_bf = subR->_bf = 0;

}

 2.3.6 左右双旋

    

从上图可以看出右单旋没有解决两颗树的平衡问题,所以在这里就要使用左右双旋才能解决该问题。

 

在上图中有三个场景:

   场景1:h>=1时,新增结点插入在e子树,e子树高度从h-1并为h并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为1,旋转后8和5平衡因子为0,10的平衡因子变为1.

   场景2:h >= 1时,新增结点插⼊在f⼦树,f⼦树⾼度从h-1变为h并不断更新8->5->10平衡因⼦,引发旋转,其中8的平衡因⼦为1,旋转后8和10平衡因⼦为0,5平衡因⼦为-1。

   场景3:h == 0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新5->10平衡因⼦,引发旋 转,其中8的平衡因⼦为0,旋转后8和10和5平衡因⼦均为0。

 2.3.7 左右双旋代码实现

void RotateLR(Node* parent)

{
   Node*subL = parent ->_left;

   Node*subLR = subL->_right;

   int bf = subLR->_bf;

 

  Rotatel(parent->_left);

  RotateR(parent);

   

     if (bf == 0)
  {
     subL->_bf = 0;
     subLR->_bf = 0;
     parent->_bf = 0;
  }
  else if (bf == -1)
  {
   subL->_bf = 0;
   subLR->_bf = 0;
   parent->_bf = 1;
  }
    else if(bf == 1)
  {
   subL->_bf = -1;
   subLR->_bf = 0;
   parent->_bf = 0;
  } 
   else
  {
    assert(false);
  }
}

2.3.8 右左双旋 

  同样分三个场景:

   场景1:h >= 1时,新增结点插⼊在e⼦树,e⼦树⾼度从h-1变为h并不断更新12->15->10平衡因 ⼦,引发旋转,其中12的平衡因⼦为-1,旋转后10和12平衡因⼦为0,15平衡因⼦为1。

  场景2:h >= 1时,新增结点插⼊在f⼦树,f⼦树⾼度从h-1变为h并不断更新12->15->10平衡因⼦,引发旋转,其中12的平衡因⼦为1,旋转后15和12平衡因⼦为0,10平衡因⼦为-1。

 场景3:h == 0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新15->10平衡因⼦,引发旋 转,其中12的平衡因⼦为0,旋转后10和12和15平衡因⼦均为0。

2.3.9 右左双旋代码实现 

void RotateRL(Node* parent)
{
  Node* subR = parent->_right;
  Node* subRL = subR->_left;
  int bf = subRL->_bf;
  RotateR(parent->_right);
  RotateL(parent);
  if (bf == 0)
{
   subR->_bf = 0;
   subRL->_bf = 0;
   parent->_bf = 0;
}
else if (bf == 1)
{
   subR->_bf = 0;
   subRL->_bf = 0;
   parent->_bf = -1;
}
else if (bf == -1)
{
  subR->_bf = 1;
  subRL->_bf = 0;
  parent->_bf = 0;
}
else
{
  assert(false);
}
}

相关文章:

AVL树实现

1.AVL的概念 1.AVL树属于二叉搜索树的一种&#xff0c;但它不同与普通的二叉搜索树还具有以下的性质&#xff1a; 每一个根的左右子树的高度差的绝对值不超过1。AVL树是通过高度差去控制平衡的&#xff0c;所以又称作为平衡二叉搜索树。 2.AVL树实现我们引入了一个平衡因子的概…...

初始MYSQL数据库(6)—— 事务

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; MYSQL 目录 事务的概念 事务的ACID特性 使用事务 查看支持事务的存储引擎 事务的语法 保存点 自动/手动提交事务 事务的隔离性和…...

0基础学习PyTorch——GPU上训练和推理

大纲 创建设备训练推理总结 在《Windows Subsystem for Linux——支持cuda能力》一文中&#xff0c;我们让开发环境支持cuda能力。现在我们要基于《0基础学习PyTorch——时尚分类&#xff08;Fashion MNIST&#xff09;训练和推理》&#xff0c;将代码修改成支持cuda的训练和推…...

这款免费工具让你的电脑焕然一新,专业人士都在用

HiBit Uninstaller 采用单一可执行文件的形式,无需复杂的安装过程,用户可以即刻开始使用。这种便捷性使其成为临时使用或紧急情况下的理想选择。尽管体积小巧,但其功能却异常强大,几乎不会对系统性能造成任何负面影响。 这款工具的一大亮点是其多样化的功能。它不仅能够常规卸…...

Java高级Day52-BasicDAO

138.BasicDao 基本说明&#xff1a; DAO&#xff1a;data access object 数据访问对象 这样的通用类&#xff0c;称为 BasicDao&#xff0c;是专门和数据库交互的&#xff0c;即完成对数据库(表)的crud操作 在BasicDao 基础上&#xff0c;实现一张表对应一个Dao&#xff0c;…...

【OceanBase 诊断调优】—— SQL 诊断宝典

视频 OceanBase 数据库 SQL 诊断和优化&#xff1a;https://www.oceanbase.com/video/5900015OB Cloud 云数据库 SQL 诊断与调优的应用实践&#xff1a;https://www.oceanbase.com/video/9000971SQL 优化&#xff1a;https://www.oceanbase.com/video/9000889阅读和管理SQL执行…...

微服务Redis解析部署使用全流程

目录 1、什么是Redis 2、Redis的作用 3、Redis常用的五种基本类型&#xff08;重要知识点&#xff09; 4、安装redis 4.1、查询镜像文件【省略】 4.2、拉取镜像文件 4.3、启动redis并设置密码 4.3.1、修改redis密码【可以不修改】 4.3.2、删除密码【坚决不推荐】 5、S…...

C++之STL—常用排序算法

sort (iterator beg, iterator end, _Pred) // 按值查找元素&#xff0c;找到返回指定位置迭代器&#xff0c;找不到返回结束迭代器位置 // beg 开始迭代器 // end 结束迭代器 // _Pred 谓词 random_shuffle(iterator beg, iterator end); // 指定范围内的元素随机调…...

【驱动】地平线X3派:备份与恢复SD卡镜像

1、备份镜像 1.1 安装gparted GParted是硬盘分区软件GNU Parted的GTK+图形界面前端,是GNOME桌面环境的默认分区软件。 GParted可以用于创建、删除、移动分区,调整分区大小,检查、复制分区等操作。可以用于调整分区以安装新操作系统、备份特定分区到另一块硬盘等。 在Ubun…...

【C++报错已解决】std::ios_base::failure

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…...

matlab入门学习(四)多项式、符号函数、数据统计

一、多项式 %多项式&#xff08;polynomial&#xff09;%创建 p[1,2,3,4] %系数向量&#xff0c;按x降幂排列&#xff0c;最右边是常数&#xff08;x的0次幂&#xff09; f1poly2str(p,x) %系数向量->好看的字符串 f x^3 2 x^2 3 x 4&#xff08;不能运算的式子&#xf…...

leetcode621. 任务调度器

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表&#xff0c;用字母 A 到 Z 表示&#xff0c;以及一个冷却时间 n。每个周期或时间间隔允许完成一项任务。任务可以按任何顺序完成&#xff0c;但有一个限制&#xff1a;两个 相同种类 的任务之间必须有长度为 n 的冷却时…...

Spark 的 Skew Join 详解

Skew Join 是 Spark 中为了解决数据倾斜问题而设计的一种优化机制。数据倾斜是指在分布式计算中&#xff0c;由于某些 key 具有大量数据&#xff0c;而其他 key 数据较少&#xff0c;导致某些分区的数据量特别大&#xff0c;造成计算负载不均衡。数据倾斜会导致个别节点出现性能…...

讯飞星火编排创建智能体学习(一)最简单的智能体构建

目录 开篇 智能体的概念 编排创建智能体 创建第一个智能体 ​编辑 大模型节点 测试与调试 开篇 前段时间在华为全联接大会上看到讯飞星火企业级智能体平台的演示&#xff0c;对于拖放的可视化设计非常喜欢&#xff0c;刚开始以为是企业用户才有的&#xff0c;回来之后查…...

mac-m1安装nvm,docker,miniconda

1.安装minicondaMAC OS(M1)安装配置miniconda_mac-mini m1 conda-CSDN博客 2.安装nvm&#xff08;用第二个方法&#xff09;Mac电脑安装nvm(node包版本管理工具)-CSDN博客 3.安装docker dmg下载链接docker-toolbox-mac-docker-for-mac安装包下载_开源镜像站-阿里云 教程MacOS系…...

STM32F407之Flash

寄存器分类 一般寄存器分为只读存储器 (ROM) 随机存储器(RAM) 只读存储器 只读存储器也被称为ROM 在正常工作时只能读不能写。 只读存储器经历的阶段 ROM->PROM->EPROM->EEPROM ->Flash 优点&#xff1a;掉电不丢失&#xff0c;解构简单 缺点&#xff1a;只适…...

优化 Go 语言数据打包:性能基准测试与分析

场景&#xff1a;在局域网内&#xff0c;需要将多个机器网卡上抓到的数据包同步到一个机器上。 原有方案&#xff1a;tcpdump -w 写入文件&#xff0c;然后定时调用 rsync 进行同步。 改造方案&#xff1a;使用 Go 重写这个抓包逻辑及同步逻辑&#xff0c;直接将抓到的包通过网…...

【SQL】未订购的客户

目录 语法 需求 示例 分析 代码 语法 SELECT columns FROM table1 LEFT JOIN table2 ON table1.common_field table2.common_field; LEFT JOIN&#xff08;或称为左外连接&#xff09;是SQL中的一种连接类型&#xff0c;它用于从两个或多个表中基于连接条件返回左表…...

Qt(9.28)

widget.cpp #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {QPushButton *btn1 new QPushButton("登录",this);this->setFixedSize(640,480);btn1->resize(80,40);btn1->move(200,300);btn1->setIcon(QIcon("C:…...

javascript-冒泡排序

前言&#xff1a;好久没学习算法了&#xff0c;今天看了一个视频课&#xff0c;之前掌握很好的冒泡排序居然没写出来&#xff1f; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport"…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...