平衡二叉搜索树/AVL树

news2024/12/23 12:30:13

VAL树的特性

  1. 左右子树高度差的绝对值不超过1。(即左右子树高度差取值为-1,0,1)
  2. 且左右子树均为VAL树
  3. 右子树的值大于左子树的值

在搜索二叉树中我们提及了搜索二叉树的退化问题。

当有序(升序或降序)地插入时,搜索二叉树就会出现下图的情况,搜索次数从树的高度次退化成n次。而平衡搜索二叉树就解决了这一问题。

 

平衡方法

在解决方案中,左右子树的差值被定为平衡因子,所以上述例子用平衡二叉搜索树的角度来看如下图。此时出现了不被允许的2和-2,意味着平衡被打破,需要进行调整平衡。

插入时平衡因子的变化逻辑

以下绿色方框代表一棵子树,h为其高度,且h>=0,具体节点上方的数字为平衡因子。

因为插入节点,导致60这个节点的左右子树失衡了。

 

        如图我们可以看到,根据插入位置的不同,其父节点的平衡因子的变化也不同。因为此处我使用的平衡因子 = 右子树高度 - 左子树高度。所以当插入到右子树时,父节点平衡因子+1,插入到左子树时,父节点平衡因子-1。

了解完插入时平衡因子的变化,我们再来了解一下变化的两种情况。

(1)插入后父节点平衡因子变为1或-1的,那么其原来的平衡因子是0,意味着该父节点原本平衡,插入节点后打破了平衡使该父节点为根的整棵子树高度产生变化,此时平衡因子需要继续向上更新,于是60这个节点也受其影响改变了,此时出现了不被允许的失衡,此时就要旋转调整平衡。

(2)第二种情况比较简单,插入后父节点平衡因子变为0,意味着其原本有一些失衡,但还在允许范围内,但插入后完全平衡了,此时无需再继续更新,因为高度没有改变。

总结:亚健康(-1、1)到健康(0)不用管,反之健康到亚健康要去向上更新平衡因子,因为其父节点可能直接就生病了。

// 在AVL树中插入值为data的节点
		bool Insert(const T& data)
		{
			if (_pRoot == nullptr)
			{
				_pRoot = new Node(data);
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _pRoot;

			while (cur)
			{
				if (data > cur->_data)
				{
					parent = cur;
					cur = cur->_pRight;
				}
				else if (data < cur->_data)
				{
					parent = cur;
					cur = cur->_pLeft;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(data);
			if (parent->_data < data)
			{
				parent->_pRight = cur;
			}
			else
			{
				parent->_pLeft = cur;
			}
			cur->_pParent = parent;


			//调整平衡因子
			while (parent)
			{
				if (cur == parent->_pLeft)
				{
					parent->_bf--;
				}
				else
				{
					parent->_bf++;
				}

				//插入后恰好平衡了,跳出循环
				if (parent->_bf == 0)
				{
					break;
				}    //高度出现变化但还未失衡,向上走进行判断
				else if (parent->_bf == 1 || parent->_bf == -1)
				{
					cur = parent;
					parent = parent->_pParent;
				}	//已经失衡需要进行调整
				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);
					}
					break;
				}
				else
				{
					//理论而言不可能出现这个情况(此时小于-2或大于2)
					assert(false);
				}
			}
			return true;
		}

旋转调整 

有了前面插入时平衡因子变化逻辑的铺垫,失衡时的情况就简化成了:

失衡节点无非是-2或2,造就失衡节点的子节点无非是-1,1(产生高度变化)。排列组合后就出现了四种情况。于是出现四种旋转方式与其一一对应。

 右单旋

此时的失衡情况为:左左(插入在失衡节点的左节点的左子树中),此时失衡节点平衡因子为-2,产生高度变化的子节点平衡因子为-1。

所以右单旋适用于“左左”,即parent的平衡因子为-2,高度变化的子节点的平衡因子为-1。

 右单旋过程:
 

可以看到,调整后30和60这两个节点的平衡因子都为0了。

