AVL树的创建与检测

news2024/11/27 4:13:47

个人主页:敲上瘾-CSDN博客

个人专栏:游戏、数据结构、c语言基础、c++学习、算法

目录

一、什么是AVL树?

二、平衡因子

1、什么是平衡因子?

2、平衡因子如何更新?

三、单旋

1、左单旋

​编辑

2、右单旋

四、双旋

1、左右双旋

2、右左双旋

五、AVL树检测

六、源码


一、什么是AVL树?

        什么是AVL树?其实它就是一颗平衡的二叉搜索树,我们都知道一颗二叉搜索树在极端情况下会退化为单支(和链表同样的结构),那么它的查找效率就会变为O(N),AVL树的就是通过一些操作来防止这种退化,从而使查找效率保持在O(logN)。

如下同一组数据的两种二叉搜索树形态:

二、平衡因子

1、什么是平衡因子?

        对一个AVL树操作的时候首先就是需要知道它是否平衡,所以可以在节点上多增加一个变量用来储存左右子树的高度差,这个数据我们就称之为平衡因子。(即平衡因子=左子树的高度-右子树的高度)平衡因子只是起到一个辅助的作用,也可用其他方式。

        现在我们知道了高度差,那么高度差到达什么时候表示不平衡需要我们去调整呢?我们可以知道高度差为零的时候肯定是最好的,但⽽是有些情况是做不到⾼度差为0的。⽐如⼀棵树是2个结点,4个结点等情况下,⾼度差最好就是1。

        所以我们就以平衡因子大于等于2或平衡因子小于等于-2为标准表示该树已经不平衡需要调整。

2、平衡因子如何更新?

        对于一个AVL树(已平衡)我们插入一个数据实际上是往叶子节点插入,而无论如何插入都会影响它的父节点的平衡因子,父节点又会影响它的父节点,以此类推。这样在更新父节点的平衡因子时就会变得很麻烦。所以在设计节点的时候除了有指向左子树的指针和指向右子树的指针以外,还需要我们添加一个前驱指针指向父节点

        平衡因子具体如何更新?因为平衡因子=左子树高度-右子树高度。所以如果新元素插入在左子树则父节点的平衡因子加1,如果插入到右子树则父节点的平衡因子减1。然后继续更新该父节点(记为p)的父节点(记为pp),同样如果p是pp的左子树则pp的平衡因子加1,如果p是pp的右子树则平衡因子减1。以此类推往上更新。

平衡因子更新后一共有三种情况:

  • 平衡因子更新为0:此时表示之前不平衡那一部分因为新节点的插入而被抵消,对整棵树的高度没有影响,所以就不会对它上一层的平衡因子造成影响,不用往上更新。
  • 平衡因子更新为1或-1:此时表示新节点的插入使得整棵树的高度改变,会影响上一层,需要继续更新。
  • 平衡因子更新为2或-2:此时表示新节点的插入使得整棵树的高度改变,并使这棵子树不平衡,需要通过旋转把它调整为平衡状态,而调整后整棵子树高度又回到原来状态,不会对上一层造成影响。不用往上更新。
	bool Insert(const T& val)//插入元素
	{
		Node* cur = root;
		Node* newNode = new Node(val);
        //如果为空树则直接插入
		if (root == nullptr)
		{
			root = newNode;
			return true;
		}
        //查找插入位置
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (val <= cur->data) cur = cur->left;
			else cur = cur->right;
		}
        //插入操作
		if (val <= parent->data) parent->left = newNode;
		else parent->right = newNode;
		newNode->prev = parent;
        //更新平衡因子
		cur = newNode;
		while (parent != nullptr)
		{
			if (parent->left == cur) parent->bf--;
			else parent->bf++;
			if (parent->bf == 0) break;
			else if (parent->bf == 1 || parent->bf == -1)
			{
				cur = parent;
				parent = parent->prev;
			}
			else if (parent->bf == 2 || parent->bf == -2)
			{
				//不平衡,进行旋转
                //......
			}
		}
		return true;
	}

三、单旋

1、左单旋

        在这里为了兼容子树的多种状况和不失一般性,使用了a,b,c来抽象表示子树。如上图因为添加节点使得a的高度发生变化(但a子树依旧保持平衡),从而使得存放15这个节点(记为subR)的这棵个子树的高度改变(subR依旧平衡),从而影响上一层(记为parent),parent的平衡因子变为2,即左边过高,使得整棵树不平衡需要调整。

        因为这里有这样一个规律,整颗b子树是比parent大的,所以可以把b子树接到parent右边,然后因为subR同样也比parent大但是要把高的子树提上去,所以把parent接到subR左边。

