【C++进阶】AVL树的介绍及实现

【C++进阶】AVL树的介绍及实现

🥕个人主页:开敲🍉

🔥所属专栏:C++🥭

🌼文章目录🌼

1. AVL的介绍

2. AVL树的实现

    2.1 AVL树的结构

    2.2 AVL树的插入

        2.2.1 插入一个值的大概过程

        2.2.2 平衡因子的更新

        2.2.3 插入节点及更新平衡因子的代码实现

    2.3 旋转

        2.3.1 旋转的原则

        2.3.2 右单旋

        2.3.3 右单旋实现代码

        2.3.4 左单旋

        2.3.5 左单旋实现代码

        2.3.6 左右双旋

        2.3.7 左右双旋实现代码

        2.3.8 右左双旋

        2.3.9 右左双旋实现代码

    2.4 AVL树的查找

    2.5 AVL树的平衡检测

1. AVL的介绍

  ① AVL树是最早发明的自平衡二叉搜索树,AVL或是一颗空树,或是一颗具备以下性质的二叉搜索树:它的左右子树都是AVL树,且左右子树的高度差不超过1。AVL树是一颗高度平衡搜索二叉树,通过控制高度差来控制平衡。

  ② AVL树的名字来源于它的发明者G. M. Adelson-Velsky 和 E. M. Landis,他们是两位前苏联的科学家,而AVL树就是它们在 1962 年的论文《An algorithm for the organization on information》中发表的。

  ③ 本篇文章中AVL的实现我们引入平衡因子的概念,树中的每个节点都有一个平衡因子,平衡因子 = 右子树的高度 - 左子树的高度,结合着第 ① 条概念我们可以知道,平衡因子的值只能是 0/1/-1。AVL树不是一定要有平衡因子,还有其他方法可以代替,但是使用平衡因子让我们观察树是否平衡变得方便。

  ④ 为什么AVL要求高度差不超过1呢?直接将高度差限制死为0不是更平衡吗?道理很简单:一棵树不一定是满二叉树。如果将高度差限制死为0,则默认这棵树就是满二叉树,这显然是不符合实际的。那为什么要限制为1,不限制为2呢?因为任何不满的树,都可以通过变换、旋转等操作将它的每个节点高度差控制在1以内,既然可以控制到1以内,那自然1是最好的。

  ⑤ AVL树整体的节点数量和分布与完全二叉树类似,因此高度可以控制在log N,因此查找的效率也就是 O(log N) ,而普通的二叉搜索树在极端情况下,如插入节点:1,2、3、4、5、6,则二叉搜索树会退化为单链,查找效率也就退化为了 O(N)。因此AVL树的效率比起普通二叉搜索树提升了一个档次。

2. AVL树的实现
    2.1 AVL树的结构

  对于AVL中的每个节点,都有:_val变量,用于存放节点的值; _left 和 _right 指针,用于链接左孩子和右孩子;parent指针,用于指向其父节点,这个指针的作用是为了方便我们修改平衡因子,因为孩子平衡因子的改变可能会导致其父节点甚至祖宗节点平衡因子的改变,有了 _parent 就方便我们去到父节点修改平衡因子;_bf变量,用于存放平衡因子的值。

  对于AVL树本体:实现AVL树中的增删查改等功能。

  下面是AVL树节点和本体的大体框架:

template <class T>
class AVLTreeNode
{

public:

        AVLTreeNode(const T val)//AVL构造函数

               :_val(val)

                ,_left(nullptr)

                ,_right(nullptr)

                ,parent(nullptr)

                ,_bf(0)//每个新节点的左右子树都为空,因此平衡因子默认为0

        {}



        T _val;

        AVLTreeNode<T>* _left;

        AVLTreeNode<T>* _right;

        AVLTreeNode<T>* parent;

        int _bf;
};

template <class T>
class AVLTree
{
public:
    AVLTree()
        :_root(nullptr)
    {}

private:
    AVLTreeNode<T>* _root;//根节点
}
    2.2 AVL树的插入
        2.2.1 插入一个值的大概过程

  ① 插入值时按照二叉搜索树的规则插入:比当前节点值小走左边;反之走右边;相同走哪边都行。

  ② 新增节点以后,会影响其父亲甚至祖先节点的高度,因此在新插入一个值后需要改变新插入位置父节点以及祖先节点的平衡因子,但是更新也分为多种情况,最坏的情况会更新到根,一般情况更新到某个位置就会停止。具体情况我们在下面分析。

  ③ 如果更新平衡因子的过程中没有问题,则插入结束。那么什么是没问题,什么又是有问题呢?在下面分析。

  ④ 如果更新平衡因子后导致平衡因子 == 2 || == -2,则当前子树出现了问题,不再平衡,我们通过旋转操作将其重新变平衡。

        2.2.2 平衡因子的更新

  平衡因子 = 右子树高度 - 左子树高度(当然这里也可以 左子树高度 - 右子树高度,随你喜欢)

