AVL平衡二叉树

01. 初始AVL树

AVL树是最早发明的自平衡二叉搜索树。在AVL树中,任何节点的两个子树的高度差(平衡因子)最多为1,这使得AVL树能够保持较好的平衡性,从而保证查找、插入和删除操作的时间复杂度都是O(log n)。

在这里插入图片描述

包含n个节点的AVL树的高度始终保持在O(log n)


02. AVL树的结构

AVL 树在原 二叉搜索树 的基础上添加了 平衡因子 bf 以及用于快速向上调整的 父亲指针 parent,所以 AVL 树是一个三叉链结构。

在这里插入图片描述

2.1 AVL树节点定义

template <class K, class V>
struct AVLTreeNode // 定义借点类型
{AVLTreeNode(const std::pair<K, V> kv): _parent(nullptr), _left(nullptr), _right(nullptr), _kv(kv), _bf(0){}AVLTreeNode<K, V> *_parent;AVLTreeNode<K, V> *_left;AVLTreeNode<K, V> *_right;//这里并没有直接存储数据K,和V,而是像map那样将其封装成一个pair<K,V>类型。std::pair<K, V> _kv; // 存储键值对int _bf;             // 平衡因子
};

那么在AVLTree类方法实现只需创建一个AVLTreeNode<K,V>*类型的_root根节点。

规定: 当前实现的平衡因子,规定差值为 右 - 左,因此如果右子树增高,_bf++,左子树增高 _bf–,具体操作将在后面体现。


2.2 AVL树的方法

template<class K,class V>
class AVLTree{typedef TreeNode<K, V> Node;
public://插入bool insert(const pair<K, V> kv) {}//查找bool find(const K& kev) {}//中序遍历void order() {}void RotateR(Node* parent) {}	//右单旋void RotateL(Node* parent) {}	//左单旋void RRotateRL(Node* parent) {}	//右左双选void RotateLR(Node* parent) {}	//左右双选void order(Node* root) {}	//中序遍历private:Node* _root;
};

03. AVL树的插入

3.1插入过程

对于avl树的插入,实际是在二叉搜索树基础之上,增加了更新平衡因子和在这个过程中进行旋转调整的过程。

  1. 空树检查
    • 若树为空 (_root == nullptr),直接创建新节点作为根节点,返回 true
  2. 搜索插入位置
    • 从根节点开始比较 key
      • key < 当前节点:向左子树搜索
      • key > 当前节点:向右子树搜索
      • key == 当前节点:键已存在,返回 false
  3. 插入新节点
    • 创建新节点并链接到父节点:
      • key < 父节点:作为左孩子插入
      • key > 父节点:作为右孩子插入
  4. 平衡因子更新与调整(下面完成)
  • 更新平衡因子,然后判断是否需要进行旋转调整高度

