【C++】AVL树(插入)

news2024/10/6 8:22:43

文章目录

  • AVL树的概念
  • 平衡化旋转
    • 右单旋转
    • 左单旋转
    • 先左后右双旋转
    • 先右后左双旋转
  • AVL树的插入
    • 根据BST树规则进行节点插入
    • 平衡化处理
    • 重新连接节点
    • 完整的插入函数代码
  • AVL树的验证
  • AVL树的性能

AVL树的概念

二叉搜索树虽然可以提高查找的效率,但是二叉搜索树有其自身的缺陷,也许是因为输入值不够随机,也许是因为经过某些插入或删除操作,二叉搜索树可能会失去平衡,造成搜索效率低的情况。
树形的平衡与否,并没有一个绝对的测量标准。平衡的意义大概就是:没有任何一个节点过深。不同的平衡条件,造成了树搜索的不同的效率表现,以及不同的实现复杂度。

AVL树是一个加上了额外平衡条件的二叉搜索树,其具有以下性质:它的左子树和右子树都是AVL树,且左子树和右子树的高度之差的绝对值不超过1。在AVL树中,我们用平衡因子来描述每个节点是否平衡。平衡因子用节点的右子树的高度减去左子树的高度所得到的高度差,根据AVL树的定义,任一节点的平衡因子只能取-1,0和1。如果一个节点的平衡因子的绝对值大于1,那么该二叉搜索树就失去了平衡,不再是AVL树了。

假设AVL树有n个节点,其高度可保持在O(lon_2n),平均搜索长度也可保持在O(lon_2n)。

平衡化旋转

AVL树要保证每一个节点的平衡因子不超过1,当我们往一棵AVL树中插入一个新节点时,就可能会造成原本平衡的AVL树不平衡。此时必须要调整树的结构,让树重新平衡。

平衡化旋转有两类:单旋转和双旋转。单旋转又分为两类:左单旋转和右单旋转;双旋转也分为两类:先左后右双旋转和先右后左双旋转。

左单旋转和右单旋转是镜像的。

右单旋转

旋转过程如图所示:
在这里插入图片描述

代码如下:

template <class Type>
void AVL<Type>::RotateR(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subR = ptr;
	ptr = subR->leftchild;
	subR->leftchild = ptr->rightchild;
	ptr->rightchild = subR;
	ptr->bf = subR->bf = 0;
}

左单旋转

左单旋转是右单旋转的镜像。

在这里插入图片描述

代码如下:

template <class Type>
void AVL<Type>::RotateL(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subL = ptr;
	ptr = subL->rightchild;
	subL->rightchild = ptr->leftchild;
	ptr->leftchild = subL;
	ptr->bf = subL->bf = 0;
}

先左后右双旋转

双旋转总是考虑三个节点,一个节点是调整后的根节点,一个是未来的左子树节点,另一个是未来的右子树节点。我们先左后右双旋转也就是把未来的左子树节点进行一次左单旋转调整到应当的位置,再把未来的右子树节点进行一次右单旋转调整到应当的位置。

我们把先左后右双旋转分为先一次的左单旋转和再一次的右单旋转,下面我们来看一下先左后右双旋转发生的情况。

情形一:

在这里插入图片描述

情形二:

在这里插入图片描述

上述两种情况都是先左后右双旋转的情况,不同的是插入节点位置的不同,插入位置主要影响的是平衡因子,那我们用什么来区分两种情况呢?主要是插入节点的父节点的平衡因子的正负,如果是大于0,那新节点就是在右子树的位置插入的,如果是小于0,那就是在左子树的位置插入的,概括两种情况,我们用下面的代码来描述先左后右双旋转:

template <class Type>
void AVL<Type>::RotateLR(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subR = ptr;
	AVLNode<Type>* subL = ptr->leftchild;
	ptr = subL->rightchild;

	subL->rightchild = ptr->leftchild;//ptr成为新根前甩掉它左边的负载
	ptr->leftchild = subL;//左单旋转,ptr成为新根
	//调整subL的bf
	if (ptr->bf <= 0)
		subL->bf = 0;
	else
		subL->bf = -1;

	subR->leftchild = ptr->rightchild;//ptr成为新根前甩掉它右边的负载
	ptr->rightchild = subR;//右单旋转,ptr成为新根
	//调整subR的bf
	if (ptr->bf >= 0)
		subR->bf = 0;
	else
		subR->bf = 1;
	
	ptr->bf = 0;
}

