AVL树的完全指南:平衡与性能

news2024/11/26 18:48:53

文章目录

  • AVL树简介
  • AVL的操作
    • 建立一个AVL树
    • 插入操作
    • 删除操作
  • 书写代码
    • 1.构造函数和析构函数
    • 2.获取最大值和最小值
    • 3.树的高度和节点个数
    • 3.前序中序和后序遍历
    • 4.判断树是否为空树
    • 5.四个旋转操作
    • 6.获取平衡因子
    • 7.插入操作
    • 8.删除操作
    • 9.搜索节点
    • .h文件中的定义
  • 总结

在这里插入图片描述

AVL树简介

AVL树是一种自平衡的二叉搜索树,它的命名来源于其发明者 G. M. Adelson-Velsky 和 E. M. Landis。AVL树通过保持树的平衡性来提高搜索、插入和删除操作的效率。
在AVL树中,每个节点都有一个平衡因子,它表示节点的左子树高度与右子树高度的差值。平衡因子可以是-1、0或1,如果任何节点的平衡因子的绝对值大于1,则该树就不平衡,需要进行平衡调整。

AVL树本质也是一颗二叉查找树
但是在二叉查找树上加了平衡的概念,因为二叉查找树有很多限制的因素,当二叉查找树的节点呈线性分布的时候,整个二叉查找树就的效率就变成O(N),所以在AVL树中就不存在不平衡的情况。

1.AVL树的特点:

  1. 左子树和右子树的高度差不大于1。
  2. 左子树和右子树的子树也是一颗AVL树

2.AVL树的相关概念

  1. 平衡因子:节点的平衡因子=左子树的高度-右子树的高度在这里插入图片描述
    注意看上面的图上面的等式分别都代表了每个节点的平衡因子

3.AVL树对比普通二叉搜索树
2. 平衡性保证: AVL树保持了树的平衡性,即任何时刻树中任意节点的左右子树高度差不超过1。而普通的二叉搜索树可能会因为插入或删除操作而导致树的不平衡,从而影响了搜索、插入和删除操作的性能。

  1. 稳定的性能: 由于AVL树的平衡性,搜索、插入和删除操作的时间复杂度始终保持在 O(log n) 的水平,其中 n 表示树中节点的数量。而普通的二叉搜索树在最坏情况下可能会退化成链表,导致搜索、插入和删除操作的时间复杂度上升至 O(n)。

  2. 高效的搜索操作: AVL树的平衡性保证了树的高度始终保持在较小的范围内,使得搜索操作非常高效。而普通的二叉搜索树可能会因为不平衡而导致搜索操作的性能下降。

  3. 快速的插入和删除操作: AVL树的自平衡性保证了插入和删除操作的高效性,使得这些操作的时间复杂度始终为 O(log n)。而普通的二叉搜索树在插入或删除节点后可能需要进行额外的平衡调整操作,导致性能下降。

  4. 适用于高性能需求的场景: 由于AVL树在搜索、插入和删除操作上的高效性,它常被用作数据库中的索引结构,以提供快速的数据检索功能。而普通的二叉搜索树可能无法满足高性能的需求。

最直接的:
在这里插入图片描述

AVL的操作

建立一个AVL树

首先AVL树和二叉树一样,需要一个左子树和右子树的索引,还有需要一个存储值的关键字,除此之外,我们还需要保存当前节点的高度,因为在AVL树当中涉及到了高度之差,如果我们将当前节点的高度保存起来,就不用每次去计算了

代码:

class AVLNode
{
	friend class AVLTree;
public:

private:
	AVLNode* _left;
	AVLNode* _right;
	int _key;
	int _height;
};


class AVLTree
{
public:

private:
	AVLNode* _root;
};

**这里我们用两个类来定义AVL树,比较方便,一个类定义节点,一个类维护节点

插入操作

当当前节点的平衡因子为0的时候,下一个值随便怎么插入都不会影响平衡,而当当前节点的平衡因子等于1的时候,下一个值插入的位置就会影响当前节点的平衡了,插入操作一共分为四种情况,我们一一介绍:

1.右旋
在这里插入图片描述

首先我们看上面这张图,当我们插入一个新的节点0时,很显然10的平衡因子是2,已经超过了AVL树定义的最大的平衡因子,所以,我们要对上面这颗树进行旋转让其满足AVL树的要求.
很显然,只需要将10右旋,然后让7作为10和4的新的父亲节点就可以了。
动图展示:
在这里插入图片描述

第二种需要右旋的情况:
在这里插入图片描述

这里只需要记住,,当我们要右旋时,冲突的右孩子作为新的左孩子就行了。
2.左旋

在这里插入图片描述
同样的,上面这个AVL树中的7的平衡因子的绝对值已经超过了1,所以应该对其进行旋转,由于4的平衡是-2,7的平衡因子是-1,,所以这种情况也被称之为RR型,对于这种RR型我们只需要对其进行一次右旋就可以解决问题了。
动图展示:
在这里插入图片描述
还有一种复杂的清情形:
在这里插入图片描述
先右旋再左旋
在这里插入图片描述

对于上面的AVL树,E是新插入的节点,首先这颗树的A节点的平衡因子是-2,然后就是C的平衡因子是1,这种情况我们把它叫做RL型,只需要对平衡因子是1的节点先进行右旋,然后再对平衡因子是A的节点进行左旋就可以了。

先左旋再右旋
在这里插入图片描述

6是最后插入的节点对于这种情况,1的平衡因子是2,而2的平衡因子是-1.这种情况被称为是LR型,只需要先对平衡因子为-1的节点先左旋,然后再对平衡因子是2的节点进行右旋即可。

删除操作

删除操作和插入操作类似,再每次删除之后都要对祖先节点的平衡因子进行检查。

书写代码

1.构造函数和析构函数

//构造函数
AVLTree::AVLTree(AVLNode* root) :_root(root) {}

//析构函数
AVLTree::~AVLTree()
{
	clear(_root);
}
void AVLTree::clear(AVLNode* node)
{
	if (node == nullptr)
	{
		return;
	}
	clear(node->_left);
	clear(node->_right);
	delete node;
}

2.获取最大值和最小值

//获取最小值
int AVLTree::getMin()
{
	if (_root == nullptr)
	{
		return INT_MIN;
	}
	AVLNode* root = _root;
	while (root->_left != nullptr)
	{
		root = root->_left;
	}
	return root->_key;
}

//获取最大值
int AVLTree::getMax()
{
	if (_root == nullptr)
	{
		return INT_MAX;
	}
	AVLNode* root = _root;
	while (root->_right != nullptr)
	{
		root = root->_right;
	}
	return root->_key;
}
//函数重载,用于返回节点
AVLNode* AVLTree::getMin(AVLNode* node)
{
	if (node == nullptr)
	{
		return nullptr;
	}
	while (node->_left != nullptr)
	{
		node = node->_left;
	}
	return node;
}

3.树的高度和节点个数

//获取AVL树的节点的个数
int AVLTree::AssistgetSize(AVLNode* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	return AssistgetSize(root->_left) + AssistgetSize(root->_right) + 1;
}
int AVLTree::getSize()
{
	return AssistgetSize(_root);
}
//获取高度
int AVLTree::getHeight()
{
	return AssistgetHeight(_root);
}
int AVLTree::AssistgetHeight(AVLNode* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	return root->_height;
}

3.前序中序和后序遍历

//中序遍历
void AVLTree::AssistinOrderTraversal(AVLNode* _root)
{
	if (_root == nullptr)
	{
		return;
	}
	AssistinOrderTraversal(_root->_left);
	cout << _root->_key << ' ';
	AssistinOrderTraversal(_root->_right);
}
void AVLTree::inOrderTraversal()
{
	AssistinOrderTraversal(_root);
}