bool Insert(const std::pair<K, V> kv){if (_root == nullptr){                         // 树中没有任何节点,直接new_root = new Node(kv); // 未使用Node(key, value),而是采用简直对的形式return true;}// 不为空树时的操作,找到插入点Node *parent = nullptr;Node *cur = _root;while (cur){if (kv.first < cur->_kv.first){ // 左走parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}elsereturn false;} // 退出的时候找到,即找到叶节点仍需要判断往那边走,插入数据(cur==null)// 创建新节点插入cur = new Node(kv);if (kv.first < parent->_kv.first0){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//控制平衡//1、更新平衡因子----更新新增节点到根节点的祖先路径//2、出现异常平衡因子,那么需要旋转平衡处理while (parent) {if (cur == parent->_right)parent->_bf++;elseparent->_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) {RotateR(parent);//右单旋}else if (parent->_bf == 2 && cur->_bf == 1) {RotateL(parent);//左单旋}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);//左右双旋}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);//右左双旋}else{assert(false);}break;}else {//说明插入更新平衡因子之前,树中平衡因子就有问题了assert(false);}} return true;}

3.1 左单旋

左单旋适用场景:

  P (_bf=+2)\							    C (_bf=0)C (_bf=+1)     调整为    		 /	   \\						     P       RR							  (_bf=0)

在这里插入图片描述

当插入10的时候出现不平衡。

左单旋:

  1. 确定parentsubRsubRL
  2. 使subRL成为parent右孩子
  3. subR左孩子变为parent
  4. 注意pparent的更新,和paarent为根节点情况
    void RotateL(Node *parent){ // parent在_bf=2处Node *subR = parent->_right;Node *subRL = subR->_left;// 先将subR的左孩子交给parentparent->_right = subRL;if (subRL != nullptr){subRL->_parent = parent;}Node *pparent = parent->_parent;//保存parent的父节点,确保更新平衡因子subR->_left = parent;parent->_parent = subR;if (_root == parent){//是否是根节点subR->_parent = nullptr;_root = subR;}else{if (pparent->_right == parent){pparent->_right = parent;}else{pparent->_left = subR;}subR->_parent = pparent;}parent->_bf = subR->_bf = 0;}
注意事项
  1. 空指针检查
    • subRL 可能为 nullptr,需判断后再操作其父指针
  2. 根节点更新
    • parent 是根节点,旋转后需更新 _root 指向 subR
  3. 平衡因子重置
    • 左单旋后,parentsubR 的平衡因子必为 0

3.2 右单旋

          P (_bf=-2)/						   C (_bf=0)C (_bf=-1)				 /	   \/						L	   PL (_bf=0)					  (_bf=-0)

在这里插入图片描述

当插入10的时候出现不平衡。

右单旋:

  1. 确定parentsubLsubLR
  2. 使subLR成为parent左孩子
  3. subR右孩子变为parent
  4. 注意pparent的更新,和paarent为根节点情况
//右单旋
void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//先将 subL 的右孩子移交给父亲parent->_left = subLR;if (subLR != nullptr)subLR->_parent = parent;Node* pparent = parent->_parent;subL->_right = parent;parent->_parent = subL;//再将父亲移交给 subL,subL 成为新父亲if (parent == _root){//如果原父亲为根,那么此时需要更新 根subL->_parent = nullptr;_root = subL;}else{//单纯改变链接关系if (pparent->_right == parent)pparent->_right = subL;elsepparent->_left = subL;subL->_parent = pparent;}//更新平衡因子parent->_bf = subL->_bf = 0;
}
注意事项
  1. 空指针检查
    • subLR 可能为 nullptr,需判断后再操作其父指针。
  2. 根节点更新
    • parent 是根节点,旋转后需更新 _root 指向 subL
  3. 平衡因子重置
    • 右单旋后,parentsubL 的平衡因子必为 0

3.3 右左单旋

在这里插入图片描述

//右左双旋
void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int BF = subRL->_bf;//先右单旋RotateR(subR);//再左单旋RotateL(parent);//根据不同的情况更新平衡因子if(BF == 0){parent->_bf = subR->_bf = 0;}else if (BF == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (BF == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{//非法情况std::cerr << "此处的平衡因子出现异常!" << std::endl;assert(false);	//直接断言报错}
}

右左单旋:

  1. 右单旋:
    1. 确定 parentsubRsubRL
    2. subRL的右子树变为 subR左子树,parent右子树为subRL左子树
    3. subRL向上提
平衡因子调整(分三类)
情况subRL->_bf调整后平衡因子触发条件
情况10parent = subR = subRL = 0新节点是 subRL 自身
情况2-1parent = 0, subR = +1, subRL = 0新节点在 subRL 左子树
情况3+1parent = -1, subR = 0, subRL = 0新节点在 subRL 右子树

3.4 左右双旋

右左双旋左右双旋逻辑非常像,这里就不演示了,直接看代码实现:

//左右双旋
void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int BF = subLR->_bf;//先左单旋RotateL(subL);//再右单旋RotateR(parent);//根据不同的情况更新平衡因子if (BF == 0){parent->_bf = subL->_bf = 0;}else if (BF == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (BF == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{//非法情况std::cerr << "此处的平衡因子出现异常!" << std::endl;assert(false);	//直接断言报错}
}