先右后左双旋转

先右后左双旋转是先左后右双旋转的镜像。我们也是将双旋转拆分成两个单旋转,先是右单旋转,再是左单旋转,根据插入位置的不同,我们在此讨论这两种情形。

情形一:

在这里插入图片描述

情形二:

在这里插入图片描述

同样的,新插入节点的左右位置不同会导致调整平衡后的各节点平衡因子不一样,概括上述两种情况,我们的先右后左双旋转代码如下:

void AVL<Type>::RotateRL(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subL = ptr;
	AVLNode<Type>* subR = ptr->rightchild;
	ptr = subR->leftchild;
	subR->leftchild = ptr->rightchild;//ptr成为新根前甩掉它右边的负载
	ptr->rightchild = subR;//右单旋转,ptr成为新根
	if (ptr->bf >= 0)
		subR->bf = 0;
	else
		subR->bf = 1;

	subL->rightchild = ptr->leftchild;//ptr成为新根前甩掉它左边的负载
	ptr->leftchild = subL;//左单旋转,ptr成为新根
	if (ptr->bf <= 0)
		subL->bf = 0;
	else
		subL->bf = -1;

	ptr->bf = 0;
}

AVL树的插入

当向一棵本来是高度平衡的AVL树中插入新节点时,如果树中某个节点的平衡因子的绝对值大于1了,那就出现了不平衡,就要做平衡化处理。

首先需要知道的是,出现不平衡的节点一定是插入新节点所经过路径上面的节点,我们在插入节点后,要去检查树是否平衡,其实检查的也就是插入新节点所经过路径上面的节点是否平衡,所以我们需要记录下来新节点插入过程中所走的路径,那用什么记录呢?考虑到后经过的节点先检验是否平衡,我们自然想到用结构。

我们插入新节点大致经过三个步骤:根据BST树插入节点规则进行插入、平衡化处理、重新连接节点。

根据BST树规则进行节点插入

下面这段代码是在二叉搜索树的插入代码上增加了栈记录插入路径的代码:

	AVLNode<Type> *p = t, *pr = nullptr;

	stack<AVLNode<Type>*> st;

	while(p != nullptr)
	{
		if(v == p->data)
			return false;

		pr = p;
		st.push(pr);

		if(v < p->data)
			p = p->leftChild;
		else
			p = p->rightChild;
	}

	p = new AVLNode<Type>(v);
	if(pr == nullptr)
	{
		t = p;
		return true;
	}

	if(p->data < pr->data)
		pr->leftChild = p;
	else
		pr->rightChild = p;

平衡化处理

设新插入的节点为p,从节点p到根节点的路径上,每节点为根的子树的高度都可能会改变,因此,在每执行一次二叉搜索树的插入运算后,我们都需要从新插入的p节点开始,沿该节点的插入路径向根节点方向回溯修改各节点的平衡因子,调整子树的高度,恢复被破坏的平衡性质。我们前面说了,我们用栈结构来记录插入路径。

新节点p的平衡因子为0,这是毫无疑问的,现在要来看它的父节点pr,如果p是pr的左子树,那么pr的平衡因子增加1,否则减少1.

修改平衡因子的代码如下:

		pr = st.top();
		st.pop();
		if (p == pr->leftchild)
			pr->bf--;
		else
			pr->bf++;

修改过后,pr的平衡因子有三种情况:

  • 1、pr的平衡因子为0. 说明p是在pr较矮的子树上插入的,在p插入后,节点pr依旧平衡,整棵树的高度并没有发生增减,此时从pr到根的路径上个以各节点为根的子树的高度不变,从而各节点的平衡因子不变,不需要继续调整平衡,可以退出对栈中元素平衡因子以及是否平衡的处理,退出循环,返回主程序。

