【AVL树的模拟实现】

news2024/10/5 12:19:26

1 AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
    在这里插入图片描述如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度 O ( l o g 2 n ) O(log_2 n) O(log2n).

2 AVL树结点的定义

代码:

template<class K,class V>
class AVLNode
{
public:
	AVLNode<K, V>* _left;
	AVLNode<K, V>* _right;
	AVLNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;//balance factory (右边++,左边--)

public:
	AVLNode(const pair<K, V> kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLNode<K, V> Node;
private:
	Node* _root=nullptr;
};

这个跟我们讲解的普通搜索二叉树的思路几乎是一样的,很容易理解。


3 AVL树的插入

这是我们今天要讲解的重点,也是难点。实现AVL树的方式有很多,博主采用的是平衡因子加三叉链这种方式来实现,因为相对于其他方式这种方式的理解要稍微简单些。

首先基本的框架搭建好:

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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else return false;
		}
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
			return true;
	}

这个是我们讲解普通二叉搜索树玩剩下的,很好理解。
现在的关键是如何更新平衡因子?
我们不难发现:

  • 当插入在parent左边时,平衡因子–,插入在parent右边时平衡因子++;
  • 插入后parent的平衡因子为0,便不用向上更新了,如果为-1/1,便还要向上更新;
  • 当parent的平衡因子为-2/2时说明已经出问题了,我们就要使出旋转大法修正,旋转完毕后便不用向上更新了。

代码解释:

//更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;

			if (parent->_bf == 0)
				break;
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 2 || 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)
				{
					//先右单旋,再左单旋
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					//先左单旋,再右单旋
					RotateLR(parent);
				}
				else
				{
					assert(false);
				}
				break;//旋转完毕已经平衡了,记得break出去
			}
			else
			{
				assert(false);
			}
		}

其中出错要旋转不外乎份4种情况:左单旋,右单旋,左右双旋,右左双旋
我们一个一个来看:

3.1 右单旋

抽象图是这样的:
在这里插入图片描述看着也很好理解,这样旋转后30 60 的平衡因子都变成了0,整个树也是平衡的。
我画了一个具象图来帮助大家理解:
h==0时:
在这里插入图片描述h==1时:

在这里插入图片描述h==2时:

在这里插入图片描述代码实现:

void RotateR(Node* parent)
	{
		Node* childL = parent->_left;
		Node* childLR = childL->_right;
		Node* grand = parent->_parent;
		parent->_left = childLR;
		if (childLR)
			childLR->_parent = parent;

		childL->_right = parent;
		parent->_parent = childL;

		//别忘了,还要链接grand与childL的关系
		if (grand == nullptr)
		{
			_root = childL;
			childL->_parent = nullptr;
		}
		else
		{
			if (grand->_left == parent)
				grand->_left = childL;
			else
				grand->_right = childL;

			childL->_parent = grand;
		}

		//更新平衡因子
		parent->_bf = childL->_bf = 0;

	}

3.2 左单旋

左单旋和右单旋类似,只是换了下位置,这里我就只给出抽象图了,不画具象图:
在这里插入图片描述代码实现:

void RotateL(Node* parent)
	{
		Node* childR = parent->_right;
		Node* childRL = childR->_left;
		Node* grand = parent->_parent;

		parent->_right = childRL;
		if (childRL)
			childRL->_parent = parent;

		childR->_left =parent ;
		parent->_parent = childR;

		if (grand == nullptr)
		{
			_root = childR;
			childR->_parent = nullptr;
		}
		else
		{
			if (grand->_left == parent)
				grand->_left = childR;
			else
				grand->_right = childR;

			childR->_parent = grand;
		}
		
		parent->_bf = childR->_bf = 0;
	}

3.3 右左双旋

像下面这种情况,我们只是用单旋是解决不了问题的,要用双旋解决:
在这里插入图片描述先以90结点右单旋转化成了我们前面讲的单旋问题,再以30左单旋即可,但是要注意分析插入后未旋转前新节点后60的平衡因子,因为60的平衡因子不同我们旋转后所更新结点的平衡因子就有所差异。注意插入后60的平衡因子可能为0.(这里建议大家画图分析)

代码实现:

void RotateRL(Node* parent)
	{
		Node* childR = parent->_right;
		Node* childRL = childR->_left;

		int bf = childRL->_bf;
		RotateR(childR);
		RotateL(parent);

		//更新平衡因子
		childRL->_bf = 0;
		if (bf == -1)
		{
			childR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			childR->_bf = 0;
		}
		else if (bf == 0)
		{
			;
		}
		else
		{
			assert(false);
		}
	}

