「C++」AVL树的实现(动图)

news2024/11/17 23:28:07
在这里插入图片描述

💻文章目录

  • AVL树
    • 概念
    • AVL的查找
    • AVL树的插入
  • 代码部分
    • AVL树的定义
    • 查找
    • 插入
    • 旋转
  • 📓总结


AVL树

概念

AVL树又名高度平衡的二叉搜索树,由G. M. Adelson-Velsky和E. M. Landis发明,顾名思义,其任意节点的左右子树最大高度差都不超过1,以此来阻止二叉搜索树退化成为单叉树这种情况。

AVL树具有以下的特性:

  • 任意节点的左右子树最大高度差不超过1
  • 所有节点的左节点都比父节点小。
  • 所有节点的右节点都比父节点大。
  • 它的左右子树都是AVL树。
  • 中序遍历是有序的

AVL树与普通二叉搜索树的对比:

在这里插入图片描述

AVL的查找

AVL树的查找与二叉搜索树基本一致,因为其自身的性质,所以只要查找的数据比当前节点小就要到左节点找,反之就是右节点。

请添加图片描述

AVL树的插入

在介绍插入前得先说一下平衡因子,这是为了得知插入新结点后树是否还平衡的方式之一。

平衡因子是在每个结点上安置一个数字,如果是新插入的节点则它的数值为0,如果其在双亲节点的右边,则双亲节点的平衡因子++,反之–,然后继续向上调整,直到父节点的因子为0/2/-2。

在这里插入图片描述

AVL因为要保持其高度平衡的特性,所以每次插入都要检查其是否平衡,如果不平衡(平衡因子的绝对值大于1),则需要通过旋转来让树保持平衡。

AVL树的旋转大致分为两种情况:

  • 极端倾斜(左倾、右倾)
  • 非极端倾斜(局部左倾,局部右倾)

极端倾斜:
极端倾斜的情况比较容易解决,如果是右倾,那么只需要让平衡因子为2的节点做左旋运动,然后更新平衡因子即可,左倾则和右倾相反,做右旋操作。
请添加图片描述

局部倾斜:
局部倾斜分为局部左倾和右倾,而左右倾其中又分为三中情况,为了方便说明,我用parent来表示平衡因子(bf)为2的节点,subR来表示parent->right,subRL来表示subR->left 。

  • subRL为0则表示subRL为新增节点
  • subRL为1则表示新节点在subRL的右子树
  • subRL为-1则表示新节点在subRL的左子树

subRL为0的情况:
在这里插入图片描述
subRL为1的情况:
在这里插入图片描述
subRL为-1的情况:
在这里插入图片描述

代码部分

AVL树的定义

因为我们需要频繁去调整树的平衡,使用普通的二链结构会比较难以控制节点,所以我使用了三叉链的结构,多增加了一个指向父节点的指针。

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode(const std::pair<K, V> kv)
	: _left(nullptr)	
	, _right(nullptr)
	, _parent(nullptr)
    , _kv(kv)
	, _bf(0)
	{}	

	AVLTreeNode<K, V>* _left;	//左节点
	AVLTreeNode<K, V>* _right;	//右节点
	AVLTreeNode<K, V>* _parent;	//父节点
    std::pair<K, V> _kv;		//使用pair当作数据
	int _bf;   // 节点的平衡因子
};

查找

二叉搜索树与AVL树的搜索基本无区别

template <class K, class V>
typename AVLTree<K, V>::Node* AVLTree<K, V>::find(const std::pair<K, V>& data)
{
    Node* cur = _root;	
    while(cur)
    {
        if(cur->_kv.first < data.first)
        {	//到右节点寻找
            cur = cur->_right;
        }
        else if(cur->_kv.first > data.first)
        {	//到左节点寻找
            cur = cur->_left;
        }
        else 
        {	//找到
            return cur;
        }
    }

    return cur;
}

插入

AVL树的插入无非也就是弄清楚倾斜的时机和位置,就和上方所说的,AVL树的旋转情况只有极端和非极端的,如果没有出现不平衡则向上调整。

