AVL树原理以及插入代码讲解(插入操作画图~细节)

这篇具有很好参考价值的文章主要介绍了AVL树原理以及插入代码讲解(插入操作画图~细节)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

原理

平衡因子

AVL树的插入insert

1. 新节点插入较高左子树的左侧---左左:右单旋

2.新节点插入较高右子树的右侧---右右:左单旋

新节点插入较高左子树的右侧---左右:先左单旋再右单旋

 新节点插入较高右子树的左侧---右左:先右单旋再左单旋​编辑


原理

AVL 树是一种平衡搜索二叉树,得名于其发明者的名字( Adelson-Velskii 以及 Landis)。(可见名字长的好处,命名都能多占一个字母出来)。在搜索树的前提下平衡搜索二叉树还定义如下:

  1. 左右子树的高度差小于等于 1。
  2. 其每一个子树均为平衡二叉树。

我们以SGI版本AVL树

先来查看树中每个结点内容。

template<class K,class V>
	struct AVLTreeNode
	{
		AVLTreeNode* _left;//左子树
		AVLTreeNode* _right;//右子树
		AVLTreeNode* _parent;//父节点
		pair<K, V> _kv;//数据存储内容
		int _bf;//平衡因子

		AVLTreeNode(pair<K, V>kv=pair<K, V>())//默认赋值构造
			:_kv(kv)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			,_bf(0)
		{}
	};

左子树与右子树指针我们不做过多介绍。

我们存储AVL树是一种存储key_value数据的树,所以我们使用pair库中类型存储数据。

让我们看看平衡因子是什么:

平衡因子

某个结点的右子树的高度减去左子树的高度得到的差值。

AVL树原理以及插入代码讲解(插入操作画图~细节)

平衡因子在[-1,1]之间都是属于平衡的平衡搜索二叉树,每个结点都需要符合这个要求

在插入的过程中我们会破坏平衡,这个时候就需要对树结点进行转至操作。

AVL树的插入insert

和普通搜索二叉树一样,先要寻找插入的位置。

        bool Insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
			}
			else
			{
				Node* parent = nullptr;//保存上级指针,我们链接都是cur走到空指针创建结点给cur
                                       //但是这个新的结点需要被链接到树中,我们不可以对
                                       //nullptr进行访问上级的数据,所以我们必须留一个parent            
                                       //用来链接新的结点。
				Node* cur = _root;
				while (cur)
				{
					parent = cur;
					if (cur->_kv.first > kv.first)
					{
						cur = cur->_left;//新数据小于当前数据,向cur的左边走
					}
					else if (cur->_kv.first < kv.first)
					{
						cur = cur->_right;//新数据大于当前数据,向cur的右边走
					}
					else
					{
						return false;//数据相同退出,并返回false
					}
				}
				cur = new Node(kv);//cur到了nullptr,创建新的结点。
                
                //新结点链接到树
				if (parent->_kv.first <cur->_kv.first)
				{
					parent->_right = cur;
				}
				else
				{
					parent->_left = cur;
				}
				cur->_parent = parent;//处理新结点的_parent

插入结点后我们需要修改平衡因子,确保插入数据后我们的树依旧是AVL树。

AVL树原理以及插入代码讲解(插入操作画图~细节)

 更新平衡因子规则:

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

	if (parent->_right == cur)//根结点(praent)的右子树(cur)插入了一个结点当前根结点平衡因子++
	{
		++parent->_bf;
	}
	else /*if()  可以不写这个*///根结点(praent)的左子树(cur)插入了一个结点当前根结点平衡因子--
	{
		--parent->_bf;
	}
	if (parent->_bf == 0)//修改更新平衡因子后,查看当前平衡因子,为0代表根补齐了该根的低子树
                         //parent所在子树高度不变,不需要继续往上更新
	{
		break;
	}
	else if (abs(parent->_bf) == 1)//parent原本为0的平衡因子被改变,高度变高了,parent左右被
                                   //该节点插入稍稍改变平衡,需要继续往上更新平衡因子。
	{
		cur = parent;
		parent = parent->_parent;
	}
	else if (abs(parent->_bf) == 2)//说明parent插入前的平衡因子是1 or -1,已经平衡临界值,插
                                   //入变成2 or -2,打破平衡,parent所在子树需要旋转处理
	{
        //平衡被破坏。
	}
	else
	{
		std::cout << "AVL fail\n";
		assert(false);
	}
}

 这既是转至:AVL树原理以及插入代码讲解(插入操作画图~细节)