3.5 AVL查找

	//查找bool find(const K& kv) {Node* ptail = _root;while (ptail){if (kv.first > ptail->_kv->first){ptail = ptail->_right;}else if (kv.first < ptail->_kv->first){ptail = ptail->_left;}else{return true;}}return false;}

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

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

相关文章

教育行业可以采用Html5全链路对视频进行加密?有什么优势?

文章目录前言一、什么是Html5加密&#xff1f;二、使用Html5对视频加密的好处三、如何采用Html5全链路对视频进行加密&#xff1f;四、教育行业采用Html5全链路视频加密有什么优势&#xff1f;总结前言 面对优质课程盗录传播的行业痛点&#xff0c;教育机构如何守护核心知识产…

Vue3 tailwindcss

1、安装tailwindcsspnpm i -D tailwindcss postcss autoprefixer # yarn add -D tailwindcss postcss autoprefixer # npm i -D tailwindcss postcss autoprefixer2、 创建TailwindCSS配置文件npx tailwindcss init -ptailwind.config.js/** type {import(tailwindcss).Config}…

提示工程:解锁大模型潜力的核心密码

以下是对Lilian Weng的提示工程权威指南&#xff08;原文链接&#xff09;的深度解析与博客化重构&#xff0c;融入最新行业实践&#xff1a; 提示工程&#xff1a;解锁大模型潜力的核心密码 ——从基础技巧到工业级解决方案全解析 一、重新定义人机交互范式 传统编程 vs 提示…

Python3邮件发送全指南:文本、HTML与附件

在 Python3 中&#xff0c;使用内置的 smtplib 库和 email 模块发送邮件是一个常见的需求。以下是更详细的实现指南&#xff0c;包含各种场景的解决方案和技术细节&#xff1a;一、发送纯文本邮件的完整实现准备工作&#xff1a;确保已开通 SMTP 服务&#xff08;各邮箱开启方式…

CSS和CSS3区别对比

CSS&#xff08;层叠样式表&#xff09;与CSS3&#xff08;CSS的第三个版本&#xff09;的区别主要体现在功能扩展、语法特性以及应用场景等方面。以下是两者的核心对比&#xff1a; 一、核心概念与版本关系CSS&#xff1a;是基础样式表语言&#xff0c;用于分离网页内容与样式…

JVM--监控和故障处理工具

一、命令行工具 1. jps (Java Process Status) 作用&#xff1a;列出当前系统中所有的 Java 进程 常用命令&#xff1a; jps -l # 显示进程ID和主类全名 jps -v # 显示JVM启动参数 输出示例&#xff1a; 1234 com.example.MainApp 5678 org.apache.catalina.startup.Bootstra…

推荐 7 个本周 yyds 的 GitHub 项目。

01.开源的 CRM 软件这是一个开源的客户关系管理&#xff08;CRM&#xff09;系统&#xff0c;现在又 32.5K 的 Star。为企业和团队提供比肩 Salesforce 等商业产品的功能&#xff0c;同时强调用户自主权、数据自由与高度可定制性。开源地址&#xff1a;https://github.com/twen…

linux网络编程之单reactor模型(一)

Reactor 是一种事件驱动的设计模式&#xff08;Event-Driven Pattern&#xff09;&#xff0c;主要用于处理高并发 I/O&#xff0c;特别适合网络服务器场景。它通过一个多路复用机制监听多个事件源&#xff08;如 socket 文件描述符&#xff09;&#xff0c;并在事件就绪时将事…

浏览器重绘与重排

深入解析浏览器渲染&#xff1a;重排(Reflow)与重绘(Repaint)的性能陷阱与优化策略作为一名前端开发者&#xff0c;你是否遇到过界面突然卡顿、滚动时页面抖动或输入框响应迟钝&#xff1f;这些常见性能问题背后&#xff0c;往往是重排与重绘在作祟。本文将深入剖析浏览器渲染机…

day049-初识Ansible与常用模块

文章目录0. 老男孩思想-人脉的本质1. Ansible1.1 密钥认证1.2 安装ansible1.3 添加ansible配置文件1.4 配置主机清单文件&#xff08;Inventory&#xff09;1.5 测试1.6 ansible的模块思想1.7 command模块1.8 需求&#xff1a;每台服务器的密码都不同&#xff0c;怎么批量执行业…