前驱指针更新

对于单旋的重难点还是在于前驱指针的更新,我们把它们分开单个分析:

  • parent:parent的前驱直接指向subR即可,但是注意在这之前一定要先把原来parent的前驱记录起来(记为pparent)
  • subR:首先将subR的前驱指向pparent,而pparent又分为两种情况:(1)、pparent为空,说明原来的parent是整棵树的根,所以现在需要把根更新为subR。(2)、pparent不为空,那么pparent同时也要指向subR,此时pparent的左子树还是右子树指向subR是个问题,我们可以判断parent是pparent的左孩子还是右孩子,如果是左孩子则pparent的左指向subR,同理如果是右孩子则pparent的右指向subR。
  • subRL:对于subRL有两种情况:(1)、subRL为空,此时不用处理,(2)、subRL不为空,此时直接把subRL的前驱指针指向parent。

平衡因子更新 

        通过观察调整后的树,调整后涉及的节点它的左右子树高度都是一样的,所以对于单旋(包括右单旋)调整后的parent和subR平衡因子都需要更新为0,而在该过程中subRL的平衡因子并未受到影响,不用更新。

左单旋代码:

void RotateL(Node* parent)
{
	Node* subR = parent->right;
	Node* subRL = subR->left;
	Node* pparent = parent->prev;
	//修改子节点
	parent->right = subRL;
	subR->left = parent;
	//修改父节点
	parent->prev = subR;
	if (subRL) subRL->prev = parent;
	subR->prev = pparent;
	if (pparent == nullptr) root = subR;//把根节点替换成subL
	else
	{
		if (pparent->left == parent) pparent->left = subR;
		else pparent->right = subR;
	}
	//修改平衡因子
	parent->bf = 0;
	subR->bf = 0;
}

2、右单旋

        如果理解了左单旋那么右单旋就比较容易理解了,同样如果添加节点使得parent左边过高,则需要右旋。如图subL和subLR都比parent要小,但为了把左子树提上去,所以让parent接到subL的右边,subLR接到parent的左边。

前驱指针更新

  • parent:parent的前驱直接指向subL即可,但是注意在这之前一定要先把原来parent的前驱记录起来(记为pparent)
  • subL:首先将subL的前驱指向pparent,而pparent又分为两种情况:(1)、pparent为空,说明原来的parent是整棵树的根,所以现在需要把根更新为subL。(2)、pparent不为空,那么pparent同时也要指向subL,此时pparent的左子树还是右子树指向subL是个问题,我们可以判断parent是pparent的左孩子还是右孩子,如果是左孩子则pparent的左指向subL,同理如果是右孩子则pparent的右指向subL。
  • subLR:对于subLR有两种情况:(1)、subLR为空,此时不用处理,(2)、subLR不为空,此时直接把subLR的前驱指针指向parent。

平衡因子更新 

        通过观察调整后的树,调整后涉及的节点它的左右子树高度都是一样的,所以对于单旋调整后的parent和subL平衡因子都需要更新为0,而在该过程中subLR的平衡因子并未受到影响,不用更新。

右单旋代码:

void RotateR(Node* parent)
{
	Node* subL = parent->left;
	Node* subLR = subL->right;
	Node* pparent = parent->prev;
	parent->left = subLR;
	subL->right = parent;
    //修改前驱
	parent->prev = subL;
	if (subLR) subLR->prev = parent;
	subL->prev = pparent;
	if (pparent == nullptr) root = subL;
	else
	{
		if (pparent->left == parent) pparent->left = subL;
		else pparent->right = subL;
	}
	parent->bf = 0;
	subL->bf = 0;
}

四、双旋

1、左右双旋

        对于该场景我们可以发现无论让它左旋还是右旋都无法到达平衡状态,它与单旋的区别是parent和subL的平衡因子是一正一负,也就是说它并不是单纯的左边高或单纯的右边高,如上图对于subL来说是左边高,对于parent来说是右边高。

        不过也好办,只需要先以subL为旋转点进行左旋,再以parent为旋转点进行右旋,这个可以直接复用上面单旋的代码。如下:

void RotateLR(Node* prev)
{
	RotateL(prev->left);
	RotateR(prev);
}

