C++学习笔记:AVL树

news2024/12/22 2:25:00

AVL树

  • 什么是AVL树?
  • AVL树节点的定义
  • AVL树的插入
    • 平衡因子调整
    • 旋转调整
    • 左旋转
    • 右旋转
    • 左右双旋
    • 右左双旋
  • AVL树完整代码实现

什么是AVL树?

AVL是1962年,两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis 为了解决如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下的问题 ,因此发明了一种特殊的二叉搜索树,并以他们的名字命名为AVL树.
相比于普通的二叉树来说,AVL树的节点定义多了一个平衡因子:
平衡因子 = 右子树的高度 - 左子树的高度
因为AVL树需要通过平衡因子来确保它的特性,而AVL树的特性有以下几点:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
    在这里插入图片描述

因为有上面两点的限制,当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过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树节点的定义

因为在AVL树中,若插入一些节点需要对AVL树的节点进行旋转调整等,在进行旋转调整的时候需要记录当前节点父节点的位置,因此在AVL树的节点定义如下:

template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& data = T())
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _bf(0)
	{}
		
	AVLTreeNode<T>* _left;  
	AVLTreeNode<T>* _right;
	AVLTreeNode<T>* _parent;
	T _data;
	int _bf;   // 节点的平衡因子
};

平衡因子_bf应初始化为0

AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。
但是AVL树插入值的时候会有平衡因子的改变,那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

平衡因子调整

  • 新增节点在父节点的左边 bf–

  • 新增节点在父节点的右边 bf++
    在更新后:

  • 插入节点之前,父亲的bf 为 1 或者 -1 ,插入节点在低的子树那边,那么更新后父亲的 bf == 0 ,左右子树高度不变,插入结束
    在这里插入图片描述

  • 插入前父节点的bf = 0,更新后父亲的 bf == 1 || bf == -1 ,父亲所在子树高度变了,需要继续往上更新
    在这里插入图片描述

  • 父亲节点bf更新后为 2 或者 -2,则说明需要进行调整
    在这里插入图片描述

新增节点可能会影响祖先,因此插入节点后,需要查看子树的高度是否变化:

  • 若子树高度不变,就不会影响祖先
  • 若子树高度改变,就会影响祖先

如下图,虽然增加了一个节点,但是并不影响节点 7 这颗子树的高度,因此祖先节点5就不会改变
在这里插入图片描述
但是出现下面这种情况,就需要进行调整了
在这里插入图片描述
因为新插入的节点影响了父节点 9 的高度,进而影响了 8 节点的平衡因子变成了2
这种时候就需要进行调整了

旋转调整

AVL树正式因为有旋转调整这一必杀利器,因此才能保证它一直是AVL树,而旋转调整又分为:左旋转,右旋转,左右旋转,右左旋转
四种旋转分别对应4种不同的情况接下来看看这些旋转的条件及思想
而旋转的必要条件就是父亲节点的bf == -2 || bf == 2:parent->_bf == -2 || parent->_bf == 2

左旋转

左旋转的条件: parent->_bf == 2 && cur->_bf == 1
如图:
简易版:
在这里插入图片描述

综合版
在这里插入图片描述

代码实现:

// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;

		Node* pparent = parent->_parent;

		parent->_parent = subR;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			subR->_parent = pparent;
		}
		parent->_bf = subR->_bf = 0;

	}

右旋转

右旋转的条件:parent->_bf == -2 && cur->_bf == -1
思想:
简易版:
在这里插入图片描述
综合版
在这里插入图片描述

左右双旋

左右双旋的条件:parent->_bf == -2 && cur->_bf == 1
思想:
在这里插入图片描述
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。
代码实现

// 左右双旋
	void 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 = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == -1) //左子树的高度较高
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋

右左双旋的条件:parent->_bf == 2 && cur->_bf == -1
思想:
在这里插入图片描述
右左双旋代码实现:

// 右左双旋
	void 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 = 0;
			subR->_bf = 0;
			subRL->_bf = 1;
		}
		else if (bf == 1)
		{
			//subRL 的右子树新增节点 
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

AVL树完整代码实现

此代码种包含了AVL树平衡的判断和中序遍历

#pragma once
#include<iostream>
#include<assert.h>

using namespace std;

template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& data = T())
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _bf(0)
	{}
		
	AVLTreeNode<T>* _left;
	AVLTreeNode<T>* _right;
	AVLTreeNode<T>* _parent;
	T _data;
	int _bf;   // 节点的平衡因子
};


// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;
public:

	// 在AVL树中插入值为data的节点
	bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data > data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_data < data)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(data);
		if (parent->_data > data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//判断平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == -1 || parent->_bf == 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;
	}

	bool Isbalance()
	{
		return _Isbalance(_root);
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

private:

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

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

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

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

	}
	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;

		Node* pparent = parent->_parent;

		parent->_parent = subR;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			subR->_parent = pparent;
		}
		parent->_bf = subR->_bf = 0;

	}
	// 右左双旋
	void 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 = 0;
			subR->_bf = 0;
			subRL->_bf = 1;
		}
		else if (bf == 1)
		{
			//subRL 的右子树新增节点 
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	// 左右双旋
	void 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 = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == -1) //左子树的高度较高
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	//AVL树的高度
	int _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);

		if (root->_bf != rightHeight - leftHeight)
		{
			cout << root->_data << "节点bf异常" << endl;
		}

		return abs(leftHeight - rightHeight) < 2
			&& _Isbalance(root->_left)
			&& _Isbalance(root->_right);

	}

	

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_data << " ";
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};

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

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

相关文章

河北省国际光伏展览会

能源是国民经济发展的重要基础之一。随着国民经济的发展&#xff0c;能源的缺口增大&#xff0c;能源安全及能源在国民经济中的地位越显突出。我国是世界上少数几个能源结构以煤为INVITATION主的国家之一&#xff0c;也是世界上最大的煤炭消费国&#xff0c;燃煤造成的环境污染…

鸿蒙系统:揭秘前端开发的新机遇

众所周知&#xff0c;华为开发者大会2023&#xff0c;宣布不再兼容安卓&#xff0c;同时宣布了“鸿飞计划”&#xff0c;欲与iOS、安卓在市场三分天下&#xff0c;这对中国国产操作系统而言&#xff0c;具有划时代的意义。 鸿蒙应用开发的兴起&发展 鸿蒙操作系统是华为自…

力扣102. 二叉树的层序遍历

Problem: 102. 二叉树的层序遍历 文章目录 题目描述思路复杂度Code 题目描述 思路 直接套用树的层序遍历模板&#xff0c;同时将每一层的节点存入一个数组中&#xff0c;并将其存入最终的二维结果数组中 复杂度 时间复杂度: O ( n ) O(n) O(n)&#xff1b;其中 n n n为树节点的…

基于Vue的体育汇App设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 核心技术的理论与分析 3 1.1 客户端技术 3 1.1.1 Vue.js框架 3 1.1.2 Vue.js路由管理 3 1.1.3 Vuex状态管理 3 1.1.4 MVVM开发模式 4 1.1.5 Vant组件库 5 1.2 服务端技术 5 1.2.1 Node.js 5 1.2.2 Egg.js框架 5 1.3 数据库技术 6 1.4 本章…

牛客网——美团2024届秋招笔试第三场编程真题

牛客网——美团2024届秋招笔试第三场编程真题 &#x1f60d;&#x1f60d;&#x1f60d; 相知&#x1f64c;&#x1f64c;&#x1f64c; 相识&#x1f622;&#x1f622;&#x1f622; 开始刷题1. 平均数为k的最长连续子数组2. 小球投盒3. 小红结账4. 小美的游戏5. 小美种果树6…

并查集(蓝桥杯 C++ 题目 代码 注解)

目录 介绍&#xff1a; 模板&#xff1a; 题目一&#xff08;合根植物&#xff09;&#xff1a; 代码&#xff1a; 题目二&#xff08;蓝桥幼儿园&#xff09;&#xff1a; 代码&#xff1a; 题目三&#xff08;小猪存钱罐&#xff09;&#xff1a; 代码&#xff1a; …

设计模式-行为型模式-职责链模式

在软件系统运行时&#xff0c;对象并不是孤立存在的&#xff0c;它们可以通过相互通信协作完成某些功能&#xff0c;一个对象在运行时也将影响到其他对象的运行。行为型模式&#xff08;Behavioral Pattern&#xff09;关注系统中对象之间的交互&#xff0c;研究系统在运行时对…

应用内存分析

adb shell cat /proc/meminfo 命令&#xff0c;查看剩余的空间还有多少&#xff0c;通常的系统剩余空间是: MemFree cached 1.衡量标准参数 VSS(Virtual set Size)&#xff1a;虚拟内存耗用&#xff08;包括共享库占用的内存&#xff09; RSS(Resident set Size)&#xff1…

