【C++STL】AVL树(更新中)

news2024/11/18 9:28:27

前言

二叉搜索树是具有特殊存储结构的树,任意根节点的左子树的所有节点值都比根节点的值小,右子树的所有节点值都比根节点大。
在这里插入图片描述
这种特殊的存储结构使得查找的效率大大提升,为logN。但是还有缺陷。
因为二叉搜索树的构建是一个节点一个节点的插入,每次插入会找到合适的位置,但如果插入的节点的大小是顺序插入的,就会出现歪脖子树
在这里插入图片描述
这样的查找效率还是N,就失去了特性。
而AVL树,平衡二叉搜索树就是在二叉搜索树的基础上,解决了歪脖子树这一特例的树。
接下来,我们就来学习AVL树

在这里插入图片描述

文章目录

  • 前言
  • 一. AVL树
  • 二. AVL树节点的插入
    • 1. 节点的定义
    • 2. 节点的插入
    • 3. 平衡因子更新
    • 4. 左单旋/右单旋
    • 5. 左右双旋
    • 6. 完整代码
  • 四. 完整代码
  • 结束语

一. AVL树

AVL树就是平衡二叉搜索树。为了解决歪脖子的二叉搜索树,我们规定二叉搜索树的每个节点的左右子树高度差的绝对值不超过1。
在这里插入图片描述
节点上的红色数字就是每个节点的高度
而左右子树的高度差就是拿左子树的高度-右子树的高度,或者右子树的高度-左子树的高度。
本篇博客规定,高度差是右子树-左子树。所以15节点的高度差是-1,6节点的高度差是0,7节点的高度差也是-1。AVL树存储的也是KV值
所有节点的高度差的绝对值都不大于1,那这棵树就是平衡的。

二. AVL树节点的插入

1. 节点的定义

AVL树的实现方式有很多种,本篇博客仅介绍一种。
节点的插入跟二叉搜索树的插入一致,但是当插入节点后,有节点的高度差不符合规定,那么我们需要对这棵树进行调整,所以首先我们可以有一个成员变量,存储当前节点的高度差,我们把它叫作平衡因子
高度的其中一种调整如下图
在这里插入图片描述
节点上黑色的数字是平衡因子,也就是高度差。
可以看到,在插入节点10后,7节点的高度差变成了2,不符合规定,需要调整,我们把这种调整称为旋转
所以新节点的插入会引起祖先节点的平衡因子的改变,所以我们需要回溯,为此我们使用三叉链的树结构。
即节点的定义中,有左孩子指针,右孩子指针,还有双亲指针

节点的定义如下:

//三叉链
template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K,V>*_left;//左指针
	AVLTreeNode<K,V>*_right;//右指针
	AVLTreeNode<K, V>*_parent;//双亲指针
	pair<K, V>_kv;//KV值
	int _bf;//平衡因子
	
	//构造
	AVLTreeNode(const pair<K,V>&kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		_bf(0)
	{

	}
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode Node;
private:
	Node*_root=nullptr;
};

这就是AVL树的基本结构。


AVL树,节点的插入同二叉搜索树。但是AVL树要保持平衡,所以在插入后还要根据情况调整节点。
所以AVL节点的插入可以分为3步
1.节点插入
2.平衡因子更新
3.旋转

2. 节点的插入

基本思路同二叉搜索树的节点插入,通过循环和二叉搜索树的性质,找到要插入的位置,然后父子链接,但因为是三叉链表,所以需要多一步对双亲指针的链接

bool Insert(const pair<K, V>&kv)
	{
		//头为空直接创建
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node*parent = nullptr;
		Node*cur = _root;

		//找到应插入的位置
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		//构建新节点
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//双亲指针的链接
		cur->_parent = parent;

		return true;
	}

3. 平衡因子更新

我们规定平衡因子是右子树的高度-左子树的高度
所以,如果插入节点是父亲节点的右节点,那么父亲节点的平衡因子就+1
是父亲节点的左节点,那么父亲节点的平衡因子就-1
而插入一个新节点,受影响的只有其祖先
在这里插入图片描述
如果插入4,那么影响的只有左图圈出的部分;如果插入13,那么影响的只有右图圈出的部分。
改变插入节点的父亲节点后,还要不要继续向上调整,有以下三种情况:

  1. 改变后,父节点的平衡因子变成1/-1,代表父节点的平衡因子原先是0
    因为原先父节点的平衡因子是0,变成1/-1后,说明高度变了,那么父节点所在子树也变了,所以需要继续向上更新
  2. 改变后,父节点的平衡因子变成2/-2
    虽然高度也变了,但是已经不平衡了。不需要继续向上更新,而是直接进行旋转,调整高度
  3. 改变后,节点的平衡因子变成0
    说明当前子树变得更平衡了不需要往上更新