那么为什么要这么做? 

现在我们先来分析一下他们的大小关系。

比较大小后发现,60非常适合当作30的右子树根节点,同时又做b子树的父节点,因为60>b子树任意值>30。

所以右单旋就是将失衡节点及其右子树下压,当作高度出现变化的节点的右子树,再连接上b子树和60这个节点,就使30的左右子树高度一致了。左子树高度是h+1(1为插入的节点),右边也是h+1(1为原父节点),此时30成为新父节点。

// 右单旋
		void RotateR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;

			pParent->_pLeft = subLR;
			if (subLR)
				subLR->_pParent = pParent;
			subL->_pRight = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subL;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subL;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subL;
				}
				else
				{
					temp->_pRight = subL;
				}
				subL->_pParent = temp;
			}
			pParent->_bf = subL->_bf = 0;
		}

左单旋

与右单旋同理,不过多赘述。

左单旋适用于“右右”,即parent的平衡因子为2,高度变化的子节点的平衡因子为1。

旋转过程: 

// 左单旋
		void RotateL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;

			pParent->_pRight = subRL;
			if (subRL)
				subRL->_pParent = pParent;
			subR->_pLeft = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subR;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subR;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subR;
				}
				else
				{
					temp->_pRight = subR;
				}
				subR->_pParent = temp;
			}
			pParent->_bf = subR->_bf = 0;
		}

左右双旋

如图所示,当出现插入节点在失衡节点(90)其左孩子(30)的右孩子(60)下的子树时,就需要用到左右双旋,单旋是无法解决的。

即:失衡节点平衡因子为-2,且其左孩子的平衡因子为1时使用左右双旋,以下简称“左右”。

第一步,我们对30为根的子树进行左单旋。(第一次旋转后,失衡的状况从“左右”变成了可以使用右单旋的“左左”)

 第二步,我们对90为根的子树进行右单旋。

 

 代码实际上是对左单旋和右单旋的复用,然后再处理节点间的连接关系。

// 左右双旋
		void RotateLR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;
			int bf = subLR->_bf;

			RotateL(subL);
			RotateR(pParent);
			subLR->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = 0;
				subL->_bf = -1;
			}
			else if (bf == -1)
			{
				pParent->_bf = 1;
				subL->_bf = 0;
			}
			else
			{
				pParent->_bf = 0;
				subL->_bf = 0;
			}
		}

右左双旋

失衡节点平衡因子为2,且其左孩子的平衡因子为-1时使用左右双旋。

第一步,我们对90为根的子树进行右单旋。(第一次旋转后,失衡的状况从“右左”变成了可以使用左单旋的“右右”)

 第二步,我们对30为根的子树进行左单旋。

 

// 右左双旋
		void RotateRL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;
			int bf = subRL->_bf;

			RotateR(subR);
			RotateL(pParent);
			subRL->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = -1;
				subR->_bf = 0;
			}
			else if (bf == -1)
			{
				pParent->_bf = 0;
				subR->_bf = 1;
			}
			else
			{
				pParent->_bf = 0;
				subR->_bf = 0;
			}
		}

完整代码实现

#pragma once
#include<assert.h>
#include<vector>
#include<iostream>
using namespace std;

namespace VAL
{
	template<class T>
	struct AVLTreeNode
	{
		AVLTreeNode(const T& data = T())
			: _pLeft(nullptr)
			, _pRight(nullptr)
			, _pParent(nullptr)
			, _data(data)
			, _bf(0)
		{}

		AVLTreeNode<T>* _pLeft;
		AVLTreeNode<T>* _pRight;
		AVLTreeNode<T>* _pParent;
		T _data;
		int _bf;   // 节点的平衡因子
	};


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

		Node* Find(const T& data)
		{
			Node* cur = _pRoot;
			while (cur)
			{
				if (cur->_data < data)
				{
					cur = cur->_pRight;
				}
				else if (cur->_data > data)
				{
					cur = cur->_pLeft;
				}
				else
				{
					return cur;
				}
			}

			return nullptr;
		}