但是不平衡的情况有无数种。

AVL树原理以及插入代码讲解(插入操作画图~细节)

 我们向大佬学习,得出4个旋转大规律:

1. 新节点插入较高左子树的左侧---左左:右单旋

AVL树原理以及插入代码讲解(插入操作画图~细节)

h为子树高度。

当cur->bf==-1&&parent==-2是发生右单旋。

左左的意思是插入数据在parent的左子树的左子树上。

右单旋:以60结点为中心,向右旋转AVL树,允许h=0;

观察图像,我们发现就是改变链接关系,

AVL树原理以及插入代码讲解(插入操作画图~细节)

 这里有2个小细节点,

  1. b允许为空,所以b的parent操作前需要判断b是否为nullptr
  2. 如果60就是整棵AVL树的根就需要改变root指向30,并且将30的parent置空

在旋转结束以后将cur与parent结点的bf置为0

完整代码:

		void RotateR(Node* parent)
		{
			Node* subL = parent->_right;
			Node* subLR = subL->_right;

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

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

2.新节点插入较高右子树的右侧---右右:左单旋

AVL树原理以及插入代码讲解(插入操作画图~细节)

原理与右单旋一样。

查看代码

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

	parent->_right = subRL;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	Node* pparent = parent->_parent;

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

新节点插入较高左子树的右侧---左右:先左单旋再右单旋

什么意思呢?就是插入到了parent->left->right子树上

AVL树原理以及插入代码讲解(插入操作画图~细节)

 如果这样的情况只用右单旋。会发生什么呢?

AVL树原理以及插入代码讲解(插入操作画图~细节)

该破坏平衡依旧破坏平衡,用左旋就是I变2一样的。

所以这时候我们需要左右旋一起用。

AVL树原理以及插入代码讲解(插入操作画图~细节)

我们将30结点的右子树再一次拆分->40根结点+左右子树。

我们需要先以30结点左旋该子树。

AVL树原理以及插入代码讲解(插入操作画图~细节)

 在根据60结点做右旋旋转,我们把40的左子树看成一个整体

AVL树原理以及插入代码讲解(插入操作画图~细节)

 然后重新定义40、60、30的平衡因子。

这里我们定义平衡因子又有讲究,我们要观察插入后旋转前40结点的平衡因子。

三种情况:

旋转前40->bf==1时,在旋转完毕后30->bf=-1、60->bf=0、40->bf=0;

AVL树原理以及插入代码讲解(插入操作画图~细节)

旋转前40->bf==-1时,在旋转完毕后30->bf=0、60->bf=1、40->bf=0;

AVL树原理以及插入代码讲解(插入操作画图~细节)

 还有一种特殊的情况40->bf==0;30->bf=0、60->bf=0、40->bf=0;

AVL树原理以及插入代码讲解(插入操作画图~细节)

左右旋代码代码:

        void RotateLR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			RotateL(subL);
			RotateR(parent);
			subLR->_bf = 0;
			if (bf == 1)
			{
				parent->_bf = 0;
				subL->_bf = -1;
			}
			else if (bf==-1)
			{
				parent->_bf = 1;
				subL->_bf = 0;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				subL->_bf = 0;
			}
			else
			{
				assert(false);
			}
		}

 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