插入节点之后更新平衡因子

//更新平衡因子
		while (parent)
		{
			//左减右加
			if (cur == parent->_left)
			{
				--parent->_bf;
			}
			else
			{
				++parent->_bf;
			}

			//判断是否需要继续向上更新
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				//需要继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//不需要继续更新,需要旋转

				//旋转:1.让AVL树变得更平衡  2.降高度

				break;
			}
			else if (parent->_bf == 0)
			{
				//更平衡了,不需要继续向上更新
				return true;
			}
			else
			{
				//出现别的情况,说明当先AVL树出问题了,直接报错
				assert(false);
			}
		}

4. 左单旋/右单旋

节点的插入和平衡因子的更新都完成后,当parent的平衡因子变成2/-2时,我们还需要根据情况进行不同的旋转

首先是单旋
我们先使用抽象图进行分析
在这里插入图片描述
我们举例h=0/1/2三种情况


在这里插入图片描述
在b位置插入也会改变高度,但不是左单旋,这里先不作讨论


在这里插入图片描述
在b位置插入不是左单旋,此处先不作讨论


在这里插入图片描述
h==2的时候,c的位置一定是x形的
证明如下
在这里插入图片描述
假设c是y形的,那么插入节点有这三种情况
第一种情况变得更平衡了,不需要单旋
第二种情况在子树就已经出现-2,子树就不是AVL树了,更新不到30,不符合
第三种情况同第二种
所以单旋中,c的位置一定是x形,而a/b是任意一种,所以h为2时,树的结构有9种,插入位置有4种,共36种可能,但是都可以利用左单旋解决


接下来,我们就来讲解左单旋的操作步骤
我们以h==1作例子
在这里插入图片描述

首先,b是比30大,比60小的节点
将b变成30的右孩子
再让30变成60的左孩子
最后再更新平衡因子
左单旋就结束了
我们用代码实现一下


我们将需要改变的节点定义一下
在这里插入图片描述

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

		//1. 将subRL变成parent的右节点
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		
		//记录当前子树的父节点
		Node*ppnode = parent->_parent;

		//2. 将parent变成subR的左节点
		subR->_left = parent;
		parent->_parent = subR;

		//3. 链接ppnode
		if (ppnode == nullptr)
		{
			//如果是ppnode是空,代表parent是根节点

			//更新根
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}

		//4. 更新平衡因子
		parent->_bf = 0;
		subR->_bf = 0;
	}

右单旋的原理根左单旋基本一致
在这里插入图片描述
subLR变成parent的左节点
再将parent变成subL的右节点
更新平衡因子
代码如下:

//右单旋
	void RotateR(Node*parent)
	{
		Node*subL = parent->_left;
		Node*subLR = subL->_right;
		//1.将subLR变成parent的左节点
		parent->_left = subLR;
		//subLR可能是NULL,不是NULL才链接
		if (subLR)
			subLR->_parent = parent;

		//2.再将parent变成subL的右节点
		Node*ppnode = parent->_parent;//因为parent不一定是根节点,所以需要记录爷爷节点
		subL->_right = parent;
		parent->_parent = subL;

		//3.链接parent指针
		if (ppnode == nullptr)
		{
			//如果是根节点
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//反之不是
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}

		//4.修改平衡因子
		//parent和subL的平衡因子都变成0
		parent->_bf=subL->_bf=0;
	}

左右单旋的使用时机是

if (parent->_bf == 2 && cur->_bf == 1)
{
	//右边比较高,左单旋
	RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
	//左边比较高,右单旋
	RotateR(parent);
}

5. 左右双旋

双旋的抽象图是这样的
在这里插入图片描述
接下来,我们照样分为h=0/1/2,三种情况分析