js判断页面是否是在iframe里面

文章目录 一、前言1.1、_blank跳转1.2、_self跳转 二、方法2.1、判断子项目是否是在iframe内部2.2、实现_self跳转 三、最后 一、前言 上面是父前端项目里的iframe加载了子前端项目的页面。此时如果点击子项目的内容&#xff0c;如果要进行父项目浏览器页面跳转&#xff0c;可以…

社区医院智慧管理:Java+SpringBoot新实践

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

斐讯N1 刷coreelec 笔记

1.下载恩山的镜像 下载好后不需要刷优盘 这个很方便&#xff0c;可以勾选擦除flash &#xff08;如果第一次装&#xff09; 升级可以不用勾选 详细使用参考恩山大佬的描述 2.下载插件 想装openwrt 发现镜像里面 coreelec-addons 挂了&#xff0c;研究了好长时间可以 去githu…

智慧城市中的数字孪生:构建城市管理的未来框架

目录 一、引言 二、数字孪生技术概述 三、数字孪生技术在智慧城市中的应用 1、实时监测与预警 2、模拟与优化 3、智能化决策 4、协同与共享 四、数字孪生技术构建城市管理的未来框架的价值 1、提高管理效率 2、优化资源配置 3、提升公共服务水平 4、增强应对突发事…

【学习笔记】微信运营工具

办公工具 在线 http://uzer.meMindMaster即刻&#xff08;APP&#xff09;收趣&#xff08;APP&#xff09;MindMaster&#xff08;app&#xff09; 安装 文字工具 Mega Emoji 文字云 石墨文档 giftools 音频工具 变声实验室&#xff08;APP&#xff09; 录音APP&am…

【完美实现】VITE + VUE3 + SVG图片解析+element-plus开发环境初始化(基于macos)

一、最终效果 废话少说&#xff0c;直接上效果 这是我的初始化程序提供的页面&#xff0c;在这个页面上实现了一下几个功能&#xff1a; 1、vite初始化之后的路由安装和初始化&#xff1b; 2、标准SVG的解析&#xff0c;并可调整大小、颜色&#xff1b; 3、element-plus的安…

不允许你不知道Python函数的返回值

函数可以通过return一次性返回多个数据&#xff0c;返回的数据以元组的形式保存。函数中若出现了return&#xff0c;return后面的程序不会执行。若函数中需要将某些数据返回&#xff0c;使用关键字return。若return后面没有返回任何数据&#xff0c;仅仅表示结束程序&#xff0…

lqb省赛日志[2/37]

一只小蒟蒻备考蓝桥杯的日志 文章目录 笔记&#xff01;lqb不能用to_string和atoi历史遗留问题1 刷题心得小结 笔记 &#xff01;lqb不能用to_string和atoi 有替代方法 参考 不使用C 11的整数转字符串 C语言提供了一种方法。 #include sstream &#xff08;我没学&#xff0…

高级语言讲义2011计专(仅高级语言部分)

1.某公司采用公用电话传递数据&#xff0c;数据是四位的整数&#xff0c;为了安全。在传递过程中数据是加密的。加密规则如下&#xff0c;每位数字加5,然后用和除以10的余数代替该数字&#xff0c;再将第一位和第四位交换&#xff0c;第二位和第三位交换&#xff0c;编一程序&a…

Redis的介绍与使用

文章目录 Redis简介安装RedisRedis常用命令全局命令String类型数据Hash哈希类型数据List列表类型数据Set集合类型数据SortedSet有序集合类型数据 一些选择题一些选择题 Redis简介 Redis是一款基于键值对的NoSQL数据库&#xff0c;它的值支持多种数据结构&#xff1a; 字符串(s…

Gartner对未来5年全球信息安全和风险管理市场的预测分析:影响市场的四大因素及对相关产品市场的影响

到 2023 年&#xff0c;信息安全和风险管理市场的最终用户支出将增长至 1850 亿美元&#xff08;现价美元&#xff09;&#xff0c;货币稳定增长率为 13.4%。到2027年&#xff0c;市场规模将达到2870亿美元&#xff0c;按固定汇率计算&#xff0c;2022年至2027年的复合年增长率…

使用MyBatis完成CRUD

Insert Map <mapper namespace"car"><!--insert sql&#xff1a;保存一个汽车信息--><insert id"insertCar">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,102,丰田mirai,40.30,2014-10-05,氢能…