//前序遍历
void AVLTree::preOrderTraversal()
{
	AssistpreOrderTraversal(_root);
}
void AVLTree::AssistpreOrderTraversal(AVLNode* root)
{
	if (root == nullptr)
	{
		return;
	}
	cout << root->_key << ' ';
	AssistpreOrderTraversal(root->_left);
	AssistpreOrderTraversal(root->_right);
}
//后序遍历
void AVLTree::AssistpostOrderTraversal(AVLNode* root)
{
	if (root == nullptr)
	{
		return;
	}
	AssistpostOrderTraversal(root->_left);
	AssistpostOrderTraversal(root->_right);
	cout << root->_key << ' ';
}
void AVLTree::postOrderTraversal()
{
	AssistpostOrderTraversal(_root);
}

4.判断树是否为空树

//判空
bool AVLTree::isEmpty()
{
	return _root == nullptr;
}

5.四个旋转操作

/左旋
void AVLTree::leftRotate(AVLNode*& node)
{
	AVLNode* right = node->_right;
	node->_right = right->_left;
	right->_left = node;
	node = right;
}
//右旋
void AVLTree::rightRotate(AVLNode*& node)
{
	AVLNode* left = node->_left;
	node->_left = left->_right;
	left->_right = node;
	node = left;
}
//先右旋再左旋
void AVLTree::rightleftRotate(AVLNode*& node)
{
	rightRotate(node->_right);
	leftRotate(node);
}
//先左旋再右旋
void AVLTree::leftrightRotate(AVLNode*& node)
{
	leftRotate(node->_left);
	rightRotate(node);
}

6.获取平衡因子

int AVLTree::getbalance(AVLNode* root)
{
	return AssistgetHeight(root->_left) - AssistgetHeight(root->_right);
}

7.插入操作

AVLNode* AVLTree::AssistInsert(int key, AVLNode*& node)
{
	if (node == nullptr)
	{
		node = new AVLNode(key, nullptr, nullptr);
		return node;
	}
  	else if (node->_key > key)
	{
		AssistInsert(key, node->_left);
		//判断平衡因子是否是2,判断是LL型还是LR型
		if (getbalance(node) == 2)
		{
			//LL
			if (getbalance(node->_left) == 1)
			{
				//右旋
				rightRotate(node);
			}
			//LR
			else
			{
				//先左旋再右旋
				leftrightRotate(node);
			}
		}
	}
	else if (node->_key < key)
	{
		//向右边插入
		AssistInsert(key, node->_right);
		//如果是-2,那就是RR型或者是RL型
		if (getbalance(node) == -2)
		{
			//RR
			if (getbalance(node->_right) == -1)
			{
				//左旋
				leftRotate(node);
			}
			//RL
			else
			{
				//先右旋再左旋
				rightleftRotate(node);
			}
		}
	}
	node->_height = max(AssistgetHeight(node->_left), AssistgetHeight(node->_right)) + 1;
	return node;
}

void AVLTree::Insert(int key)
{
	_root = AssistInsert(key, _root);
}

8.删除操作

AVLNode* AVLTree::Assistremove(int key, AVLNode*& node)
{
	if (node == nullptr)
	{
		//空节点不需要删除直接返回nullptr
		return nullptr;
	}
	//要删除的值小于当前节点的值
	if (node->_key > key)
	{
		//递归左子树
		node->_left = Assistremove(key, node->_left);
	}
	//删除值大于当前节点的值
	else if (node->_key < key)
	{
		//递归右子树
		node->_right = Assistremove(key, node->_right);
	}
	else
	{
		//1.左子树是空树,右子树和左子树都是空树
		if (node->_left == nullptr)
		{
			AVLNode* tmp = node->_right;
			delete node;
			return tmp;
		}
		//只有右子树是空树的时候
		if (node->_right == nullptr)
		{
			AVLNode* tmp = node->_left;
			delete node;
			return tmp;
		}
		//左子树和右子树都不是空树时
		else
		{
			//找到右子树的最小值节点
			AVLNode* min = getMin(node->_right);
			//赋值
			node->_key = min->_key;
			//将删除有左子树和右子树的节点转换为删除末尾的节点
			node->_right = Assistremove(min->_key, node->_right);
		}
	}
	node->_height = max(AssistgetHeight(node->_left), AssistgetHeight(node->_right)) + 1;
	int balance = getbalance(node);
	if (balance == 2)
	{
		if (getbalance(node->_left) == 1)
		{
			rightRotate(node);
		}
		else
		{
			leftrightRotate(node);
		}
	}
	else if (balance == -2)
	{
		if (getbalance(node->_right) == -1)
		{
			leftRotate(node);
		}
		else
		{
			rightleftRotate(node);
		}
	}
	return node;
}
void AVLTree::remove(int key)
{
	_root = Assistremove(key, _root);
}