在这里插入图片描述
h==0的情况其实就是单旋时,在b位置插入节点的情况。此时如果只是左单旋,无法解决问题
在这里插入图片描述


在这里插入图片描述
同样,只左单旋无法解决问题


h==2的情况和单旋时讲解的类似

在这里插入图片描述

双旋

左右双旋
在这里插入图片描述

我们举h=1的情况,插入节点后,AVL树变得不平衡。
我们先对30进行左旋,将左边变得更高
然后再对90右旋,让右边变平衡
最后还需要更新平衡因子

因为旋转后的平衡因子不一定都为0,所以两次单旋后,还需要再更新平衡因子
在这里插入图片描述

有这样三种情况
对应的代码是这样的

//左右双旋
	void RotateLR(Node*parent)
	{
		Node*subL = parent->_left;
		Node*subLR = subL->_right;
		int bf = subLR->_bf;
		//先对subL进行左旋
		RotateL(subL);
		//再对parent右旋
		Rotate(parent);

		//更新平衡因子
		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 if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			//出现其他情况代表出问题了
			assert(false);
		}
	}

右左双旋
右左双旋就是先对subR右旋
再对parent左旋
最后更新平衡因子
对应代码如下:

//右左双旋
	void RotateRL(Node*parent)
	{
		Node*subR = parent->_right;
		Node*subRL = subR->_left;
		int bf = subRL->_bf;
		//先对subR进行右旋
		RotateR(subR);
		//再对parent左旋
		RotateL(parent);

		//更新平衡因子
		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			//出现其他情况代表出问题了
			assert(false);
		}
	}

二者的使用情况如下:

if (parent->_bf == 2 && cur->_bf == -1)
{
	//右左双旋
	RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
	//左右双旋
	RotateLR(parent);
}
else
{
	//出现别的情况
	assert(false);
}

6. 完整代码

	//插入节点
	bool Insert(const pair<K, V>&kv)
	{
		//头为空直接创建
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node*parent = nullptr;
		Node*cur = _root;

		//找到应插入的位置
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		//构建新节点
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//双亲指针的链接
		cur->_parent = parent;

		//更新平衡因子
		while (parent)
		{
			//左减右加
			if (cur == parent->_left)
			{
				--parent->_bf;
			}
			else
			{
				++parent->_bf;
			}

			//判断是否需要继续向上更新
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				//需要继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//不需要继续更新,需要旋转

				//旋转:1.让AVL树变得更平衡  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);
				}
				else
				{
					//出现别的情况
					assert(false);
				}

				break;
			}
			else if (parent->_bf == 0)
			{
				//更平衡了,不需要继续向上更新
				return true;
			}
			else
			{
				//出现别的情况,说明当先AVL树出问题了,直接报错
				assert(false);
			}
		}

		return true;
	}

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

		//1. 将subRL变成parent的右节点
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		
		//记录当前子树的父节点
		Node*ppnode = parent->_parent;

		//2. 将parent变成subR的左节点
		subR->_left = parent;
		parent->_parent = subR;

		//3. 链接ppnode
		if (ppnode == nullptr)
		{
			//如果是ppnode是空,代表parent是根节点

			//更新根
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}

		//4. 更新平衡因子
		parent->_bf = 0;
		subR->_bf = 0;
	}


	//右单旋
	void RotateR(Node*parent)
	{
		Node*subL = parent->_left;
		Node*subLR = subL->_right;
		//1.将subLR变成parent的左节点
		parent->_left = subLR;
		//subLR可能是NULL,不是NULL才链接
		if (subLR)
			subLR->_parent = parent;

		//2.再将parent变成subL的右节点
		Node*ppnode = parent->_parent;//因为parent不一定是根节点,所以需要记录爷爷节点
		subL->_right = parent;
		parent->_parent = subL;

		//3.链接parent指针
		if (ppnode == nullptr)
		{
			//如果是根节点
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//反之不是
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}

		//4.修改平衡因子
		//parent和subL的平衡因子都变成0
		parent->_bf=subL->_bf=0;
	}

	//左右双旋
	void RotateLR(Node*parent)
	{
		Node*subL = parent->_left;
		Node*subLR = subL->_right;
		int bf = subLR->_bf;
		//先对subL进行左旋
		RotateL(subL);
		//再对parent右旋
		RotateR(parent);

		//更新平衡因子
		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 if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			//出现其他情况代表出问题了
			assert(false);
		}
	}

	//右左双旋
	void RotateRL(Node*parent)
	{
		Node*subR = parent->_right;
		Node*subRL = subR->_left;
		int bf = subRL->_bf;
		//先对subR进行右旋
		RotateR(subR);
		//再对parent左旋
		RotateL(parent);

		//更新平衡因子
		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			//出现其他情况代表出问题了
			assert(false);
		}
	}

