【C++】AVL树(高度平衡二叉树)

news2024/10/6 1:34:31

AVL树

      • 概念
      • AVL树节点定义
      • AVL树节点插入
        • 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 log_2 n log2n)。

在这里插入图片描述

AVL树节点定义

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;// 该节点的左孩子
	AVLTreeNode<K, V>* _right;// 该节点的右孩子
	AVLTreeNode<K, V>* _parent;// 该节点的父节点

	pair<K, V> _kv;// 该节点的平衡因子
	int _bf;

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

AVL树节点插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子

在这里插入图片描述

更新平衡因子的规则:
1、新增在右,parent->bf++; 新增在左,parent->bf–:
2、更新后,parent->bf == 1 r -1,说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,parent高度变了,需要继续往上更新
3、更新后,parent->bf == 0,说明parent插入前的平衡因子是1 r -1,说明左右子树一边高-边低,插入后两边一样高,插入填上了矮了那边,parent所在子树高度不变,不需要继续往上更新
4更新后,parent->bf == 2 r -2,说明parent插入前的平衡因子是1 or -1,已经平衡临界值,插入变成2 or -2,打破平衡,parent所在子树需要旋转处理。
5更新后,parent->bf > 2 r< -2的值,不可能,如果存在,则说明插入前就不是AVL树,需要去检查之前操作的问题.

AVL树四种旋转情况

左单旋

在这里插入图片描述

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		Node* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		//1.parent是整棵树的根
		//2.parent是子树的根
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
		subR->_bf = parent->_bf = 0;
	}
右单旋

在这里插入图片描述

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

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

		parent->_bf = subL->_bf = 0;
	}
先左单旋再右单旋

在这里插入图片描述

void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;
		RotateL(parent->_left);
		RotateR(parent);

		//旋转完后的根节点
		subLR->_bf = 0;
		if (bf == 1)
		{
 			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subL->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
先右单旋后左单旋

在这里插入图片描述

	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);
		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subR->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

元素的插入及控制平衡

typedef AVLTreeNode<K, V> Node;
	bool Insert(const pair<K, V>& kv)
	{
		//如果当前树为空直接设置节点
		if (_root == NULL)
		{
			_root = new Node(kv);
			return true;
		}
		//需要有指针记录上一个移动位置
		Node* cur = _root;
		Node* parent = nullptr;
		//寻找合适位置插入
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//直接插入节点并设置它的指向
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		//控制平衡
		//1.更新平衡因子
		while (parent)
		{
			if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (abs(parent->_bf) == 1)
			{	//如果为1整体向上移动再次调增平衡
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				//说明parent所在子树已经不平衡了,需要旋转处理
				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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					//预防调整出错情况
					assert(false);
				}
				break;
			}
			else
			{
			//预防调整出错情况
				assert(false);
			}
		}
		return true;

	}

判断最后节点是否平衡

	//判断是否平衡
bool _IsBanlance(Node* root)
	{
		if (root == NULL)
		{
			return true;
		}

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		if (rightH - leftH != root->_bf)
		{
			cout << root->_kv.first << "节点平衡因子异常" << endl;
			return false;
		}

		return abs(leftH - rightH) < 2
			&& _IsBanlance(root->_left)
			&& _IsBanlance(root->_right);
	}

	//计算它的最大高度
	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return max(leftH, rightH) + 1;
	}

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

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

相关文章

LeetCode面试经典150题(day 3)

169. 多数元素 难度&#xff1a;简单 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1&#xff1a; 输入&#xff1a;nums …

4.19 20

服务端没有 listen&#xff0c;客户端发起连接建立&#xff0c;会发生什么&#xff1f; 服务端如果只 bind 了 IP 地址和端口&#xff0c;而没有调用 listen 的话&#xff0c;然后客户端对服务端发起了连接建立&#xff0c;服务端会回 RST 报文。 没有 listen&#x…

【HSPCIE仿真】输入网表文件(1)基本内容和基本规则

输入网表文件 1. 输入网表文件基本内容2. 输入网表文件示例3. 一些基本规则4. 数值表示5. 压缩文件格式的读取6. 参数和表达式 从HSPICE的仿真流程看&#xff0c;出去初始化配置过程&#xff0c;真正的仿真是从输入网表文件开始的。 HSPICE 根据输入网表文件&#xff08; inpu…

Springboot 实践(11)Spring Cloud 与zuul 路由配置与应用

前文讲解springboot cloud与consul的服务注册与发现&#xff0c;为多微服务程序互联互通做了准备&#xff1b;那么&#xff0c;各个微服务之间是直接调用IP或者域名实现接口调用&#xff0c;还是有其他方式呢&#xff1f;其实&#xff0c;可以通过搭建zuul服务&#xff0c;实现…

左耳朵耗子:从一次经历谈 TIME_WAIT 的那些事

原文地址&#xff1a;https://coolshell.cn/articles/22263.html 今天来讲一讲TCP 的 TIME_WAIT 的问题。这个问题尽人皆知&#xff0c;不过&#xff0c;这次遇到的是不太一样的场景&#xff0c;前两天也解决了&#xff0c;正好写篇文章&#xff0c;顺便把 TIME_WAIT 的那些事都…

