【C++STL】AVL树

news2024/11/16 22:39:57

前言

二叉搜索树是具有特殊存储结构的树,任意根节点的左子树的所有节点值都比根节点的值小,右子树的所有节点值都比根节点大。
在这里插入图片描述
这种特殊的存储结构使得查找的效率大大提升,为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/504699.html

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

相关文章

django 过往后端搭建笔记整理 (2)--rest_framework视图使用

rest_framework视图使用 CreateAPIView---单独执行post上传逻辑views.pyuser_serializers.py CreateAPIView—单独执行post上传逻辑 基本逻辑&#xff1a; ① 准备视图函数类 ② 准备序列化器类 &#xff08;其中可以自定义如何序列化数据、序列化哪些数据&#xff0c;以及如何…

苹果最近删除的照片删除怎么恢复?专业人士都这样恢复照片!

案例&#xff1a;苹果照片删除了怎么恢复&#xff1f; 【友友们&#xff0c;最近不小心清空了手机照片&#xff0c;在最近删除没有看到&#xff0c;有什么方法可以找回来吗&#xff1f;】 随着现代科技的不断发展&#xff0c;手机已经成为人们生活中必不可少的物品&#xff0c…

设计模式 -- 享元模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

以CSDN为例,用Charles抓浏览器接口

0 描述 很多网站&#xff0c;都禁止F12&#xff0c;这就给抓包设置了门槛&#xff0c;虽然解决的方法有很多&#xff0c;今天尝试使用工具试一试&#xff0c;以CSDN热榜为例&#xff0c;直接转python代码 1 工具下载 工欲善其事&#xff0c;必先利其器 这个我就不多说&#x…

MySql -- 为什么使用B+树做索引

再有人问你为什么MySQL用B树做索引&#xff0c;就把这篇文章发给她 本文是参考上述文章进行总结和补充,大家感兴趣也可以阅读原文. 目录 1. 索引 2. 二叉搜索树 3. 平衡二叉树 4. B树 5. B树 6.扩展 -- 聚集索引 VS 非聚集索引 1. 索引 概念:索引是一种特殊的文件&#xff…

工业4.0,无代码改变软件开发格局,数字化转型不再寸步难行

从工业1.0时代到工业4.0时代&#xff0c;我们已经看到了&#xff0c;中国在不断发展的过程中&#xff0c;面临着很多的挑战&#xff0c;企业也面临着很多的困难。在这样的大背景下&#xff0c;传统的开发方式已经不再适用了。很多企业开始寻求一种新的方式来开发软件&#xff0…

【场景削减】基于DBSCAN密度聚类风电-负荷确定性场景缩减方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

1688阿里巴巴中国站图片识别商品API接口、搜图链接、收藏加购接口

API&#xff08;Application Programming Interface&#xff09; 是现代移动应用程序开发和互联网服务有机结合的产物。API的应用使得应用程序之间的通信变得更加轻松、快捷&#xff0c;尤其对于业务复杂而庞大的企业系统&#xff0c;API让开发者能够从中提取必要的功能进行二次…

图表控件Stimulsoft 2023.2 带来极致深色主题, 一起来看看还有哪些亮点?

Stimulsoft Reports 是一款报告编写器&#xff0c;主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署&#xff0c;如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等&#xff0c;在你的应用程序中嵌入报告设计器…

IS215UCVEH2AB有助于在更远距离传输电力的同时实现更好的转换效率。

​ IS215UCVEH2AB有助于在更远距离传输电力的同时实现更好的转换效率。 根据法拉第电磁感应定律&#xff0c;当交变磁场作用于具有磁性的材料时&#xff0c;材料中会感应出电动势。 由于导电材料的磁性&#xff0c;EMF 的电流围绕磁性材料主体旋转。这种旋转电流称为涡流。当导…

相交链表问题

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…

java版spring cloud 企业电子招投标采购系统源码之首页设计

随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审计监督要…

通过MyBatis(下)

作者&#xff1a;~小明学编程 文章专栏&#xff1a;spring框架 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 对MyBatis进行单元测试 springboot的单元测试 生成单元测试类 MyBatis中的增删查改 增 删 查 改 ${} 和 #{} 的区别 SQL注入 模糊查询…

低版本CUDA安装/多版本切换/用户级CUDA安装

前言&#xff1a;我想要安装Torch V1.7.1&#xff0c;根据版本信息只有cuda10.2和11.0。但是&#xff0c;本地安装的CUDA版本为11.6和11.2&#xff0c;都不能满足需求。因此需要降低CUDA版本为10.2 本机系统信息 CUDA版本和显卡驱动 nvidia-smi目前CUDA版本为11.6&#xff0c…

7万字省级智慧农业大数据平台项目规划建设方案

1.1 系统总体结构和逻辑结构 XX市智慧农业项目数据中心是全省数据处理加工和数据分析应用的中心&#xff0c;总体上需实现上连省农业厅、下连各级农业、外连市级部门&#xff1b;构建资源整合、互联互通、资源共享的全省统一的数据中心资源库&#xff1b;构建完善的底层支撑平…

Excel常用快捷键,你也可以成为大家口中的大神

掌握复制、粘贴&#xff0c;相信每个职场人都能骄傲地说“自己熟练使用Excel”&#xff0c;果真如此不害臊吗&#xff1f;不出10秒钟&#xff0c;肯定被面试官问得哑口无言。快捷键太多&#xff0c;记住几个常用的&#xff0c;收藏起来&#xff0c;需要的时候过来查看。 “学E…

软考高级架构师-1计算机硬件

目录 1. 前言 & 更新2. CPU组成3.存储器4. 总线1. 前言 & 更新 计算机硬件章节19-21年没考过,在22年真题考过磁盘调度,根据趋势分析,以后考的概率也不大,了解即可。 本节删掉了第一版中的编码、海明码等内容。 2. CPU组成 计算机的基本硬件系统由控制器、运算器…

日常节省 30%计算资源:阿里云实时计算 Flink 自动调优实践

摘要&#xff1a;本文整理自阿里云开发工程师&#xff0c;Apache Flink Contributor 钟旭阳&#xff0c;在 Flink Forward Asia 2022 生产实践的分享。本篇内容主要分为四个部分&#xff1a; 1. 历史背景 2. 框架简介 3. 案例介绍 4. 未来规划 Tips&#xff1a;点击「阅读原文」…

粘包/拆包问题一直都存在,只是到TCP就拆不动了。

• OSI open-system-Interconnection• TCP/IP 5层协议栈 • 应用层和操作系统的边界是 系统调用 &#xff0c;对应到网络编程是socket api• TCP/UDP 概况• TCP粘包问题• 结合TCP/IP报头再回顾&#xff0c;柳暗花明 OSI开放系统互联 定义了网络框架&#xff0c;以层为单位实…

【VSLAM】ORB-SLAM3安装部署与运行

心口如一&#xff0c;犹不失为光明磊落丈夫之行也。——梁启超 文章目录 :smirk:1. ORB-SLAM3介绍:blush:2. 代码安装部署1. 安装ros与opencv2. 安装Pangolin作为可视化和用户界面3. 安装Eigen3一个开源线性库&#xff0c;可进行矩阵运算4. 安装ORB-SLAM3 :satisfied:3. 案例运…