3.4 左右双旋

跟右左双旋类似,这里就不在多讲了:
在这里插入图片描述
代码实现:

void RotateLR(Node* parent)
	{
		Node* childL = parent->_left;
		Node* childLR = childL->_right;

		int bf = childLR->_bf;
		RotateL(childL);
		RotateR(parent);

		//更新平衡因子
		childLR->_bf = 0;
		if (bf == -1)
		{
			childL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			childL->_bf = -1;
		}
		else if (bf == 0)
		{
			;
		}
		else
		{
			assert(false);
		}
	}

4 AVL树的验证

代码:

private:
	Node* _root=nullptr;
	void _inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_inorder(root->_right);
	}

	size_t _height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _height(root->_left);
		int rightHeight= _height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	bool _isbalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);

		return (abs(leftHeight - rightHeight) <= 1) && _isbalance(root->_left) && _isbalance(root->_right);
	}

public:

	void inorder()
	{
		_inorder(_root);
	}

	size_t height()
	{
		return _height(_root);
	}

	bool isbalance()
	{
		return _isbalance(_root);
	}

平衡二叉树的验证我们很早就讲过了,所以相信大家能够很轻易看懂代码。测试时还可以根据左右子树高度差来判断平衡因子的正确性,大家可以自己下去试试。
大家下去可以用一些随机数来测测。
有需要的老铁可以去博主码云里看看:
点击这里
好了,今天的分享就到这里了,我们下期再见。


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

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

相关文章

人工智能基础部分14-蒙特卡洛方法在人工智能中的应用及其Python实现

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分14-蒙特卡洛方法在人工智能中的应用及其Python实现&#xff0c;在人工智能领域&#xff0c;蒙特卡洛方法&#xff08;Monte Carlo Method, MCM&#xff09;被广泛应用于各种问题的求解。本文首先将…

wvp-GB28181-pro录像功能开发环境搭建、配置、使用

开发环境、调试环境搭建 开发wvp平台搭建 离线安装脚本&#xff1a;https://gitcode.net/zenglg/ubuntu_wvp_online_install.git 下载离线安装脚本&#xff0c;完成wvp平台的部署 开发环境要求 操作系统&#xff1a;包管理工具是apt ky10桌面版uos桌面版deepin桌面版ubuntu桌面…

ArmDot.NET Crack

ArmDot.NET Crack ArmDot是一个.NET加密工具&#xff0c;用于保护使用.NET编写的程序。 企业需要保护他们的知识产权&#xff0c;包括他们的算法、产品和使用的资源的源代码。 然而&#xff0c;.NET编译器会生成一个通用的可访问代码。代码中嵌入的资源很容易访问&#xff0c;并…

RocketMQ不同的类型消息

目录 普通消息 可靠同步发送 可靠异步发送 单向发送 三种发送方式的对比 顺序消息 事物消息 两个概念 事务消息发送步骤 事务消息回查步骤 消息消费要注意的细节 RocketMQ支持两种消息模式: 普通消息 RocketMQ提供三种方式来发送普通消息&#xff1a;可靠同步发送、…

剑指Offer题集(力扣)

文章目录 剑指Offer题集&#xff08;[力扣题单](https://leetcode.cn/problemset/all/?listIdlcof&page1)&#xff09;[剑指 Offer 03. 数组中重复的数字](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)[剑指 Offer 04. 二维数组中的查找](https:…

SSM框架练习一(登录后关联数据表的业务模型)

需要实现的整体功能&#xff1a; 登录反馈信息列表展示查询反馈信息发表反馈 1.数据库设计 创建数据库 创建表结构及其约束 添加测试数据 工具&#xff1a;PHP、Navicat create table tab_user(id int primary key auto_increment,uname varchar(30) not null,pwd varc…

Weblogic XMLDecoder 反序列化漏洞(CVE-2017-10271复现)

文章目录 前言影响版本环境搭建漏洞复现深度利用 前言 CVE-2017-10271漏洞产生的原因大致是Weblogic的WLS Security组件对外提供webservice服务&#xff0c;其中使用了XMLDecoder来解析用户传入的XML数据&#xff0c;在解析的过程中出现反序列化漏洞&#xff0c;导致可执行任意…

从搬砖工到架构师,Java全栈学习路线总结

&#x1f307;文章目录 前言一、前置知识二、 Web前端基础示例&#xff1a;1.文本域2.密码字段 三、后端基础一. Java基础二. 数据库技术三. Web开发技术四. 框架技术五. 服务器部署 四、其他技术五、全栈开发六、综合实践七、学习教程一、前端开发二、后端开发三、数据库开发四…