AVL树原理以及插入代码讲解(插入操作画图~细节)

 重新赋值bf与左右:先左单旋再右单旋规则一样

旋转前40->bf==-1时,在旋转完毕后30->bf=0、30->bf=1、40->bf=0;

AVL树原理以及插入代码讲解(插入操作画图~细节)

 旋转前40->bf==1时,在旋转完毕后30->bf=-1、30->bf=0、40->bf=0;

AVL树原理以及插入代码讲解(插入操作画图~细节)

  旋转前40->bf==0时,在旋转完毕后30->bf=-1、30->bf=0、40->bf=0

AVL树原理以及插入代码讲解(插入操作画图~细节)

右左旋代码:

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

 AVLinsert完整代码:

template<class K, class V>
	struct AVLTree
	{
		typedef AVLTreeNode<K, V> Node;
	public:
		bool Insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
			}
			else
			{
				Node* parent = nullptr;
				Node* cur = _root;
				while (cur)
				{
					parent = cur;
					if (cur->_kv.first > kv.first)
					{
						cur = cur->_left;
					}
					else if (cur->_kv.first < kv.first)
					{
						cur = cur->_right;
					}
					else
					{
						return false;
					}
				}
				cur = new Node(kv);
				if (parent->_kv.first <cur->_kv.first)
				{
					parent->_right = cur;
				}
				else
				{
					parent->_left = cur;
				}
				cur->_parent = parent;
				while (parent)
				{

					if (parent->_right == cur)
					{
						++parent->_bf;
					}
					else 
					{
						--parent->_bf;
					}
					if (parent->_bf == 0)
					{
						break;
					}
					else if (abs(parent->_bf) == 1)
					{
						cur = parent;
						parent = parent->_parent;
					}
					else if (abs(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)
						{
							RotateLR(parent);
						}
						else if (parent->_bf == 2 && cur->_bf == -1)
						{
							RotateRL(parent);
						}
						break;
					}
					else
					{
						std::cout << "AVL fail\n";
						assert(false);
					}
				}
				
				return true;
			}
		}
	private:
		void RotateLR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			RotateL(subL);
			RotateR(parent);
			subLR->_bf = 0;
			if (bf == 1)
			{
				parent->_bf = 0;
				subL->_bf = -1;
			}
			else if (bf==-1)
			{
				parent->_bf = 1;
				subL->_bf = 0;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				subL->_bf = 0;
			}
			else
			{
                printf("bf is fail!!\n");
				assert(false);
			}
		}

		void RotateRL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL=subR->_left;
			int bf = subRL->_bf;
			RotateR(subR);
			RotateL(parent);
			subRL->_bf = 0;
			if (bf == 1)
			{
				parent = -1;
				subR = 0;
			}
			else if(bf==-1)
			{
				parent = 0;
				subR = 1;
			}
			else if (bf == 0)
			{
				parent = 0;
				subR = 0;
			}
			else
			{
                printf("bf is fail!!\n");
				assert(false);
			}

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

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

		void RotateR(Node* parent)
		{
			Node* subL = parent->_right;
			Node* subLR = subL->_right;

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

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

		Node* _root;
	};
}

 谢谢!!文章来源地址https://www.toymoban.com/news/detail-491502.html