9.搜索节点

//搜索
bool AVLTree::search(int key, AVLNode* node)
{
	if (node == nullptr)
	{
		return false;
	}
	if (node->_key == key)
	{
		return true;
	}
	if (node->_key > key)
	{
		return search(key, node->_left);
	}
	else
	{
		return search(key, node->_right);
	}
}
bool AVLTree::search(int key)
{
	return search(key, _root); 
}

上面的代码对照着图理解更容易理解

.h文件中的定义

class AVLNode
{
	friend class AVLTree;
public:
	AVLNode(int key, AVLNode* left, AVLNode* right, int hight = 0)
	{
		_key = key;
		_left = left;
		_right = right;
		_height = hight;
	}
private:
	AVLNode* _left;
	AVLNode* _right;
	int _key;
	int _height;
};


class AVLTree
{
public:
	AVLTree(AVLNode* root = nullptr);
	~AVLTree();

	int getbalance(AVLNode* root);

	AVLNode* AssistInsert(int key, AVLNode*& node);
	void Insert(int key);

	void leftRotate(AVLNode*& node);
	void rightRotate(AVLNode*& node);
	void rightleftRotate(AVLNode*& node);
	void leftrightRotate(AVLNode*& node);

	AVLNode* Assistremove(int key, AVLNode*& node);
	void remove(int key);

	bool search(int key);
	bool search(int key, AVLNode* node);

	int getMin();
	AVLNode* getMin(AVLNode* node);
	int getMax();
	int AssistgetHeight(AVLNode* root);
	int getHeight();

	void inOrderTraversal();
	void AssistinOrderTraversal(AVLNode* _root);
	void preOrderTraversal();
	void AssistpreOrderTraversal(AVLNode* _root);
	void postOrderTraversal();
	void AssistpostOrderTraversal(AVLNode* _root);

	int getSize();
	int AssistgetSize(AVLNode* root);
	void clear(AVLNode* node);
	bool isEmpty();
private:
	AVLNode* _root;
};

总结

在本篇博客中,我们深入探讨了 AVL 树这一经典的数据结构。AVL 树作为一种自平衡的二叉搜索树,通过保持树的平衡性,提供了高效的搜索、插入和删除操作。我们从 AVL 树的基本概念出发,介绍了它的定义、特性和基本操作,包括旋转操作和平衡因子的概念。通过具体的例子和图示,我们深入理解了 AVL 树的工作原理和算法设计。

AVL 树之所以如此重要和受欢迎,主要是因为它在各种应用中都具有突出的优势。无论是作为数据库索引、编译器中的符号表还是操作系统中的文件系统,AVL 树都能够提供高效的数据检索功能,保证了程序的性能和效率。通过学习和掌握 AVL 树,我们不仅可以解决实际问题,提高程序的性能,还能够深入理解和应用其他复杂数据结构,为我们的编程技能和软件工程能力增添新的高度。

希望通过本篇博客的阅读,读者能够对 AVL 树有一个更加深入的理解,并且能够将其应用到实际的项目中去,为软件开发和数据处理带来更大的价值。感谢大家的阅读和支持!

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

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

相关文章

企业邮箱是什么?怎么申请一个企业邮箱