springboot+jsp乡村中小学校园网站建设

随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;乡村小学校园网当然也不能排除在外&#xff0c;从校园概况、学校风采、招生信息的统计和分析&#xff0c;在过程中会产生大量的…

Maven依赖原则及如何解决Maven依赖冲突

前言 在大数据应用中&#xff0c;现在发现依赖关系非常复杂&#xff0c;在上线之前很长测试&#xff0c;前一段时间在部署udf 出现了导致生产Hiveserver2 宕机问题&#xff0c;出现严重事故。现在就咨询研究一下。Maven虽然已经诞生多年&#xff0c;但仍然是当前最流行的Java系…

Arrays:点燃你的数组操作技巧的隐秘武器。

前言 数组在 Java 中是一种常用的数据结构&#xff0c;用于存储和操作大量数据。但是在处理数组中的数据&#xff0c;可能会变得复杂和繁琐。Arrays 是我们在处理数组时的一把利器。它提供了丰富的方法和功能&#xff0c;使得数组操作变得更加简单、高效和可靠。无论是排序、搜…

【c语言】字符串类型转换 | itoa函数的使用

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

MySQL innodb介绍

InnoDB引擎的优点是支持兼容ACID的事务&#xff0c;以及参数完整性&#xff08;即对外键的支持&#xff09;。 Oracle公司2005年10月收购了Innovase&#xff1b;Innobase采用双认证授权。它使用GNU发行&#xff0c;也允许其他想将InnoDB结合到商业软件的团体好的授权 mysql5.…

Java 动态原理详解

Java 动态代理是一种非常重要的编程技术&#xff0c;它在很多场景下都有着广泛的应用。本文将介绍 Java 动态代理的实现原理&#xff0c;并附上相应的源码&#xff0c;以帮助读者更好地理解和应用这一技术。 一、什么是 Java 动态代理&#xff1f; Java 动态代理是一种在运行时…

【并发基础】Happens-Before模型详解

目录 一、Happens-Before模型简介 二、组成Happens-Before模型的八种规则 2.1 程序顺序规则&#xff08;as-if-serial语义&#xff09; 2.2 传递性规则 2.3 volatile变量规则 2.4 监视器锁规则 2.5 start规则 2.6 Join规则 一、Happens-Before模型简介 除了显示引用vo…

双目测距--5 双目相机 联合 YOLOv8

目录 效果&#xff1a; 1、立体矫正不改变图像尺寸 2、视差图尺寸与原图尺寸一致 3、视差图、深度信息图 4、几个重要的函数 createTracker() 5、代码 main.cpp utils.cpp 效果&#xff1a; 1、立体矫正不改变图像尺寸 左右相机图像立体矫正后&#xff0c;图像尺寸为变化…

freeRTOS中使用看门狗的一点思考

关于看门狗想必各位嵌入式软件开发的朋友应该都不会陌生的。在嵌入式软件开发中&#xff0c;看门狗常被用于监测cpu的程序是否正常在运行&#xff0c;如果cpu程序运行异常会由看门狗在达到设定的阈值时触发复位&#xff0c;从而让整个cpu复位重新开始运行。 看门狗的本质是一个…

Qt QQueue 安全的多线程队列、阻塞队列

文章目录 1. C queue 队列基本用法2. Qt QQueue 队列基本用法3. Qt QQueue 多线程队列4. Qt BlockingQueue 自定义线程安全的阻塞队列 1. C queue 队列基本用法 在C中&#xff0c;queue是一个模板类&#xff0c;用于实现队列数据结构&#xff0c;遵循先进先出的原则。 ♦ 常用…

测试3:用例

目录 1.测试用例的基本要素 2.测试用例的设计方法 1.基于需求的设计方法 2.等价类 1.概念 2.步骤: 3.例子 3.边界值 1.概念 2.步骤 3.例子 4.判定表 1.概念 2.设计测试用例 3.例子 5.正交排列 1.什么是正交表 2.测试用例 3.如何通过正交表设计测试用例 6.场景…

(3)Qt——信号槽

目录 1.信号槽的概念** 2.信号槽的连接*** 2.1自带信号 → 自带槽 2.2 自带信号 → 自定义槽 2.3 自定义信号 3. 参数传递** 3.1 全局变量 3.2 信号槽传参 4. 对应关系** 4.1 一对多 4.2 多对一 1.信号槽的概念** 信号槽指的是信号函数与槽函数的连接&#xff0c;可…