四. 完整代码

#pragma once
#include<iostream>
#include<cassert>
using namespace std;

//三叉链
template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>*_left;//左指针
	AVLTreeNode<K, V>*_right;//右指针
	AVLTreeNode<K, V>*_parent;//双亲指针

	pair<K, V>_kv;//KV值

	int _bf;//平衡因子   高度差    右子树高度-左子树高度
	
	//构造
	AVLTreeNode(const pair<K,V>&kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{

	}
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K,V> Node;

public:

	//插入节点
	bool Insert(const pair<K, V>&kv)
	{
		//头为空直接创建
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node*parent = nullptr;
		Node*cur = _root;

		//找到应插入的位置
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		//构建新节点
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//双亲指针的链接
		cur->_parent = parent;

		//更新平衡因子
		while (parent)
		{
			//左减右加
			if (cur == parent->_left)
			{
				--parent->_bf;
			}
			else
			{
				++parent->_bf;
			}

			//判断是否需要继续向上更新
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				//需要继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//不需要继续更新,需要旋转

				//旋转:1.让AVL树变得更平衡  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);
				}
				else
				{
					//出现别的情况
					assert(false);
				}

				break;
			}
			else if (parent->_bf == 0)
			{
				//更平衡了,不需要继续向上更新
				return true;
			}
			else
			{
				//出现别的情况,说明当先AVL树出问题了,直接报错
				assert(false);
			}
		}

		return true;
	}

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//任意节点的高度
	int Height(Node*root)
	{
		if (root == nullptr)
			return 0;

		int HeightL = Height(root->_left);
		int HeightR = Height(root->_right);

		return HeightL > HeightR ? HeightL + 1 : HeightR + 1;
	}

	//是否是平衡二叉树
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

private:

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

		//1. 将subRL变成parent的右节点
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		
		//记录当前子树的父节点
		Node*ppnode = parent->_parent;

		//2. 将parent变成subR的左节点
		subR->_left = parent;
		parent->_parent = subR;

		//3. 链接ppnode
		if (ppnode == nullptr)
		{
			//如果是ppnode是空,代表parent是根节点

			//更新根
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}

		//4. 更新平衡因子
		parent->_bf = 0;
		subR->_bf = 0;
	}


	//右单旋
	void RotateR(Node*parent)
	{
		Node*subL = parent->_left;
		Node*subLR = subL->_right;
		//1.将subLR变成parent的左节点
		parent->_left = subLR;
		//subLR可能是NULL,不是NULL才链接
		if (subLR)
			subLR->_parent = parent;

		//2.再将parent变成subL的右节点
		Node*ppnode = parent->_parent;//因为parent不一定是根节点,所以需要记录爷爷节点
		subL->_right = parent;
		parent->_parent = subL;

		//3.链接parent指针
		if (ppnode == nullptr)
		{
			//如果是根节点
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//反之不是
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}

		//4.修改平衡因子
		//parent和subL的平衡因子都变成0
		parent->_bf=subL->_bf=0;
	}

	//左右双旋
	void RotateLR(Node*parent)
	{
		Node*subL = parent->_left;
		Node*subLR = subL->_right;
		int bf = subLR->_bf;
		//先对subL进行左旋
		RotateL(subL);
		//再对parent右旋
		RotateR(parent);

		//更新平衡因子
		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 if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			//出现其他情况代表出问题了
			assert(false);
		}
	}

	//右左双旋
	void RotateRL(Node*parent)
	{
		Node*subR = parent->_right;
		Node*subRL = subR->_left;
		int bf = subRL->_bf;
		//先对subR进行右旋
		RotateR(subR);
		//再对parent左旋
		RotateL(parent);

		//更新平衡因子
		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			//出现其他情况代表出问题了
			assert(false);
		}
	}

	//中序遍历
	void _InOrder(Node*root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	//验证是否平衡
	bool _IsBalance(Node*root)
	{
		//空树也是平衡树
		if (root == nullptr)
			return true;

		//左右子树高度
		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);

		//还可以检测平衡因子更改是否正确
		if (rightHeight - leftHeight != root->_bf)
			cout << root->_kv.first << "节点平衡因子异常" << endl;

		//高度差
		int diff = rightHeight - leftHeight;

		if (diff != root->_bf || diff > 1 || diff < -1)
			return false;

		//任意节点都要平衡
		return _IsBalance(root->_left) && _IsBalance(root->_right);
	}