力扣网编程134题:加油站(双指针)

一. 简介 前面两篇文章使用暴力解法&#xff0c;或者贪心算法解决了力扣网的加油站问题&#xff0c;文章如下&#xff1a; 力扣网编程150题&#xff1a;加油站&#xff08;暴力解法&#xff09;-CSDN博客 力扣网编程150题&#xff1a;加油站&#xff08;贪心解法&#xff09…

XPath 语法【Web 自动化-定位方法】

&#x1f9ed; XPath 语法简介&#xff08;Web 自动化核心定位手段&#xff09;一、XPath 是什么&#xff1f;XPath&#xff08;XML Path Language&#xff09;是用于在 XML/HTML 文档中定位节点的语言&#xff0c;由 W3C 标准定义。浏览器支持的是 XPath 1.0。应用场景广泛&am…

记一次 Linux 安装 docker-compose

一.下载 1.手动下载 下载地址&#xff1a;https://github.com/docker/compose/releases 下载后&#xff0c;放在/usr/local/bin/目录下&#xff0c;命名为&#xff1a;docker-compose 2.命令下载 sudo curl -L "https://github.com/docker/compose/releases/download/…

Go语言WebSocket编程:从零打造实时通信利器

1. WebSocket的魅力&#xff1a;为什么它这么火&#xff1f;WebSocket&#xff0c;简单来说&#xff0c;就是一种在单条TCP连接上实现全双工通信的神器。相比HTTP的请求-响应模式&#xff0c;它像是一条随时畅通的电话线&#xff0c;客户端和服务器可以随时“喊话”&#xff0c…

速学 RocketMQ

目录 本地启动&测试&可视化 核心概念 集群 主从 集群 Dledger 集群 总结 客户端消息确认机制 广播模式 消息过滤机制 顺序消息机制 延迟消息与批量消息 事务消息机制 ACL权限控制体系 RocketMQ客户端注意事项 消息的 ID、Key、Tag 最佳实践 消费者端…

【个人思考】不点菜的美学:Omakase 的信任、四季与食艺

本文原创作者:姚瑞南 AI-agent 大模型运营专家/音乐人/野生穿搭model,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 🍣 什么是 Omakase?…

vivo Pulsar 万亿级消息处理实践(3)-KoP指标异常修复

作者&#xff1a;vivo 互联网大数据团队- Chen Jianbo 本文是《vivo Pulsar万亿级消息处理实践》系列文章第3篇。 Pulsar是Apache基金会的开源分布式流处理平台和消息中间件&#xff0c;它实现了Kafka的协议&#xff0c;可以让使用Kafka API的应用直接迁移至Pulsar&#xff0c;…

Marin说PCB之Allegro高亮BOM器件技巧详解

一&#xff0c;首先在原理图输出BOM的时候&#xff0c;只需要勾选器件的位号这个选项即可&#xff0c;具体操作如下所示&#xff1a;二&#xff0c;输出BOM完成后&#xff0c;打开表格选择我们器件的位号那列即可&#xff0c;然后复制到我们的TEXT文本中。三&#xff0c;接着就…

数据结构与算法——从递归入手一维动态规划【2】

前言&#xff1a; 记录一下对左程云系列算法课程--算法讲解066【必备】的剩余习题的学习。本文主要简单记录个人学习心得和提供C版本代码。如需要题目的细致讲解&#xff0c;请前往原视频。 涉及内容&#xff1a; 动态规划、三指针、 参考视频&#xff1a; 左程云--算法讲…

【理念●体系】Windows AI 开发环境搭建实录:六层架构的逐步实现与路径治理指南

【理念●体系】从零打造 Windows WSL Docker Anaconda PyCharm 的 AI 全链路开发体系-CSDN博客 Windows AI 开发环境搭建实录&#xff1a;六层架构的逐步实现与路径治理指南 ——理念落地篇&#xff0c;从路径规划到系统治理&#xff0c;打造结构化可复现的 AI 开发环境 AI…