不过有一个细节,平衡因子如何改变?我们就需要对b子树展开进行分析,如下:

左右双旋:

以场景一为例动画展示:第一部以subL为旋转点进行左旋,第二部以parent为旋转点进行右旋。

 

        通过观察很容易发现,如果原先subLR平衡因子为-1,则旋转后parent、subL、subLR平衡因子分别为1,0,0。如果原先subLR平衡因子为1,则旋转后parent、subL、subLR平衡因子分别为0,-1,0。对于这个特点我们直接在旋前进行判断直接修改就行。

特殊场景:

对于该场景各节点的平衡因子反而是不需要特殊处理的,单旋过程已经处理过了。

2、右左双旋

        右左双旋相对于左右双旋就是照葫芦画瓢,这里就不做过多的讲解。

旋转代码:

void RotateRL(Node* prev)
{
	RotateR(prev->right);
	RotateL(prev);
}

 

        同样如果原先subRL平衡因子为-1,则旋转后parent、subR、subRL平衡因子分别为0,1,0。如果原先subLR平衡因子为1,则旋转后parent、subR、subRL平衡因子分别为-1,0,0。对于这个点我们直接在旋前进行判断直接修改就行。

该场景的平衡因子处理和单旋处理相同,不用做特殊处理。

五、AVL树检测

        当我们写好一个AVL树后然后判断它是否平衡是个问题。注意这里我们不能直接使用平衡因子去判断,平衡因子只是我们用来创建AVL树的一个工具,要看一颗AVL树是否真正平衡则需要去看它的高度差,如果任意一颗子树的高度差大于等于2那么这个树就不平衡。

代码如下:

int Height(Node* root)//计算高度
{
	if (root == nullptr) return 0;
	int l = Height(root->left);
	int r = Height(root->right);
	return l > r ? l : r;
}
bool _IsAVLTree(Node* root)//判断是否平衡
{
	if (root == nullptr) return true;
	int LHeight = Height(root->left);
	int RHeight = Height(root->right);
	if (LHeight - RHeight >= 2 || LHeight - RHeight <= -2) return false;
	return _IsAVLTree(root->left) && _IsAVLTree(root->right);
}

六、源码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
template<typename T>
struct AVLTreeNode
{
	AVLTreeNode(const T& val = T())
		:left(nullptr)
		, right(nullptr)
		, prev(nullptr)
		, data(val)
		, bf(0) {}
	AVLTreeNode<T>* left;
	AVLTreeNode<T>* right;
	AVLTreeNode<T>* prev;
	T data;
	int bf;
};
template<typename T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;
public:
	bool Insert(const T& val)
	{
		Node* cur = root;
		Node* newNode = new Node(val);
		if (root == nullptr)
		{
			root = newNode;
			return true;
		}
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (val <= cur->data) cur = cur->left;
			else cur = cur->right;
		}
		if (val <= parent->data) parent->left = newNode;
		else parent->right = newNode;
		newNode->prev = parent;
		cur = newNode;
		while (parent != nullptr)
		{
			if (parent->left == cur) parent->bf--;
			else parent->bf++;
			if (parent->bf == 0) break;
			else if (parent->bf == 1 || parent->bf == -1)
			{
				cur = parent;
				parent = parent->prev;
			}
			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)
				{
					if (cur->right->bf == -1) parent->bf = 1;
					else cur->bf = -1;
					RotateLR(parent);
				}
				else if (parent->bf == 2 && cur->bf == -1)
				{
					if (cur->left->bf == -1) cur->bf = 1;
					else parent->bf = -1;
					RotateRL(parent);
				}
				else assert(false);
				return true;
			}
			else
			{
				assert(0);
			}
		}
		return false;
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;
		Node* pparent = parent->prev;
		//修改子节点
		parent->right = subRL;
		subR->left = parent;
		//修改父节点
		parent->prev = subR;
		if (subRL) subRL->prev = parent;
		subR->prev = pparent;
		if (pparent == nullptr) root = subR;//把根节点替换成subL
		else
		{
			if (pparent->left == parent) pparent->left = subR;
			else pparent->right = subR;
		}
		//修改平衡因子
		parent->bf = 0;
		subR->bf = 0;
	}
	void RotateR(Node* parent)
	{
		Node* subL = parent->left;
		Node* subLR = subL->right;
		Node* pparent = parent->prev;
		parent->left = subLR;
		subL->right = parent;
		//修改前驱
		parent->prev = subL;
		if (subLR) subLR->prev = parent;
		subL->prev = pparent;
		if (pparent == nullptr) root = subL;
		else
		{
			if (pparent->left == parent) pparent->left = subL;
			else pparent->right = subL;
		}
		parent->bf = 0;
		subL->bf = 0;
	}
	void RotateLR(Node* parent)
	{
		RotateL(parent->left);
		RotateR(parent);
	}
	void RotateRL(Node* parent)
	{
		RotateR(parent->right);
		RotateL(parent);
	}
	int Height(Node* root)
	{
		if (root == nullptr) return 0;
		int l = Height(root->left);
		int r = Height(root->right);
		return l > r ? l : r;
	}
	bool IsAVLTree()
	{
		return _IsAVLTree(root);
	}
	bool _IsAVLTree(Node* root)
	{
		if (root == nullptr) return true;
		int LHeight = Height(root->left);
		int RHeight = Height(root->right);
		if (LHeight - RHeight >= 2 || LHeight - RHeight <= -2) return false;
		return _IsAVLTree(root->left) && _IsAVLTree(root->right);
	}