template <class K, class V>
bool AVLTree<K, V>::Insert(const std::pair<K, V> data)
{
    if(!_root)
    {
        _root = new Node(data);
        return true;
    }

    Node* cur = _root;
    Node* parent = nullptr;
    
    while(cur)	//先搜索
    {
        if(cur->_kv.first < data.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if(cur->_kv.first > data.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else 
            return false;
    }

    cur = new Node(data);		//创建新节点
    cur->_parent = parent;		//链接
    
    if(!parent->_left)
    {
        parent->_left = cur;
    }
    else 
    {
        parent->_right = cur;
    }

    while(parent)
    {
        if(cur == parent->_left)
            parent->_bf--;		//这里与上方动图有些许不同,动图与我平衡因子的加减是相反的
        else	// cur == parent->_right
            parent->_bf++;

        if(parent->_bf == 0)	//为0说明左右节点最大高度差一致
            break;
        else if(parent->_bf == 1 || parent->_bf == -1)	//为1则继续向上调整
        {
            cur = parent;		
            parent = parent->_parent;
        }
        else if(parent->_bf == 2 || 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;
        }
        else 
            assert(false);
    }
    return true;
}

旋转

AVL树的旋转其难点在于正确的连接节点,与调整平衡因子的数值。

void AVLTree<K, V>::RotateL(Node* parent)
{	//左旋
    Node* subR = parent->_right;	
    Node* subRL = subR->_left;
    Node* parentParent = parent->_parent;
	
    parent->_right = subRL;		//交换父节点和其右孩子的位置
    parent->_parent = subR;
    subR->_left = parent;		
    subR->_parent = parentParent;
    
    if(subRL)	//subRL有为空的可能性
        subRL->_parent = parent;

    if(_root == parent)
    {	
        _root = subR;
    }
    else 
    {	//连接祖父节点
        if(parent == parentParent->_left)
        {
            parentParent->_left = subR;
        }
        else 
        {
            parentParent->_right = subR;
        }
    }
    parent->_bf = subR->_bf = 0;	//调整平衡因子
}

void AVLTree<K, V>::RotateR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    Node* parentParent = parent->_parent;

    subL->_right = parent;
    subL->_parent = parentParent;

    parent->_left = subLR;
    parent->_parent = subL;

    if(subLR)
        subLR->_parent = parent;

    if(parent == _root)
        _root = subL;
    else
    {
        if(parent == parentParent->_left)
            parentParent->_left = subL;
        else
            parentParent->_right = subL;
    }

    subL->_bf = parent->_bf = 0;
} 

void AVLTree<K, V>::RotateRL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    int bf = subRL->_bf;	//先记录平衡因子,以防调整后丢失

    RotateR(parent->_right);
    RotateL(parent);
	//调整因子
    if(bf == 0)	//说明subRL是新增节点
    {
        parent->_bf = subR->_bf = subRL->_bf = 0;
    }
    else if(bf == 1)	//新增节点在subRL的右子树
    {
        parent->_bf = -1;
        subR->_bf = 0;
        subRL->_bf = 0;
    }
    else if(bf == -1) 	//新增节点在subRL的右子树
    {
        subRL->_bf = 0;
        subR->_bf = 1;
        parent->_bf = 0;
    }
    else 
    {
        assert(false);
    }
}

void AVLTree<K, V>::RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf;

    RotateL(parent->_left);
    RotateR(parent);

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

📓总结

AVL的时间复杂度:

函数时间复杂度
find O ( l o g 2 n ) O(log_2n) O(log2n)
insert() O ( l o g 2 n ) O(log_2n) O(log2n)

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

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

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

相关文章

哈希表HashTable

散列表&#xff08;Hash table&#xff0c;也叫哈希表&#xff09;&#xff0c;是根据键&#xff08;Key&#xff09;而直接访问在内存存储位置的数据结构。 哈希表中关键码就是数组的索引下标&#xff0c;然后通过下标直接访问数组中的元素&#xff0c;复杂度O(1) 哈希表本质…

外贸ERP系统是什么?推荐的外贸管理软件?

外贸ERP管理系统有哪些&#xff1f;海洋建站管理软件的功能&#xff1f; 为了更有效地处理外贸业务&#xff0c;许多企业正在寻找先进的工具和技术。为了提高效率、降低成本并增强竞争力&#xff0c;越来越多的外贸企业正在转向外贸ERP系统。那么&#xff0c;外贸ERP系统究竟是…

深度学习之基于Django+Tensorflow动物识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Django和TensorFlow的动物识别系统可以被设计成能够使用深度学习算法自动识别上传的图像中的动物种类&#xff…

CrystalDiskInfo/CrystalDiskMark/DiskGenius系统迁移

CrystalDiskInfo 主要用于看硬盘的各种信息&#xff0c;包括但不限于硬盘通电时间、通电次数、硬盘好坏状态 CrystalDiskMark 主要用于测试硬盘的读写速度、连续读写速度 DiskGenius 主要用于通过U盘装操作系统后进行&#xff0c;磁盘分区&#xff0c;更改磁盘名、隐藏部分…

女儿冬天的第一件羽绒服,这也太好看了

分享女儿的时尚穿搭 撞色插肩款羽绒服 同色系的精彩碰撞 描绘出绚烂的色彩 走在街上就是最靓的崽 显肤色显瘦超吸睛 妥投时尚小潮人一枚

Google App Campaigns的逻辑及其建议

Google App Campaigns&#xff08;Google应用推广&#xff09;是一种广告服务&#xff0c;旨在帮助应用开发者在Google平台上推广其应用程序。本文小编将讲讲Google App Campaigns的逻辑&#xff0c;并提供一些建议&#xff0c;以帮助应用开发者最大程度地利用这项服务。 1、Go…

gzip 压缩优化大 XML 响应的处理方法

当处理大型XML响应时&#xff0c;我们经常会面临内存限制和性能问题。 在处理这个问题时&#xff0c;我们可以使用Python的requests库和lxml库来解决。下面是解决方案的步骤&#xff1a; 1. 使用requests库发送HTTP请求获取XML响应。 2. 检查响应的Content-Encoding标头&…