		// 在AVL树中插入值为data的节点
		bool Insert(const T& data)
		{
			if (_pRoot == nullptr)
			{
				_pRoot = new Node(data);
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _pRoot;

			while (cur)
			{
				if (data > cur->_data)
				{
					parent = cur;
					cur = cur->_pRight;
				}
				else if (data < cur->_data)
				{
					parent = cur;
					cur = cur->_pLeft;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(data);
			if (parent->_data < data)
			{
				parent->_pRight = cur;
			}
			else
			{
				parent->_pLeft = cur;
			}
			cur->_pParent = parent;


			//调整平衡因子
			while (parent)
			{
				if (cur == parent->_pLeft)
				{
					parent->_bf--;
				}
				else
				{
					parent->_bf++;
				}

				//插入后恰好平衡了,跳出循环
				if (parent->_bf == 0)
				{
					break;
				}    //高度出现变化但还未失衡,向上走进行判断
				else if (parent->_bf == 1 || parent->_bf == -1)
				{
					cur = parent;
					parent = parent->_pParent;
				}	//已经失衡需要进行调整
				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);
					}
					break;
				}
				else
				{
					// 理论而言不可能出现这个情况
					assert(false);
				}
			}
			return true;
		}

		// AVL树的验证
		bool IsAVLTree()
		{
			return _IsAVLTree(_pRoot);
		}

		int Size()
		{
			return _Size(_pRoot);
		}

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

		size_t Height()
		{
			return _Height(_pRoot);
		}

	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_pLeft);
			cout << root->_data << endl;
			_InOrder(root->_pRight);
		}

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

			int leftHeight = _Height(root->_pLeft);
			int rightHeight = _Height(root->_pRight);
			// 不平衡
			if (abs(leftHeight - rightHeight) >= 2)
			{
				cout << root->_data << endl;
				return false;
			}

			// 顺便检查一下平衡因子是否正确
			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_data << endl;
				return false;
			}

			return _IsAVLTree(root->_pLeft)
				&& _IsAVLTree(root->_pRight);
		}

		size_t _Height(Node* _pRoot)
		{
			if (_pRoot == nullptr) return 0;

			return max(_Height(_pRoot->_pLeft), _Height(_pRoot->_pRight)) + 1;
		}

		int _Size(Node* root)
		{
			return root == nullptr ? 0 : _Size(root->_pLeft) + _Size(root->_pRight) + 1;
		}

		// 右单旋
		void RotateR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;

			pParent->_pLeft = subLR;
			if (subLR)
				subLR->_pParent = pParent;
			subL->_pRight = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subL;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subL;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subL;
				}
				else
				{
					temp->_pRight = subL;
				}
				subL->_pParent = temp;
			}
			pParent->_bf = subL->_bf = 0;
		}
		// 左单旋
		void RotateL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;

			pParent->_pRight = subRL;
			if (subRL)
				subRL->_pParent = pParent;
			subR->_pLeft = pParent;
			Node* temp = pParent->_pParent;
			pParent->_pParent = subR;

			//pParent有可能为根,右单旋后应该更新根节点指向。
			if (pParent == _pRoot)
			{
				_pRoot = subR;
				_pRoot->_pParent = nullptr;
			}
			else
			{
				if (temp->_pLeft == pParent)
				{
					temp->_pLeft = subR;
				}
				else
				{
					temp->_pRight = subR;
				}
				subR->_pParent = temp;
			}
			pParent->_bf = subR->_bf = 0;
		}
		// 右左双旋
		void RotateRL(Node* pParent)
		{
			Node* subR = pParent->_pRight;
			Node* subRL = subR->_pLeft;
			int bf = subRL->_bf;

			RotateR(subR);
			RotateL(pParent);
			subRL->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = -1;
				subR->_bf = 0;
			}
			else if (bf == -1)
			{
				pParent->_bf = 0;
				subR->_bf = 1;
			}
			else
			{
				pParent->_bf = 0;
				subR->_bf = 0;
			}
		}
		// 左右双旋
		void RotateLR(Node* pParent)
		{
			Node* subL = pParent->_pLeft;
			Node* subLR = subL->_pRight;
			int bf = subLR->_bf;

			RotateL(subL);
			RotateR(pParent);
			subLR->_bf = 0;

			if (bf == 1)
			{
				pParent->_bf = 0;
				subL->_bf = -1;
			}
			else if (bf == -1)
			{
				pParent->_bf = 1;
				subL->_bf = 0;
			}
			else
			{
				pParent->_bf = 0;
				subL->_bf = 0;
			}
		}

	private:
		Node* _pRoot;
	};
}

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

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