private:
	Node* root = nullptr;
};

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

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

相关文章

OSPF的不规则区域

1.远离骨干非骨干区域 2.不连续骨干 解决方案 tunnel ---点到点GRE 在合法与非ABR间建立隧道&#xff0c;然后将其宣告于OSPF协议中&#xff1b; 缺点&#xff1a;1、周期和触发信息对中间穿越区域造成资源占用&#xff08;当同一条路由来自不同区域&#xff0c;路由器会先…

JS基础练习|动态创建多个input并且用数组记录其中的数据

效果图 、 在点击添加输入框的时候&#xff0c;创建新的元素&#xff0c;并且为其绑定响应的事件。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-…

稀缺森林火险等级预测算法,基于xgboost方法的火险等级预测,共划分5级,依据当前地区月份,降水量,风力等参数进行预测,并提供15000字的报告

森林火险等级预测算法&#xff0c;基于xgboost方法的火险等级预测&#xff0c;共划分5级&#xff0c;依据当前地区月份&#xff0c;降水量&#xff0c;风力等参数进行预测&#xff0c;并提供15000字的报告 森林火险等级预测算法介绍 项目名称 基于XGBoost的森林火险等级预测算…

双向无头非循环链表的简单实现及介绍

前言 欢迎大家阅读小奥奇的新作&#xff0c;听说上一篇我们留下了一点点 “ 简单的题目 ” &#xff0c;我们在本篇要干什么呢&#xff0c;请看本篇任务&#xff01; 本篇任务概述&#xff1a; 1、解决 “ 简单的遗留题目 ” 2、 LInkedList&#xff08;双向&#xff09;的使用…

2.1MyBatis——ORM对象关系映射

2.1MyBatis——ORM对象关系映射 1. 验证映射配置2.ResultType和ResultMap2.1ResultMap是最终的ORM依据2.2ResultType和ResultMap的使用区别 3.具体的转换逻辑3.1 TypeHandle类型转换 5.总结 概括的说&#xff0c;MyBatis中&#xff0c;对于映射关系的声明是由开发者在xml文件手…

“2024年最流行的10个前端框架”

大多数时候&#xff0c;前端开发人员需要使用一组组合语言来构建他们的前端 Web 应用程序。 HTML 负责网页中的基本布局&#xff0c;CSS 管理视觉格式和结构&#xff0c;JavaScript 用于维护交互性和功能。在这篇文章中&#xff0c;我们将了解最好的前端框架&#xff0c;这些框…

FL Studio 24.1.2.4381中文版免费下载及FL Studio 24最新使用学习教程

家好呀&#xff0c;作为一个资深的音乐爱好者和制作人&#xff0c;今天我要安利一个我最近超级痴迷的数字音频工作站软件——FL Studio24.1.2.4381中文版。这款产品可是让我的音乐创作之路如虎添翼&#xff0c;快来跟我一起看看它的炫酷功能吧&#xff01; 最近接到很多小伙伴的…

2024 ciscn WP

一、MISC 1.火锅链观光打卡 打开后连接自己的钱包&#xff0c;然后点击开始游戏&#xff0c;答题八次后点击获取NFT&#xff0c;得到有flag的图片 没什么多说的&#xff0c;知识问答题 兑换 NFT Flag{y0u_ar3_hotpot_K1ng} 2.Power Trajectory Diagram 方法1&#xff1a; 使用p…