代码如下:

		if (pr->bf == 0)
			break;
  • 2、pr的平衡因子的绝对值为1. 说明插入前pr的平衡因子是0,插入新节点p后,pr这颗子树不需要进行平衡化旋转,但整棵树的高度增加,需要从pr向根的方向回溯,考察栈中其他节点的平衡状态。

代码如下:

		if (pr->bf == 1 || pr->bf == -1)
			p = pr;//回溯
  • 3、节点pr的平衡因子的绝对值为2,说明新节点在较高的子树上进行插入,造成了不平衡,需要进行平衡化旋转,此时我们根据p和pr节点的平衡因子的正负情况选择哪种平衡化旋转。

在pr节点的平衡因子小于0的情况下,p的平衡因子也小于0,那就进行先左后右双旋转,否则进行左单旋转,不存在pr节点的平衡因子小于0,p的平衡因子等于0的情况,因为这种情况是在p是pr的左子树,且p有右子树,又在p的左子树上进行插入产生的。很明显,这种情况下,在p的左子树进行插入之前树就已经不平衡了,已经需要调整平衡了。

在pr节点的平衡因子大于0的情况下,如果p节点的平衡因子也大于0,那就进行右单旋转,否则就进行先右后左双旋转。

代码如下:

			if (pr->bf < 0)
			{
				if (p->bf < 0) //   /
					RotateR(pr);
				else  //  <
					RotateLR(pr);
			}
			else
			{
				if (p->bf > 0)  // "\"
					RotateL(pr);
				else   //  >
					RotateRL(pr);
			}

重新连接节点

需要单独考虑的一种情况就是,树本身是一棵空树,我们插入的节点是树根

代码如下:

	if(st.empty())
		t = pr;
	else
	{
		AVLNode<Type> *ppr = st.top();
		if(pr->data < ppr->data)
			ppr->leftChild = pr;
		else
			ppr->rightChild = pr;
	}

完整的插入函数代码

在这里插入代码片template <class Type>
class AVL;
//定义AVL树的节点类型
template <class Type>
class AVLNode
{
	friend class AVL<Type>;
public:
	AVLNode(Type d = Type(), AVLNode<Type>*left = nullptr, AVLNode<Type>*right = nullptr)
		:data(d), leftchild(left), rightchild(right), bf(0)
	{}
	~AVLNode()
	{}
private:
	AVLNode<Type>* leftchild;
	AVLNode<Type>* rightchild;
	Type data;
	int bf;//平衡因子
};

//定义AVL树
template <class Type>
class AVL
{
public:
	AVL():root(nullptr)
	{}
	~AVL()
	{
		Destroy(root);
	}
public:
	bool Insert(const Type &key)
	{
		return Insert(root, key);
	}
	bool Remove(const Type &key)
	{
		return Remove(root, key);
	}
protected:
	bool Insert(AVLNode<Type>*&t, const Type& key);
	bool Remove(AVLNode<Type>*&t, const Type& key);
protected:
	void RotateR(AVLNode<Type> *&ptr);
	void RotateL(AVLNode<Type> *&ptr);
	void RotateLR(AVLNode<Type> *&ptr);
	void RotateRL(AVLNode<Type> *&ptr);
public:
	void Destroy(AVLNode<Type>*&t)
	{
		if (t != nullptr)
		{
			Destroy(t->leftchild);
			Destroy(t->rightchild);
			delete t;
			t = nullptr;
		}
	}
private:
	AVLNode<Type> *root;
};
template <class Type>
void AVL<Type>::RotateR(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subR = ptr;
	ptr = subR->leftchild;
	subR->leftchild = ptr->rightchild;
	ptr->rightchild = subR;
	ptr->bf = subR->bf = 0;
}