到了这里,关于AVL树原理以及插入代码讲解(插入操作画图~细节)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32 OLED 显示原理的讲解以及OLED显示汉字与图片的代码

    本文主要涉及OLED显示原理的讲解以及OLED显示汉字与图片的代码。 OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display,OELD) 。 OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板

    2024年02月04日
    浏览(42)
  • 【AI 实战】Text Processing and Word Embedding 文本处理以及词嵌入原理和代码实例讲解

    https://www.youtube.com/watch?v=6_2_2CPB97s 文本处理是自然语言处理(NLP)的一个重要部分,它涉及到将文本数据转化为可以被机器学习算法理解的格式。这个过程通常包括以下步骤: 文本清洗:这是文本处理的第一步,主要是去除文本中的噪声,如特殊字符、数字、标点符号等。

    2024年02月01日
    浏览(49)
  • 用通俗易懂的方式讲解:一文讲透主流大语言模型的技术原理细节

    大家好,今天的文章分享三个方面的内容: 1、比较 LLaMA、ChatGLM、Falcon 等大语言模型的细节:tokenizer、位置编码、Layer Normalization、激活函数等。 2、大语言模型的分布式训练技术:数据并行、张量模型并行、流水线并行、3D 并行、零冗余优化器 ZeRO、CPU 卸载技术 ZeRo-offload、

    2024年01月16日
    浏览(55)
  • C语言数据结构(链表概念讲解和插入操作)

    本篇文章带大家正式的来学习数据结构,数据结构是学习操作系统,和深入C语言必不可少的,所以这篇文章开始带大家学习数据结构的知识。 链表(Linked List)是一种常见的数据结构,用于存储和组织数据元素。它由一系列节点(Node)组成,每个节点包含存储的数据(或称

    2024年02月06日
    浏览(37)
  • C++ AVL树(四种旋转,插入)

    AVL树又称高度平衡二叉搜索树,它的高度接近log[2]N(以2为底N的对数),整棵树的形状接近完全二叉树 增删查改的时间复杂度是O(log[2]N) 本节我们实现的是Key-Value模型的AVL树 这里我们的AVL树节点比起普通的二叉树的节点来说多了两个成员 第一个是平衡因子,这里我们定义的平衡因子

    2024年02月04日
    浏览(38)
  • [linux kernel]slub内存管理分析(4) 细节操作以及安全加固

    前情回顾 关于slab几个结构体的关系和初始化和内存分配的逻辑请见: [linux kernel]slub内存管理分析(0) 导读 [linux kernel]slub内存管理分析(1) 结构体 [linux kernel]slub内存管理分析(2) 初始化 [linux kernel]slub内存管理分析(2.5) slab重用 [linux kernel]slub内存管理分析(3) kmalloc 描述方法约定

    2023年04月09日
    浏览(50)
  • 【数据结构】AVL树的插入与验证

    普通的二叉搜索树在极端情况下会 退化成类似链表 的形状,从而使 查找的效率降低至O(N) 。 在此基础上,苏联与以色列数学家 A delson- V elskii 与 苏联数学家 L andis,发明出了 AVL树或者说平衡二叉搜索树。 注:第一张——Adelson-Velskii(1922-2014) ,第二张——Landis(1921——

    2024年02月09日
    浏览(35)
  • 堆的实现,画图和代码分析建堆,堆排序,时间复杂度以及TOP-K问题

    如果有一个关键码的集合K = {k 0 ,k 1 ,k 2 ,…,k n-1 },把它的所有元素按 完全二叉树 的顺序存储方式存储在一个一维数组中,并满足:k i =k 2*i+1 且 =k 2*i+2 (k i =k 2*i+1 且 =k 2*i+2 ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫

    2024年02月04日
    浏览(39)
  • 【手撕Transformer】Transformer输入输出细节以及代码实现(pytorch)

    数据从输入到encoder到decoder输出这个过程中的流程(以机器翻译为例子): 对于机器翻译来说,一个样本是由原始句子和翻译后的句子组成的。比如原始句子是: “我爱机器学习”,那么翻译后是 ’i love machine learning‘。 则该一个样本就是由“我爱机器学习”和 “i love ma

    2023年04月22日
    浏览(44)
  • 【C++】AVL树和红黑树的插入

    时间过的好快,我也修炼到红黑树了 人世这一遭,何其短暂而漫长啊…… 1. 虽然二叉搜索树的搜索效率很高,当搜索树接近满二叉树时,搜索效率可以达到logN,但是如果数据是有序的,则二叉搜索树会退化为单支树,搜索效率和普通的序列式容器相同了就,所以在搜索树的

    2023年04月08日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包