高效开发与设计:提效Spring应用的运行效率和生产力 | 京东云技术团队

引言 现状和背景 Spring框架是广泛使用的Java开发框架之一&#xff0c;它提供了强大的功能和灵活性&#xff0c;但在大型应用中&#xff0c;由于Spring框架的复杂性和依赖关系&#xff0c;应用的启动时间和性能可能会受到影响。这可能导致开发过程中的迟缓和开发效率低下。优…

性能小课堂:Jmeter录制手机app脚本!

环境准备&#xff1a;1.手机2.wifi3.Jmeter 具体步骤&#xff1a; 1、启动Jmeter&#xff1b; 2、“测试计划”中添加“线程组”&#xff1b; 3、“工作台”中添加“HTTP代理服务器”&#xff1b; 4、配置代理服务器&#xff1a;Global Settings下面的端口配置&#xff1a…

免费AI画图工具大比拼:6款细节惊艳的推荐

1、即时灵感 即时灵感是一种专业的人工智能绘制肖像工具&#xff0c;由国内团队开发&#xff0c;对国内用户和新手用户非常友好&#xff0c;输入中文描述关键词可以在20秒内获得4张超级精致、超细节、超真实、超自然的肖像图片&#xff0c;风格多变&#xff0c;有丰富的模型供…

MySQL之JDBC编程

目录 1. 数据库编程的必备条件 2. Java的数据库编程&#xff1a;JDBC 3. JDBC工作原理 4. JDBC使用 4.1 IDEA配置JDBC 4.2 JDBC开发案例 4.3 JDBC使用步骤总结 5. JDBC常用接口和类 5.1 JDBC API 5.2 数据库连接Connection 5.3 Statement对象 5.4 ResultS…

去除尾部和头部空格及换行符

在使用Python的Requests库发送HTTP请求时&#xff0c;我们经常需要自定义请求头&#xff0c;以便与服务器进行通信。 在使用Requests库发送HTTP请求时&#xff0c;我们通常会定义请求头&#xff0c;以传递关键信息给服务器。然而&#xff0c;在构建请求头时&#xff0c;可能会…

【EI会议征稿】第五届电子商务与互联网技术国际学术会议(ECIT 2024)

2023 4th International Conference on E-Commerce and Internet Technology 第五届电子商务与互联网技术国际学术会议(ECIT 2024) 电子商务是以信息网络技术为手段&#xff0c;以商品交换为中心的商业活动。在互联网开放的网络环境下&#xff0c;基于客户端/服务端应用方式&…

动态时钟实现

前端HTMLCSS3JavaScript实现动态时钟 一、实现思路概述二、源代码(包含HTML、CSS、JS)三、图片资源与效果截图1. 图片资源2. 效果截图 一、实现思路概述 1. 通过HTML搭建基本时钟的页面结构(这里将时钟图片资源作为背景图&#xff09;2. 将时钟背景和时/分/秒图片进行CSS位置居…

Sui生态多家协议上线流动质押,兼顾收益与灵活性

在Sui上&#xff0c;流动质押协议允许DeFi用户质押SUI&#xff0c;并获得可交易或用于其他DeFi活动的流动质押标记token。这一过程绕过了传统质押中验证节点锁定token的问题。用户可以通过Sui的权益证明机制&#xff08;PoS&#xff09;确保网络的安全&#xff0c;同时参与生态…

艺术作品3D虚拟云展厅能让客户远程身临其境地欣赏美

艺术品由于货物昂贵、易碎且保存难度大&#xff0c;因此在艺术品售卖中极易受时空限制&#xff0c;艺术品三维云展平台在线制作是基于web端将艺术品的图文、模型及视频等资料进行上传搭配&#xff0c;构建一个线上艺术品3D虚拟展厅&#xff0c;为艺术家和观众提供了全新的展示和…

【MySQL】一些内置函数(时间函数、字符串函数、数学函数等,学会了有妙用)

内置函数 前言正式开始时间函数显示当前日期、时间、日期时间的日期计算相差多少天示例创建一张表&#xff0c;记录生日 留言表 字符串函数charsetconcatinstr(string, substring)ucase和lcaseleft(string, length)length求字符串长度replace(str, search_str, replace_str)tri…

2024年测试程序员必看系列之自动化测试框架

自动化测试框架概念 自动化测试框架是一个集成体系&#xff0c;这个体系中包含测试功能的函数库、测试数据源、测试对象以及可重用的模块。 框架&#xff08;framework&#xff09;是一个框子——指其约束性&#xff0c;也是一个架子——指其支撑性。是一个基本概念上的结构&…

如何优雅的避免空指针异常

文章目录 1.数据准备2.实战&#xff1a;获取用户所在的城市2.1.直接获取&#xff1b;容易出现空指针异常。2.2.使用if-else判断&#xff1b;避免了出现空指针的问题&#xff0c;但是代码结构层次嵌套多&#xff0c;不美观2.3.使用工具类美化一下if判断代码2.4.使用Optional解决…