更新原则:

  ① 只有子树高度变化才会影响平衡因子。

  ② 插入节点一定会导致高度变化。如果节点插入在 parent 左边,则平衡因子--;如果节点插入在 parent 右边,则平衡因子++。

  ③ 以 parent 为根节点的子树的高度是否变化,决定了是否要继续向上更新平衡因子。

更新停止条件:

  ① 更新后 parent 的平衡因子 = 0,说明 parent 的平衡因子的变化为 1 -> 0 或者 -1 -> 0,说明parent 之前的平衡因子为 1 或者 -1,也就说明更新前 parent 的左右子树一边高一边低,更新后左右高度相同,此时不会影响 parent 的父亲或者祖宗节点,更新完 parent 就结束:

  ② 更新后 parent 的平衡因子 = 1 或者 -1,说明 parent 的平衡因子的变化为 0 -> 1 或者 0 -> -1,说明更新前 parent 的左右子树高度相同,更新后出现了一边高一边低的情况,此时更新完 parent 的平衡因子后还要继续向上更新:

  ③ 更新后 parent 的平衡因子 = 2 或者 -2,此时破坏了AVL树的结构,需要通过旋转操作将 parent 所在子树旋转平衡。旋转后 parent 所在子树高度平衡,不需要再向上更新:

        2.2.3 插入节点及更新平衡因子的代码实现
bool Insert(const T& val)
{
    Node* parent = nullptr;
    Node* cur = _root;
    if (!_root) _root = new Node(val);//如果是第一个插入的节点,则作为根
    else
    {
        while (cur)//二叉搜索树插入规则
        {
            if (cur->_val > val)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (cur->_val < val)
            {
                parent = cur;
                cur = cur->_right;
            }
            else assert(0);
        }
        cur = new Node(val);
        if (parent->_val > val) parent->_left = cur;
        else if (parent->_val < val) parent->_right = cur;
        else assert(false);
        cur->parent = parent;

        while (parent)
        {

//更新平衡因子:如果节点插入在 parent 左边,则平衡因子--;反之,平衡因子++
            if (parent->_left == cur)
                parent->_bf--;
            else if (parent->_right == cur)
                parent->_bf++;
            else assert(false);
            if (parent->_bf == 0) return true;//不需要继续向上更新
            else if (parent->_bf == 1 || parent->_bf == -1)//需要继续向上更新
            {
                cur = parent;//向上更新
                parent = parent->parent;
            }
            else if (parent->_bf == 2 || parent->_bf == -2)//此时不平衡了,进行旋转操作
            {
                //旋转

                //旋转操作在后面
                return true;
            }
        }
    }
    return true;
}
    2.3 旋转
        2.3.1 旋转的原则

① 保证旋转后整棵树还是二叉搜索树

② 让旋转的子树从不平衡到平衡,旋转共分为四种:右单旋、左单旋、左右双旋、右左双旋

        2.3.2 右单旋

        2.3.3 右单旋实现代码
//右单旋
void RotateR(Node* parent)
{
	Node* Pparent = parent->parent;
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	subL->parent = Pparent;
	subL->_right = parent;
	
	parent->_left = subLR;
	parent->parent = subL;
	if(subLR) subLR->parent = parent;

	if (!Pparent) _root = subL;
	else if (Pparent->_left == parent) Pparent->_left = subL;
	else if (Pparent->_right == parent) Pparent->_right = subL;
	else assert(0);

	parent->_bf = subL->_bf = 0;
}
        2.3.4 左单旋

  左单旋与右单旋是完全类似的,因此这里只画旋转的过程,不作详细解释:

        2.3.5 左单旋实现代码