STM32入门学习之TFT_LCD显示

1.TFT_LCD简介&#xff1a;薄膜晶体管液晶显示器TFT_LCD(Thin Film Transistor-Liquid Crystal Display)在液晶显示屏的每一个像素上都设置有一个薄膜晶体管&#xff08;TFT&#xff09;&#xff0c;能够有效的克服非选择时的串扰&#xff0c;使显示屏的静态特性与扫描线数无关…

登录校验-Filter-详解

目录 执行流程 拦截路径 过滤器链 小结 执行流程 过滤器Filter拦截到请求之后&#xff0c;首先执行方放行之前的逻辑&#xff0c;然后执行放行操作&#xff08;doFilter&#xff09;&#xff0c;然后会访问对应的Web资源&#xff08;对应的Controller类&#xff09;&#…

【C++】进一步认识模板

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录 前言一、非类型模板参数二、模板的特…

Java学数据结构(3)——树Tree B树 红黑树 Java标准库中的集合Set与映射Map 使用多个映射Map的案例

目录 引出B树插入insert删除remove 红黑树(red black tree)自底向上的插入自顶向下红黑树自顶向下的删除 标准库中的集合Set与映射Map关于Set接口关于Map接口TreeSet类和TreeMap类的实现使用多个映射Map&#xff1a;一个词典的案例方案一&#xff1a;使用一个Map对象方案二&…

Leetcode.75 颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sort 函数的情况下解决这…

[管理与领导-50]:IT基层管理者 - 8项核心技能 - 5 - 沟通是润滑剂

目录 前言&#xff1a; 一、什么是沟通 1.1 定义 1.2 沟通模型 1.3 沟通的六层次模型 1.4 为什么需要沟通 二、沟通的五维度 三、沟通的原则 3.1 以终为始 3.2 双赢思维&#xff1a;人们只会做对自己有利的事 3.3 牵善的思维 四、沟通的过程 五、沟通技巧 六、深…

统计Mysql库中每个表的总行数,解决table_rows不准确问题

1、拼接SQL selectsubstring( GROUP_CONCAT(a.sf SEPARATOR ),1,length(GROUP_CONCAT(a.sf SEPARATOR ))-10) as sql_str from( select concat(select ", TABLE_name , ", count(*) as row_num from , TABLE_SCHEMA, .,TABLE_name, union all ) as sf frominformat…

matlab使用教程(25)—常微分方程(ODE)选项

1.ODE 选项摘要 解算 ODE 经常要求微调参数、调整误差容限或向求解器传递附加信息。本主题说明如何指定选项以及每个选项与哪些微分方程求解器兼容。 1.1 选项语法 使用 odeset 函数创建 options 结构体&#xff0c;然后将其作为第四个输入参数传递给求解器。例如&#xff0…

支付宝的支付

对于前端的入门学习的人员来说&#xff0c;支付宝提供的沙箱环境&#xff0c;可以让你体验支付的整个流程。 一、沙箱环境 沙箱&#xff08;又叫沙盘&#xff09;环境是用于开发者测试的模拟环境&#xff0c;中间发生任何行为都是虚拟的&#xff0c;如支付。 二、技术选型 支…

一文800字从0到1运用工具Postman快速导出python接口测试脚本

Postman的脚本可以导出多种语言的脚本&#xff0c;方便二次维护开发。 Python的requests库&#xff0c;支持python2和python3&#xff0c;用于发送http/https请求 使用unittest进行接口自动化测试 一、环境准备 1、安装python&#xff08;使用python2或3都可以&#xff09; …

前端组件库造轮子——Input组件开发教程

前端组件库造轮子——Input组件开发教程 前言 本系列旨在记录前端组件库开发经验&#xff0c;我们的组件库项目目前已在Github开源&#xff0c;下面是项目的部分组件。文章会详细介绍一些造组件库轮子的技巧并且最后会给出完整的演示demo。 文章旨在总结经验&#xff0c;开源…

基于Java+SpringBoot+Vue前后端分离科研工作量管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

c语言练习题33: 判断回⽂字符串

判断回⽂字符串&#xff1a; 题目&#xff1a; 输⼊⼀个字符串&#xff0c;判断这个字符串是否是回⽂字符串&#xff08;字符串的⻓度⼩于等于30&#xff0c;字符串不包含空 格&#xff09;&#xff0c;如果是回⽂字符串输出Yes&#xff0c;如果不是回⽂字符串输出No。 //回…

Hugging Face Transformer 的APIs应用实例

拥抱面变压器 API 简要摘要 一、说明 Hugging Face 的变压器库提供了广泛的 API&#xff0c;可用于处理各种 NLP 任务的预训练变压器模型。在本教程中&#xff0c;我们将探讨主要 API 并提供示例来帮助你了解它们的用法。 二、导入模型 1. 分词器接口&#xff1a; 分词器 AP…

浅谈分布式共识算法概念与演进

分布式共识是指在分布式系统中&#xff0c;多个节点之间达成共识的过程。 分布式共识的意义在于确保分布式系统中各个节点之间的数据一致性。通过分布式共识算法&#xff0c;可以使得多个节点针对某个状态达成一致&#xff0c;从而保证系统中各个节点之间的数据一致性。这对于…