private:
	Node*_root=nullptr;
};

结束语

本篇博客没有详细讲解AVL树节点的删除,后续继续更新
删除本身跟插入类似,大致步骤是先找到节点,然后中和二叉搜索树的删除和AVL树的插入
如果删除的不是叶子节点,要考虑托孤,即找其他节点替代原先位置;然后再调节平衡因子
注意:平衡因子的调节,如果调节后parent的平衡因子为1或-1,说明高度没有变为0才说明高度改变,需要继续向上更新。

本篇内容到此就结束了,感谢你的阅读!

如果有补充或者纠正的地方,欢迎评论区补充,纠错。如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

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

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

相关文章

MySQL中去重 distinct 和 group by 是如何去重的

1&#xff1a;测试数据 CREATE TABLE student (stu_no VARCHAR(40) NOT NULL,name VARCHAR(100) NOT NULL );insert into student values(1,name1); insert into student values(2,name2); insert into student values(3,name1); insert into student values(4,name2); i…

【C++初阶】:类与对象(下)

类与对象 一.再谈构造函数1.初始化列表&#xff08;构造函数的一部分&#xff09;2.explicit关键字 二.static成员三.友元1.友元函数2.友元类 四.内部类五.匿名对象六.再次理解类与对象 一.再谈构造函数 1.初始化列表&#xff08;构造函数的一部分&#xff09; 我们可以直接在…

2023数维杯ABC题思路代码分析

已完成全部可以领取&#xff0c;详情看文末&#xff01;&#xff01;&#xff01; 数维杯A题思路 A题是这次比赛比较难的题目&#xff0c;地下水系统水体污染问题&#xff0c;他给出了我们一些行为特征&#xff0c;污染物的行为特征主要涉及对流迁移、水动力弥散、吸附及阻滞…

一、走进easyUI的世界

1.什么是easyUI&#xff1f; jQuery EasyUI是一组基于jQuery的UI插件集合体&#xff0c;而jQuery EasyUI的目标就是帮助web开发者更轻松的打造出功能丰富并且美观的UI界面。开发者不需要编写复杂的javascript&#xff0c;也不需要对css样式有深入的了解&#xff0c;开发者需要…

matlab实验三程序设计与优化

一、实验目的及要求 一、实验的目的与要求 1、掌握 MATLAB的函数 2、掌握 MATLAB的程序流 3、掌握 MATLAB脚本和函数文件的编写 4、熟悉基于矩阵的程序设计与优化 二、实验原理 1、MATLAB的M文件&#xff1a;脚本文件与函数文件&#xff1b; 2、MATLAB程序流&#xff1a;input…

【企业信息化】第2集 免费开源ERP: Odoo 16 销售管理系统

文章目录 前言一、概览二、使用功能1.通过清晰报价提高销售效率2.创建专业报价单3.管理订单及合同4.简化沟通5.维护产品&价格6.直观的报告7.集成 三、总结 前言 世界排名第一的免费开源ERP: Odoo 16 销售管理系统。通过Odoo Sign应用程序和在线支付&#xff0c;发送报价。…

“极乐净土”时隔7年再度席卷B站,二次元魂藏不住了!

七年前&#xff0c;日本知名歌手美依礼芽发行一曲《极乐净土》&#xff0c;搭配歌曲的宅舞视频燃起了众多二次元魂&#xff0c;翻跳投稿纷至沓来。 可以说&#xff0c;她是许多老二次元人的回忆&#xff0c;也是一个时代的标记。 当时&#xff0c;《极乐净土》横空出世&#…

为什么需要边缘计算?哪些场景需要边缘计算?