template <class Type>
void AVL<Type>::RotateL(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subL = ptr;
	ptr = subL->rightchild;
	subL->rightchild = ptr->leftchild;
	ptr->leftchild = subL;
	ptr->bf = subL->bf = 0;
}
template <class Type>
void AVL<Type>::RotateLR(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subR = ptr;
	AVLNode<Type>* subL = ptr->leftchild;
	ptr = subL->rightchild;

	subL->rightchild = ptr->leftchild;//ptr成为新根前甩掉它左边的负载
	ptr->leftchild = subL;//左单旋转,ptr成为新根
	//调整subL的bf
	if (ptr->bf <= 0)
		subL->bf = 0;
	else
		subL->bf = -1;

	subR->leftchild = ptr->rightchild;//ptr成为新根前甩掉它右边的负载
	ptr->rightchild = subR;//右单旋转,ptr成为新根
	//调整subR的bf
	if (ptr->bf >= 0)
		subR->bf = 0;
	else
		subR->bf = 1;
	
	ptr->bf = 0;
}
template <class Type>
void AVL<Type>::RotateRL(AVLNode<Type> *&ptr)
{
	AVLNode<Type>* subL = ptr;
	AVLNode<Type>* subR = ptr->rightchild;
	ptr = subR->leftchild;
	subR->leftchild = ptr->rightchild;//ptr成为新根前甩掉它右边的负载
	ptr->rightchild = subR;//右单旋转,ptr成为新根
	if (ptr->bf >= 0)
		subR->bf = 0;
	else
		subR->bf = 1;

	subL->rightchild = ptr->leftchild;//ptr成为新根前甩掉它左边的负载
	ptr->leftchild = subL;//左单旋转,ptr成为新根
	if (ptr->bf <= 0)
		subL->bf = 0;
	else
		subL->bf = -1;

	ptr->bf = 0;
}
template <class Type>
bool AVL<Type>::Insert(AVLNode<Type>*&t, const Type& v)
{
	AVLNode<Type> *p = t, *pr = nullptr;
	stack<AVLNode<Type>*> st;//借助栈结构保存节点插入的路线
	//确定插入位置
	while (p != nullptr)
	{
		if (p->data == v)
			return false;
		pr = p;
		st.push(pr);
		if (v < p->data)
			p = p->leftchild;
		else
			p = p->rightchild;
	}
	//新建节点
	p = new AVLNode<Type>(v);
	if (pr == nullptr)
	{
		t = p;
		return true;
	}
	if (p->data < pr->data)
		pr->leftchild = p;
	else
		pr->rightchild = p;
	
	//调整平衡
	while (!st.empty())
	{
		pr = st.top();
		st.pop();
		if (p == pr->leftchild)
			pr->bf--;
		else
			pr->bf++;

		if (pr->bf == 0)
			break;
		if (pr->bf == 1 || pr->bf == -1)
			p = pr;//回溯
		else
		{
			//四种需要调整树的结构的情况
			if (pr->bf < 0)
			{
				if (p->bf < 0) //   /
					RotateR(pr);
				else  //  <
					RotateLR(pr);
			}
			else
			{
				if (p->bf > 0)  // "\"
					RotateL(pr);
				else   //  >
					RotateRL(pr);
			}
			break;
		}
	}

	//重新连接
	if (st.empty())
		t = pr;
	else
	{
		AVLNode<Type>* ppr = st.top();
		if (pr->data < ppr->data)
			ppr->leftchild = pr;
		else
			ppr->rightchild = pr;
	}
	return true;
}

AVL树的验证

AVL树是在二叉搜索树的基础上加入了对平衡性的限制,因此要验证AVL树,可以分为两步:

  • 验证树为二叉搜索树

如果中序遍历得到一个有序的序列,那么就说明是二叉搜索树

  • 验证树为平衡树,每个节点的子树的高度差的绝对值不超过1,也就是平衡因子的绝对值不超过1,同时要注意节点的平衡因子是否计算正确。

AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,要求平衡因子的绝对值不超过1,这样保证了搜索查询的时间复杂度为log2(n),十分高效,但是同时,因为要保证维护树的绝对平衡,在对AVL树做一些结构修改的操作时,性能就十分低下,比如插入某个值,就可能会引起多次旋转,更差的是在删除时,有可能要让旋转持续到根的位置。考虑到AVL树的特性,当我们需要一种查询十分高效且有序的数据结构,而且数据的个数为静态的,也就意味着树的结构不会发生变化,此时可以考虑AVL树,如果我们的结构经常改变,经常需要插入或者删除一些数据,AVL树就不是那么合适了。

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

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