相关文章

人工智能类SCI,1区TOP,3个月可录!

今天给大家推荐一本人工智能类SCIE领域的SCI&#xff0c;此期刊为我处目前合作的重点期刊&#xff01;影响因子7.0-8.0之间&#xff0c;JCR1区&#xff0c;中科院2/1区&#xff08;TOP&#xff09;&#xff0c;最重要的是审稿周期较短&#xff0c;对急投的学者较为友好&#xf…

MATLAB-振动问题:两自由度耦合系统自由振动

一、基本理论 二、MATLAB实现 以下是两自由度耦合系统自由振动质量块振动过程动画显示的MATLAB程序。 clear; clc; close allx0 1; D1 40; D12 8; D2 D1; m1 1; omega0 sqrt(D1/m1); k1 D12 / D1; k2 D12 / D2; k sqrt(k1 * k2); omegazh omega0 * sqrt(1 k); omeg…

SpringBoot使用Spark的DataFrame API

什么是Spark&#xff1f; Apache Spark是一个开源的分布式计算系统&#xff0c;它提供了一个快速和通用的集群计算平台。Spark 能够处理大规模数据&#xff0c;支持多种编程语言&#xff0c;如Scala、Java和Python&#xff0c;并且具有多种高级功能&#xff0c;包括SQL查询、机…

基于51单片机的密码锁Proteus仿真

文章目录 一、密码锁1.题目要求2.思路3.仿真图3.1 未仿真时3.2 初始界面3.3 输入密码界面3.4 开锁成功界面3.5 修改密码界面3.6 输入密码错误界面 4.仿真程序4.1 矩阵按键4.2 液晶显示16024.3 存储模块2402 二、总结 一、密码锁 1.题目要求 以51单片机为核心&#xff0c;设计…

【原创实现 设计模式】Spring+策略+模版+工厂模式去掉if-else,实现开闭原则,优雅扩展

1 定义与优点 1.1 定义 策略模式&#xff08;Strategy Pattern&#xff09;属于对象的⾏为模式。他主要是用于针对同一个抽象行为&#xff0c;在程序运行时根据客户端不同的参数或者上下文&#xff0c;动态的选择不同的具体实现方式&#xff0c;即类的行为可以在运行时更改。…

C++:静态断言内存对齐

静态断言 C中的断言assert (1)直接参考&#xff1a;https://www.cnblogs.com/lvchaoshun/p/7816288.html (2)C的assert是运行时检测发现错误&#xff0c;而不是编译时 (3)C在编译时错误用#error来输出C静态断言 (1)C引入static_assert(表达式, “提示字符串”)来实现编译时的静…

[数据集][目标检测]婴儿状态睡觉哭泣检测数据集VOC+YOLO格式7109张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;7109 标注数量(xml文件个数)&#xff1a;7109 标注数量(txt文件个数)&#xff1a;7109 标注…

【MySQL基础篇】SQL指令:DQL及DCL

1、DQL DQL - 介绍 DQL英文全称是Data Query Language(数据查询语言)&#xff0c;数据查询语言&#xff0c;用来查询数据表中的记录。&#xff08;在MySQL中应用是最为广泛的&#xff09; 查询关键字&#xff1a;SELECT DQL - 语法 SELECT 字段列表 FROM 表名列表 WHER…

代码随想录算法训练营第四十七天| 188.买卖股票的最佳时机IV ,309.最佳买卖股票时机含冷冻期 ,714.买卖股票的最佳时机含手续费