//左单旋
void RotateL(Node* parent)
{
	Node* Pparent = parent->parent;
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	subR->parent = Pparent;
	subR->_left = parent;

	parent->_right = subRL;
	parent->parent = subR;
	if (subRL) subRL->parent = parent;

	if (!Pparent) _root = subR;
	else if (Pparent->_left == parent) Pparent->_left = subR;
	else if (Pparent->_right == parent) Pparent->_right = subR;
	else assert(0);

	parent->_bf = subR->_bf = 0;
}
        2.3.6 左右双旋

  双旋对于单旋来说要麻烦不少,来看下面的图:

  这个时候就要搬出我们的左右双旋:

  至此,我们看似解决了这个问题,但真的是这样吗?

  当然不是,双旋的最难点——平衡因子的问题我们还没解决呢?你可能会问,单旋不是帮我们处理好了吗?nonono,双旋的平衡因子问题单旋可没法解决,来看下面的图:

        2.3.7 左右双旋实现代码
	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		RotateR(subR);//右单旋
		RotateL(parent);//左单旋


		if (subRL->_bf == 0) parent->_bf = subR->_bf = 0;
		else if (subRL->_bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (subRL->_bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else assert(0);
		subRL->_bf = 0;
	}
        2.3.8 右左双旋

  右左双旋与左右双旋也是完全类似的,这里也是只画出过程图,不作详细解释:

        2.3.9 右左双旋实现代码
	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		RotateR(subR);//右单旋
		RotateL(parent);//左单旋


		if (subRL->_bf == 0) parent->_bf = subR->_bf = 0;
		else if (subRL->_bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (subRL->_bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else assert(0);
		subRL->_bf = 0;
	}
    2.4 AVL树的查找

  AVL树的查找就非常简单了,中序遍历:

	//查找
	const Node* find(Node* root,const T val)
	{
		if (!root) return nullptr;
		if (root->_val == val) return root;

		const Node* ret_left = find(root->_left, val);
		if (ret_left) return ret_left;
		return find(root->_right, val);
	}
    2.5 AVL树的平衡检测
#include "AVLTree.h"

int main()
{
	AVLTree<int> t;
	 常规的测试⽤例
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试⽤例
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.Printf();

	t.Find(14);
	t.Find(99);
	return 0;
}

                                                   创作不易,点个赞呗,蟹蟹啦~ 

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

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

相关文章

2024年中国电子学会青少年软件编程(Python)等级考试(二级)核心考点速查卡

考前练习 2024年03月中国电子学会青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;二级&#xff09;答案 解析 2024年06月中国电子学会青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;二级&#xff09;答案 解析 知识点描述 …

C语言题目之单身狗2

文章目录 一、题目二、思路三、代码实现 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、题目 二、思路 第一步 在c语言题目之打印单身狗我们已经讲解了在一组数据中出现一个单身狗的情况&#xff0c;而本道题是出现两个单身狗的情况。根据一个数…

LabVIEW编程能力如何能突飞猛进

要想让LabVIEW编程能力实现突飞猛进&#xff0c;需要采取系统化的学习方法&#xff0c;并结合实际项目进行不断的实践。以下是一些提高LabVIEW编程能力的关键策略&#xff1a; 1. 扎实掌握基础 LabVIEW的编程本质与其他编程语言不同&#xff0c;它是基于图形化的编程方式&…

宝塔面板部署雷池社区版教程

宝塔面板部署雷池社区版教程 简单介绍一下宝塔面板&#xff0c;安全高效的服务器运维面板&#xff0c;使用宝塔面板的人非常多 在网站管理上&#xff0c;许多用户都是通过宝塔面板进行管理&#xff0c;宝塔面板的Nginx默认监听端口为80和443&#xff0c;这就导致共存部署时雷池…

信息安全工程师(23)网络安全体系相关模型

前言 网络安全体系相关模型是描述网络安全体系如何实现的理论框架和抽象模型&#xff0c;它们为理解和设计网络安全解决方案提供了系统化的方法。 1. PDR模型 提出者&#xff1a;美国国际互联网安全系统公司(ISS)核心内容&#xff1a;保护(Protection)、检测(Detection)、响应(…

MongoDB 双活集群在运营商的实践

在现代电信行业中&#xff0c;订单中心作为核心业务系统之一&#xff0c;承担着处理客户订单、管理订单状态、与各个业务系统进行交互等重要职责。其订单中心的高效运作直接关系到客户体验和业务连续性。为了满足不断增长的业务需求和日益复杂的运营环境&#xff0c;运营商需要…

华为HarmonyOS地图服务 11 - 如何在地图上增加点注释?

场景介绍 本章节将向您介绍如何在地图的指定位置添加点注释以标识位置、商家、建筑等&#xff0c;并可以通过信息窗口展示详细信息。 点注释支持功能&#xff1a; 支持设置图标、文字、碰撞规则等。支持添加点击事件。 PointAnnotation有默认风格&#xff0c;同时也支持自定…

11.梯度下降法的思想——举足轻重的模型优化算法

引言 优化算法在机器学习和人工智能中扮演者至关重要的角色。机器学习模型的训练过程本质上是一个优化问题&#xff0c;即通过调整模型参数来最小化损失函数。梯度下降法(Gradient Descent)在优化算法中占据着重要的地位&#xff0c;因其简单、有效且易于实现。 通过阅读本篇…

jupyter使用pytorch

1、激活环境 以下所有命令都在Anaconda Prompt中操作。 conda activate 环境名称我的环境名称是myenv 如果不知道自己的pytorch配在哪个环境&#xff0c;就用下面方法挨个试。 2、安装jupyter 1、安装 pip install jupyter2、如果已经安装&#xff0c;检查jupyter是否已…

【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版

文章目录 一、算法概念二、算法原理&#xff08;一&#xff09;感知机&#xff08;二&#xff09;多层感知机1、隐藏层2、激活函数sigma函数tanh函数ReLU函数 3、反向传播算法 三、算法优缺点&#xff08;一&#xff09;优点&#xff08;二&#xff09;缺点 四、MLP分类任务实现…

多模态大模型学习(一)

参考&#xff1a;https://www.bilibili.com/video/BV1kT411o7a6?p2&spm_id_frompageDriver&vd_source156234c72054035c149dcb072202e6be 余弦相似度&#xff0c;让正样本内积趋近于1&#xff0c;负样本趋近于-1。度量学习。N特别大时&#xff0c;负样本远大于正样本&…

喜报!厦门唐普信息技术斩获 厦门火炬高新区“火炬瞪羚企业” 殊荣

近日&#xff0c;依据《厦门火炬高新区关于支持科技企业创新提质发展的若干措施》&#xff08;厦高管规 [2024] 3 号&#xff09;&#xff0c;火炬管委会启动了 2024 年度 “火炬瞪羚企业” 认定工作。 厦门火炬高新区管委会对2024年“火炬瞪羚企业”名单进行了公示&#xff0…

机器翻译之多头注意力(MultiAttentionn)在Seq2Seq的应用

目录 1.多头注意力&#xff08;MultiAttentionn&#xff09;的理念图 2.代码实现 2.1创建多头注意力函数 2.2验证上述封装的代码 2.3 创建 添加了Bahdanau的decoder 2.4训练 2.5预测 3.知识点个人理解 1.多头注意力&#xff08;MultiAttentionn&#xff09;的理念图…

protobuf编码方式

protobuf编码方式 一个简单的例子 message Test1 {optional int32 a 1; }上述的proto文件&#xff0c;设置a 150&#xff0c;那么将其序列化后&#xff0c;得到的数据就是08 96 01&#xff0c;然后你使用protoscope工具去解析这些数据&#xff0c;就得到1 : 150&#xff0c…

基于深度学习的花卉智能分类识别系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 传统的花卉分类方法通常依赖于专家的知识和经验&#xff0c;这种方法不仅耗时耗力&#xff0c;而且容易受到主观因素的影响。本系统利用 TensorFlow、Keras 等深度学习框架构建卷积神经网络&#…

【Linux:共享内存】

共享内存的概念&#xff1a; 操作系统通过页表将共享内存的起始虚拟地址映射到当前进程的地址空间中共享内存是由需要通信的双方进程之一来创建但该资源并不属于创建它的进程&#xff0c;而属于操作系统 共享内存可以在系统中存在多份&#xff0c;供不同个数&#xff0c;不同进…

推荐5款压箱底的宝贝,某度搜索就有

​ 今天要给大家推荐5款压箱底的宝贝软件了&#xff0c;都是在某度搜索一下就能找到的好东西。 1.桌面壁纸——WinDynamicDesktop ​ WinDynamicDesktop是一款创新的桌面壁纸管理工具&#xff0c;能根据用户的地理位置和时间自动更换壁纸。软件内置多个美丽的动态壁纸主题&am…

苹果电脑系统重磅更新——macOS Sequoia 15 系统 新功能一 览

有了 macoS Sequoia&#xff0c;你的工作效率将再次提升&#xff1a;快速调整桌面布局&#xff0c;一目了然地浏览网页重点&#xff0c;还可以通过无线镜像功能操控你的iPhone。 下面就来看看几项出色新功能&#xff0c;还有能够全面发挥这些功能的 App 和游戏。 macOS Sequo…

智能新突破:AIOT 边缘计算网关让老旧水电表图像识别

数字化高速发展的时代&#xff0c;AIOT&#xff08;人工智能物联网&#xff09;技术正以惊人的速度改变着我们的生活和工作方式。而其中&#xff0c;AIOT 边缘计算网关凭借其强大的功能&#xff0c;成为了推动物联网发展的关键力量。 这款边缘计算网关拥有令人瞩目的 1T POS 算…

使用build_chain.sh离线搭建匹配的区块链,并通过命令配置各群组节点的MySQL数据库

【任务】 登陆Linux服务器&#xff0c;以MySQL分布式存储方式安装并部署如图所示的三群组、四机构、 七节点的星形组网拓扑区块链系统。其中&#xff0c;三群组名称分别为group1、group2和group3&#xff0c; 四个机构名称为agencyA、agencyB、agencyC、agencyD。p2p_port、cha…