相关文章

Python与Matlab混合编程案例

前言因为项目需要&#xff0c;需要批处理很多Matlab的.m文件&#xff0c;从每个文件中提取结果合并到一个文件中。 很明显&#xff0c;如果手工统计&#xff0c;几百个文件会累死的。 因此立即想到了Python在批处理方面的优势&#xff0c;因此就在网上找了相关资料&#xff0c;…

C++初阶:vector

文章目录1 vector介绍2 实现vector2.1 类的定义2.2 默认成员函数2.2.1 构造函数2.2.2 析构函数2.2.3 拷贝构造2.2.4 赋值重载2.3访问接口2.4 容量接口2.5 修改接口2.5.1 尾插尾删2.5.2 任意位置插入2.5.3 任意位置删除2.6 其他接口1 vector介绍 1 vector是表示可变大小数组的序…

10+编程语言实现云笔记

目标 为编程初学者打造入门学习项目&#xff0c;使用各种主流编程语言来实现。让想学编程的&#xff0c;一个都不落下。 上述基本涵盖了当前编程开发所有主流语言。 左侧为前端版本&#xff1a;安卓、iOS、鸿蒙、Flutter、Vue、uni-app。 右侧为服务器端版本&#xff1a;Jav…

代码随想录算法训练营三期 day 27 - 回溯 (3) (补)

39. 组合总和 题目链接&#xff1a;39. 组合总和 原文链接&#xff1a;39. 组合总和 视频链接&#xff1a;39. 组合总和 本题和 77.组合 &#xff0c;216.组合总和III 的区别是&#xff1a;本题没有数量要求&#xff0c;可以无限重复&#xff0c;但是有总和的限制。 树形结构&…

【axios】axios的基础知识和使用

一、基础知识概念Axios 是专注于网络数据请求的库,只负责发请求、拿数据&#xff0c;不能操作DOM元素。相比于原生的 XMLHttpRequest 对象&#xff0c;axios 简单易用。相比于 jQuery&#xff0c;axios 更加轻量化&#xff0c;不能操作DOM元素&#xff0c;只专注于网络数据请求…

cubeIDE开发, stm32人工智能开发应用实践(Cube.AI).篇二

一、事有蹊跷 接篇一&#xff0c;前面提到在使用cube.AI生成的c语言神经网络模型API调用时&#xff0c;输入数据数量是24&#xff0c;输出数据数量是4&#xff0c;但上文设想采集了三轴加速度传感器的x/y/z三个各数据&#xff0c;按Jogging(慢跑),Walking(走了)两种态势采集了两…

Java链表OJ题

目录1. 删除链表中等于给定值val的所有结点2. 逆置单链表3. 链表的中间结点4. 链表中倒数第k个结点5. 将两个有序链表合并为一个新的有序链表6. 以给定值x为基准将链表分割成两部分7. 判断是否为回文链表8. 两个链表的第一个公共结点9. 判断链表中是否有环10. 链表开始入环的第…

【Linux】目录权限和默认权限

上期介绍了Linux的文件权限&#xff0c;这期我们仔细来说说Linux环境下目录权限和默认权限一、目录权限1.1 进入目录所需的权限我们在进入目录时需要什么样的权限呢&#xff1f;是r、w还是x呢&#xff1f;下面我们一起来验证一下&#xff1a;&#x1f4cb;如下我门拥有全部目录…

Day11 AOP介绍

1 前言AOP&#xff0c;Aspect Oriented Programming&#xff0c;面向切面编程&#xff0c;是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象&#xff0c;一个对象包括静态的属性信息&#xff0c;包括动态的方法信息等。而AOP是横向的对不同事物的抽象&#xff0c;属性与属…

【Python从入门到精通】第一阶段