操作系统实验之银行算法

一、实验目的 采用高级语言编写一个动态分配系统资源的程序&#xff0c;模拟死锁现象&#xff0c;观察死锁发生的条件&#xff0c;并采用适当的算法&#xff0c;有效地防止死锁的发生。 二、实验内容 本次实验采用银行算法防止死锁的发生。设有3个并发进程共享10个系统资源。在…

1c语言基础

1.关键字 一、数据类型关键字 A基本数据类型&#xff08;5个&#xff09; void&#xff1a;声明函数无返回值或无参数&#xff0c;声明无类型指针&#xff0c;显式丢弃运算结果char&#xff1a;字符型类型数据&#xff0c;属于整型数据的一种int&#xff1a;整型数据&#x…

Ollama 运行视觉语言模型LLaVA

Ollama的LLaVA&#xff08;大型语言和视觉助手&#xff09;模型集已更新至 1.6 版&#xff0c;支持&#xff1a; 更高的图像分辨率&#xff1a;支持高达 4 倍的像素&#xff0c;使模型能够掌握更多细节。改进的文本识别和推理能力&#xff1a;在附加文档、图表和图表数据集上进…

Github界面学习

之前并没有使用到其他功能大多数是看代码&#xff0c;然后看discussion&#xff1b; now,在做毕设的时候发现了一个gymnasium关于异步环境的bug&#xff0c;查看github发现已经被修复了&#xff1b; 因此希望学习一下修复者是在哪个module修复以及如何修复以及提交代码&#…

Spring Boot框架在大学生就业招聘中的应用

3系统分析 3.1可行性分析 通过对本大学生就业招聘系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本大学生就业招聘系统采用JAVA作为开发语言&#xff0c;S…

kaggle实战3RossmanStore商店销售额预测XgBoost解决回归问题案例1

kaggle实战2信用卡反欺诈逻辑回归模型案例1 数据集下载地址 https://download.csdn.net/download/AnalogElectronic/89844637 https://tianchi.aliyun.com/dataset/89785 加载数据 #预测销售额 回归问题 import numpy as np import pandas as pd import matplotlib.pyplot a…

无神论文解读之ControlNet:Adding Conditional Control to Text-to-Image Diffusion Models

一、什么是ControlNet ControlNet是一种能够控制模型生成内容的方法&#xff0c;能够对文生图等模型添加限制信息&#xff08;边缘、深度图、法向量图、姿势点图等&#xff09;&#xff0c;在当今生成比较火的时代很流行。 这种方法使得能够直接提供空间信息控制图片以更细粒…

招联2025校招内推倒计时

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…

【课程学习】随机过程之泊松过程

随机过程之泊松过程 泊松分布泊松过程 泊松分布 二项分布是离散性的分布&#xff0c;泊松分布是把二项分布取n趋于无穷得到的连续分布。也就是在一段时间内不停的观察某件事情发生的次数。 如&#xff1a;一个小时内观察一段路上经过行人的数目&#xff0c;如果每个半个小时观…

nginx和gateway的关系和区别

在技术选型时&#xff0c;选择 Nginx 和 Spring Cloud Gateway&#xff08;或简称为 Gateway&#xff09;主要取决于具体应用场景和技术需求。下面是两者的一些关键差异和适用场景。 一、Nginx 概念 Nginx 是一个高性能的 Web 服务器和反向代理服务器&#xff0c;常被用作静…

智能手表(Smart Watch)项目

文章目录 前言一、智能手表&#xff08;Smart Watch&#xff09;简介二、系统组成三、软件框架四、IAP_F411 App4.1 MDK工程结构4.2 设计思路 五、Smart Watch App5.1 MDK工程结构5.2 片上外设5.3 板载驱动BSP5.4 硬件访问机制-HWDataAccess5.4.1 LVGL仿真和MDK工程的互相移植5…

CSRF | CSRF 漏洞介绍

关注这个漏洞的其他相关笔记&#xff1a;CSRF 漏洞 - 学习手册-CSDN博客 0x01&#xff1a;CSRF 漏洞简介 CSRF&#xff08;Cross-Site request forgery&#xff0c;跨站请求伪造&#xff09;也被称为 One Click Attack 或者 Session Riding&#xff0c;通常缩写为 CSRF 或者 X…