企业邮箱是什么&#xff1f;企业邮箱包含着许多企业需要的功能&#xff0c;包含统一创建签名、大容量存储、域名定制等功能&#xff0c;这些功能能够帮助企业更好地展示企业的专业形象以及更好得协作办公。本文将详细介绍企业邮箱的概念、特征和企业邮箱的申请步骤。 一、企业…

EasyExcel 中实体类的注解@ExcelProperty

ExcelProperty(value "职务", index 0) value 与index 的优先级, 实测得出下面结论. 1、只有value : 按照value 的匹配 2、只有index: 按照index 的匹配 3、 同时有value和index: 按照index的匹配. 结果: 按照index的匹配, 找到的数据 {"administrat…

Django 安全性与防御性编程:如何保护 Django Web 应用

title: Django 安全性与防御性编程&#xff1a;如何保护 Django Web 应用 date: 2024/5/13 20:26:58 updated: 2024/5/13 20:26:58 categories: 后端开发 tags: CSRFXSSSQLUploadHTTPOnlyPasswordSession 跨站请求伪造&#xff08;CSRF&#xff09; 跨站请求伪造&#xff0…

TriCore:Interrupt 2

今天继续来看看 IR 模块。 名词缩写 缩写全称说明IRInterrupt Router SRService Request 包括&#xff1a; 1. External Resource 2. Internal Resource 3.SW&#xff08;Software&#xff09; SPService Privoder 包括&#xff1a; 1. CPU 2. DMA SRNService Request NodeS…

宁夏银川最牛起名大师的老师颜廷利:宝与饱,饿跟恶

对于中国优秀传统文化之根-汉语而言&#xff0c; 恶&#xff0c;对应着‘饿’&#xff1b; 宝&#xff0c;对应着‘饱’… 由此可见&#xff0c;无论是‘饿’&#xff08;与‘恶’同音&#xff09;&#xff0c;还是‘饱’&#xff08;与‘宝’通音&#xff09;&#xff0c;实际…

GDPU unity游戏开发 角色控制器与射线检测

在你的生活中&#xff0c;你一直扮演着你的角色&#xff0c;别被谁控制了。 小试 1. 创建一个角色控制器&#xff0c;通过键盘控制角色控制器的移动&#xff0c;角色控制器与家具发生碰撞后&#xff0c;通过Debug语句打印出被碰撞物体的信息(搜索OnControllerColliderHit的使用…

GO语言核心30讲 实战与应用 (WaitGroup和Once,context,Pool,Map,字符编码,string包,bytes包)

原站地址&#xff1a;Go语言核心36讲_Golang_Go语言-极客时间 一、sync.WaitGroup和sync.Once 1. sync.WaitGroup 比通道更加适合实现一对多的 goroutine 协作流程。 2. WaitGroup类型有三个指针方法&#xff1a;Wait、Add和Done&#xff0c;以及内部有一个计数器。 (1) Wa…

【报错合集】完美解决“虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本”

文章目录 解决方案&#xff1a;更改设置的硬件版本 今天我需要将别人的虚拟机克隆到我的VMware Workstation上运行&#xff0c;结果发生了以下的错误&#xff1a; 刚开始以为是VMware Workstation的版本问题太低导致的&#xff0c;所以我删除了原来的那个版本&#xff0c;下载…

51cto已购买的视频怎么下载到本地

你是否曾在学习51CTO的精品课程时&#xff0c;希望可以随时随地无网络干扰地进行学习&#xff0c;或是想要将这些已购买的课程永久珍藏&#xff1f;今天&#xff0c;你的愿望将要实现。我们将向你揭示如何轻松地将已购买的51CTO视频下载到本地&#xff0c;让学习的路上再也没有…

企业邮箱域名是什么?怎么注册一个企业邮箱域名?

企业邮箱域名是什么&#xff1f;企业邮箱域名是企业申请的专属域名&#xff0c;绑定专属的邮箱域名&#xff0c;能够在发送邮件时提高品牌识别性、专业性和宣传效果。那么&#xff0c;我们该怎么注册一个企业邮箱域名呢&#xff1f;本文将为你详细介绍。 一、企业邮箱域名是什…

本地搭建各大直播平台录屏服务结合内网穿透工具实现远程管理录屏任务

文章目录 1. Bililive-go与套件下载1.1 获取ffmpeg1.2 获取Bililive-go1.3 配置套件 2. 本地运行测试3. 录屏设置演示4. 内网穿透工具下载安装5. 配置Bililive-go公网地址6. 配置固定公网地址 本文主要介绍如何在Windows系统电脑本地部署直播录屏利器Bililive-go&#xff0c;并…

内网环境安装使用DBeaver使用第一天

之前一直使用navicat&#xff0c;现在出于某种原因不让使用了&#xff0c;于是上手了这个工具&#xff0c;说实话&#xff0c;真的&#xff0c;但是必须要用。 首先安装的时候&#xff0c;必须要选择MySQL驱动&#xff0c;如果外网直接选择以后就可以下载了&#xff0c;内网需…

【MySQL复合查询】

文章目录 一、基本的使用案例二、多表查询三、自连接四、子查询4.1单行子查询4.2多行子查询in关键字all关键字any关键字 4.3多列子查询4.4 在from子句中使用子查询 解决多表问题的本质五、合并查询1.union2.union all 一、基本的使用案例 注明&#xff1a;以下案例使用的均为一…

Docker-compose部署TRX节点

1、编写Dockerfile rootubuntu:~# mkdir /data/docker-compose/trx -p rootubuntu:~# cd /data/docker-compose/trx/ rootubuntu:/data/docker-compose/trx# ls rootubuntu:/data/docker-compose/trx# vim Dockerfile rootubuntu:/data/docker-compose/trx# cat Dockerfile FR…

鲁棒控制问题描述

复杂的合成问题成为一个具有特殊结构控制器的设计问题。 H无穷范数&#xff08;H∞ norm&#xff09;&#xff1a;对于线性时不变&#xff08;LTI&#xff09;系统&#xff0c;H∞范数通常定义为系统频率响应的最大幅值。换句话说&#xff0c;它是系统传递函数在复平面单位圆上…

基于SSM的“图书仓储管理系统”的设计与实现(源码+数据库+文档)

基于SSM的“图书仓储管理系统”的设计与实现&#xff08;源码数据库文档) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 系统登录页面 人员管理信息页面 添加人员信息页…

公司申请增加公众号数量

一般可以申请多少个公众号&#xff1f;众所周知&#xff0c;在2013年前后&#xff0c;公众号申请是不限制数量的&#xff0c;后来企业开始限制申请50个&#xff0c;直到2018年的11月tx又发布&#xff0c;其中个人主体可申请公众号由2个调整为1个&#xff0c;企业主体由50个调整…

[C/C++] -- 搜索迷宫路径

DFS&#xff08;深度优先搜索&#xff09;和BFS&#xff08;广度优先搜索&#xff09;是两种常用的图遍历算法&#xff0c;它们在搜索图或树中的节点时有着不同的策略和特点。 深度优先搜索 (DFS): 在DFS中&#xff0c;从起始节点开始&#xff0c;沿着一条路径尽可能深地搜索&a…

Footprint Analytics 与 Core Chain 达成战略合作

​ 领先的区块链数据解决方案提供商 Footprint Analytics 与比特币驱动、EVM 兼容的 Layer 1 区块链 Core Chain 宣布达成战略合作。此次合作旨在将 Footprint Analytics 的前沿数据解决方案与 Core Chain 的区块链基础设施相结合&#xff0c;共同引领区块链领域的创新发展。 …

class常量池、运行时常量池和字符串常量池的关系

类常量池、运行时常量池和字符串常量池这三种常量池&#xff0c;在Java中扮演着不同但又相互关联的角色。理解它们之间的关系&#xff0c;有助于深入理解Java虚拟机&#xff08;JVM&#xff09;的内部工作机制&#xff0c;尤其是在类加载、内存分配和字符串处理方面。 类常量池…