188. 买卖股票的最佳时机 IV - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int k, int[] prices) {int[][] dp new int[prices.length][2*k];for(int i0;i<2*k;i){if(i%2 0){dp[0][i] -prices[0];}else{dp[0][i] 0;} }for(int i1;i…

LeetCode题练习与总结:环形链表Ⅱ--142

一、题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测…

C语言 | Leetcode C语言题解之第206题反转链表

题目&#xff1a; 题解&#xff1a; struct ListNode* reverseList(struct ListNode* head) {if (head NULL || head->next NULL) {return head;}struct ListNode* newHead reverseList(head->next);head->next->next head;head->next NULL;return newHea…

Camera Raw:增强

Camera Raw 中的增强 Enhance命令基于 AI 技术提升图像的质量&#xff0c;可用于降噪、生成清晰的细节以及提高图像的分辨率。 ◆ ◆ ◆ 主要用途 1、高 ISO 图像降噪 勾选“去杂色” Denoise&#xff0c;可轻松消除使用高 ISO 设置或在低光环境下拍摄的照片中的噪点。 可以对…

Nettyの粘包、半包问题框架解决方案自定义协议

1、Netty框架是如何解决粘包、半包问题 关于粘包&#xff0c;半包问题&#xff0c;在前面几篇中都有提及&#xff0c;我们简单的复习一下。 粘包指的是客户端发出的多条消息&#xff0c;被服务端当做一条进行接收。半包指的是客户端发出一条完整的消息&#xff0c;在传输的过程…

鸿蒙项目实战-月木学途:1.编写首页,包括搜索栏、轮播图、宫格

效果展示 搜索栏制作 相关知识回顾 输入框组件TextInput 单行输入框类型.type(InputType.Normal)//基本输入框.type(InputType.Password)//密码.type(InputType.Email)//邮箱.type(InputType.Number)//数字.type(InputType.PhoneNumber)//电话号.type(InputType.Normal).type…

boston房价预测--机器学习Boston数据分析

1.采用散点图绘制相关性。 #分析波士顿房价数据集的数据相关性 import numpy as np import pandas as pd import matplotlib.pyplot as plt #载入数据集 data_url "http://lib.stat.cmu.edu/datasets/boston" raw_df pd.read_csv(data_url, sep"\s", …

Java数据结构6-栈与队列

1. 栈(Stack) 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则 压栈…

第11章 规划过程组(11.5创建WBS)

第11章 规划过程组&#xff08;一&#xff09;11.5创建WBS&#xff0c;在第三版教材第380~383页&#xff1b; 文字图片音频方式 视频22 第一个知识点&#xff1a;主要输入 1、项目管理计划 范围管理计划 定义了如何根据项目范围说明书创建WBS2、项目文件 项目范围说明…

Uboot重定位

Uboot重定位 一、重定位的意义二、介绍一些重定位相关的表项结构(节)三、uboot的重定位过程:一、重定位的意义 uboot的重定位有两次,第一次是在编译成镜像后,在makefile中调用进行处理的,其调用tools/riscv_prelink.c的代码进行重定位处理(主要就是对重定位表中的R_RIS…

为什么IP地址会被列入黑名单?

您是否曾经历过网站访客数量骤减或电子邮件投递失败的困扰&#xff1f;这背后或许隐藏着一个常被忽略的原因&#xff1a;您的IP地址可能已经被列入了黑名单内。尽管您并没有进行任何违法的网络操作&#xff0c;但这个问题依然可能出现。那么&#xff0c;究竟黑名单是什么&#…

巴黎成为欧洲AI中心 大学开始输出AI创始人

来自Dealroom 的数据显示&#xff0c;在欧洲和以色列AI创业公司中&#xff0c;法国的AI创业公司资金最充裕。Mistral、Owkin、Hugging Face等法国企业已经融资23亿美元&#xff0c;比英国、德国AI创业公司都要多。 一名大学生走出校门凭借聪明才智和一个黄金点子成为富豪&#…