文章目录前言python的起源打印hello world注释变量变量基本概念类型类型转换运算符字符串拓展字符串的三种定义方法字符串拼接字符串格式化数据输入input比较布尔类型和比较运算符if判断if elseif elif else嵌套循环while循环while循环嵌套for循环range()的使用函数的使用函数的…

3小时精通opencv(五) 利用TrackBar进行颜色检测

3小时精通opencv(五) 利用TrackBar进行颜色检测 参考视频资源:3h精通Opencv-Python 本章内容介绍如何利用TrackBar调节色域, 手动提取到我们需要的颜色 文章目录3小时精通opencv(五) 利用TrackBar进行颜色检测创建Trackbar色彩检测创建Trackbar 在opencv中使用createTrackbar函…

C语言:数组

往期文章 C语言&#xff1a;初识C语言C语言&#xff1a;分支语句和循环语句C语言&#xff1a;函数 目录往期文章前言1. 一维数组的创建和初始化1.1 数组的创建1.2 数组的初始化2. 一维数组的使用3. 一维数组在内存中的存储4. 二维数组的创建和初始化4.1 二维数组的创建4.2 二维…

大数据技术架构(组件)7——Hive:Filter PushDown Cases And Outer Join Behavior

1.2、Filter PushDown Cases And Outer Join Behavior前提:关闭优化器set hive.auto.convertjoinfalse; set hive.cbo.enablefalse;Inner Join:1、Join On中的谓词: 左表下推、右表下推2、Where谓词:左表下推、右表下推-- 第一种情况: join on 谓词 selectt1.user_id,t2.user_i…

C++函数定义和调用介绍

C函数定义和调用介绍 函数的意义&#xff1a;利用率高&#xff0c;可读性强&#xff0c;利于移植。 一个C程序中主函数有且只有一个&#xff0c;是程序的入口&#xff0c;而函数&#xff08;或称子函数&#xff09;可以有很多。 每个 C 程序都至少有一个函数&#xff0c;即主…

2021 XV6 8:locks

实验有两个任务&#xff0c;都是为了减少锁的竞争从而提高运行效率。Memory allocator一开始我们是有个双向链表用来存储空闲的内存块&#xff0c;如果很多个进程要竞争这一个链表&#xff0c;就会把效率降低很多。所以我们把链表拆成每个CPU一个&#xff0c;在申请内存的时候就…

栈和队列的应用

一、栈在括号匹配中的应用 数据结构之栈_迷茫中的小伙的博客-CSDN博客_数据结构之栈栈括号和队列的应用 二、栈在表达式求值中的应用 中缀转 ->后缀 &#xff1a; 左右先 (左边能先算,先算左边,因为这样可以保证确定性,即计算机运算的方式) 后缀转->中缀 &#xff1a…

王者荣耀入门技能树-解答

前言 前段时间写了一篇关于王者荣耀入门技能树的习题&#xff0c;今天来给大家解答一下。 职业 以下哪个不属于王者荣耀中的职业&#xff1a; 射手法师辅助亚瑟 这道题选&#xff1a;亚瑟 王者荣耀中有6大职业分类&#xff0c;分别是&#xff1a;坦克、战士、刺客、法师、…

如何好好说话-第12章 理清楚问题就是答案

生活中该不该积极主动与别人展开社交活动&#xff1f;有些时候社交活动并不开心&#xff0c;仅仅只是无聊的闲才。但他确实能拉拢人际关系&#xff0c;帮我们获得近身套路。而且有一种观点认为不善于社交的人是不成功的。注意以上说的这些都是偏见。当我们站在一个更高的维度认…

Jetpack架构组件库:Hilt

Hilt Hilt 是基于 Dagger2 的依赖注入框架&#xff0c;Google团队将其专门为Android开发打造了一种纯注解的使用方式&#xff0c;相比 Dagger2 而言使用起来更加简单。 依赖注入框架的主要作用就是控制反转&#xff08;IOC, Inversion of Control&#xff09;, 那么什么是控制…

表格相关的一些标签

<!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>表格相关的标一些签</title> </head> <body> <!-- 需求 1&#xff1a;做一个四行&#xff0c;三…