为什么需要边缘计算&#xff1f; 边缘计算&#xff08;Edge Computing&#xff09;是一种将数据处理和计算功能移到接近数据源头的边缘设备上进行的计算模式。相比传统的云计算模式&#xff0c;边缘计算能够在接近数据源头的地方进行实时的数据处理&#xff0c;这为计算机视觉…

前端vue3一键打包发布

一键打包发布可以分为两种&#xff0c;一是本地代码&#xff0c;编译打包后发布至服务器&#xff0c;二是直接在服务器上拉去代码打包发布至指定目录中。 两种各有使用场景&#xff0c;第一种是前端开发自己调试发布用的比较多&#xff0c;第二种是测试或者其他人员用的多&…

java 二维数组的定义及操作

二维数组的定义有很多方式&#xff1a; 第一种方式&#xff1a; 数据类型[][] 数组名 new数据类型[行的个数][列的个数]; 下面以第一种方式声明一个数组&#xff0c;如下所示。 int[][] xx new int[3][4]; 表示有三行&#xff0c;每行方四个数据第二种方式&#xff1a; 数据类…

甘特图控件DHTMLX Gantt入门使用教程【引入】:dhtmlxGantt 与Node.js(上)

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的大部分开发需求&#xff0c;具备完善的甘特图图表库&#xff0c;功能强大&#xff0c;价格便宜&#xff0c;提供丰富而灵活的JavaScript API接口&#xff0c;与各种服务器端技术&am…

Vue3-黑马(三)

目录&#xff1a; &#xff08;1&#xff09;vue3-基础-计算属性 &#xff08;2&#xff09; vue3-基础-xhr-基本使用 &#xff08;3&#xff09;vue3-基础-xhr-promise改造 &#xff08;1&#xff09;vue3-基础-计算属性 上面有重复的代码&#xff0c;用计算属性&#xff0…

Kali工具集简介

Kali Linux提供了数种经过定制的专门为渗透测试设计的工具。工具都会按下图中下拉选单所示的方式按组分类聚合。了解工具是做渗透测试第一个认知。 口Information Gathering(信息收集) 这些都是侦察工具,用来收集目标网络和设备的数据。在这类工具中,从找出设备的工具到查看使…

大数据分析案例-基于高斯朴素贝叶斯算法构建良恶性肿瘤识别器

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

时间复杂度和空间复杂度的七七八八

文章目录 时间空间复杂度1. 时间空间复杂度的重要性(作用)2. 时间复杂度和大O表示法1&#xff09;算法图解2&#xff09;代码随想录3&#xff09;王道《数据结构》 3. 大O指的是最糟的情形和一般的情形1&#xff09;大O表示的是一般情况&#xff0c;并不是严格的上界2&#xff…

算法修炼之练气篇——练气四层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

创建虚拟目录和用户访问控制+虚拟目录

目录标题 虚拟目录配置文件创建配置文件对应的目录资源在创建一个虚拟目录的配置文件 用户访问控制虚拟目录创建用户访问控制权限创建配置文件中的用户资源测试tom用户测试zhangsan用户 虚拟目录 Alias 虚拟目录名称 真是目录路径为了方便对于网站资源进行灵活管理&#xff0c…

使用 MVC 模式,实现简单登录功能 (Kotlin)

先放效果图&#xff1a; 第一张是登录页面效果图。用户输入登录名和密码&#xff0c;经过后台的非空验证和固定值验证&#xff0c;跳转到首页 第二张是首页效果图。用户点击 “update” 显示用户名和密码 这里的用户名和密码是后台设置的固定值&#xff0c;整体的登录逻辑特别…

python+opencv图像形态变换

形态变换主要用于二值图像的形状操作&#xff0c;形态变换的实现原理基于数字形态学。数字形态学也称形态学&#xff0c;它主要从图像内部提取信息来描述图像形态。形态学广泛应用于视觉检测、文字识别、医学图像处理、图像压缩编码等。形态变换主要包括腐蚀、膨胀和高级形态操…

Linux aarch64架构中使用docker安装mysql8

Linux aarch64架构中使用docker安装mysql8 1、遇到问题&#xff1a; 官网下载mysql包&#xff0c;安装完成后&#xff0c;启动mysql一直显示-bash: ./mysqld: 无法执行二进制文件。 网上找了各种资料&#xff0c;但是都没有作用&#xff0c;怀疑自己操作姿势不正确&#xff…