二叉树进阶(AVLTree)

news2024/11/24 12:55:24

目录

1.AVLTree概念

2.AVLTree模拟实现

2.1 AVLTree节点

2.2 插入实现基本框架

2.3 左单旋

2.4 右单旋

2.5 LR双旋

2.6 RL双旋

2.7 AVLTree树验证


1.AVLTree概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查

找元素相当于在一个链表中搜索元素,效率低下

于是有两位俄罗斯科学家提出了AVLTree的概念,来解决单支树的问题

在向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要

对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

可以证明,如果它有n个结点,其高度可保持在 O(logN),搜索时间复杂度也是O(logN)

这里直接给出AVL树(高度平衡搜索二叉树)的定义

1.每个节点的左右子树高度之差的绝对值小于等于1

2.左右子树都为AVL树

2.AVLTree模拟实现

2.1 AVLTree节点

AVLTree实现的方法很多,这里采用的是平衡因子的实现方法

平衡因子是指每个节点右子树高度减去左子树高度的值

通过在每个节点设置一个balance factor,简称bf,就可以将代码简化,方便AVLTree之后的建树过程

template <class K,class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	//每个节点中还存一个指向父节点的指针
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf; //balance factor

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};

2.2 插入实现基本框架

AVL树,是一棵高度平衡二叉搜索树

先是二叉搜索树,再是平衡

所以说建一棵AVL树,实际上和建一棵二叉搜索树的步骤完全一样,只不过需要考虑平衡因子的问

题,一旦平衡因子不对劲,我们就要相应作出调整,从而实现高度平衡

那现在往一棵树插入新节点,我们肯定就需要更新平衡因子

这里就只会出现两种情况

第一,更新完以后,平衡因子没有出现问题,即bf绝对值小于等于1,那平衡结果就没有受影响,不需要处理

第二,更新完以后,平衡出现问题,即bf绝对值大于1,那就要作相应的旋转处理

比如我们往下面这棵树插入一个新的节点10,9的平衡因子就肯定需要调整,由原来的0变成1

相应的8的平衡因子也需要调整,由原来的1,变为2

但是我们可以注意到的是,插入新增节点,只会影响祖先的平衡因子,也就是一条路径上的点,而

不是同一条路径的节点平衡因子是不会受到影响的

像上面树中的2,插入新节点10,对于2来说,是没有任何影响的,平衡因子还是2,别人只是吃瓜

群众,别人诛九族也轮不到它

插入新增节点后,它的父亲节点的bf肯定是需要改变的,插入在右边,那bf就加1;插入在左边,

那bf就减1,像图中的父亲节点9,新增节点插入在它的右边,bf就加1,从原来的0变为1

那爷爷是否需要调整呢?或者更往上的祖先节点的bf是否需要调整呢?

这就需要分情况讨论,判断是否需要往上继续更新,大白话来说,就是看祖先节点的左右子树是否

有变化?变了,肯定就需要更新,没变,就不需要更新

像图中新插入节点10来说,对于8这个节点,右子树高度变了,由1变2,另外一边的左子树的高度

没变,还是0,那自然它的bf就需要改变

但实操上,并不需要这么麻烦,直接看父节点调整完后的bf即可

因为父节点就在爷爷节点的左子树或者右子树之中

假如父节点调整完后的bf为0,说明之前一边高一边低,插入的新增节点刚好填上矮的那边,高度

没有变,那爷爷节点的bf肯定也不用变化,因为子树高度就没有变

那假如父节点调整完后的bf为1或者-1,说明插入前左右高度相等,现在有一边高,子树的高度改

变了,那对应爷爷节点的bf肯定也要变化

而最复杂的情况就是bf为2或者-2,此时子树就需要旋转调整,来保持仍然为一棵AVL树

至此,我们已经可以搭出插入的基本框架

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->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			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)
		{    
            //只要插入新节点,其父节点的bf肯定会改变
            //在右边就加1,在左边就减1
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
            
            //判断祖先节点的bf是否还需要改变
			if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 继续更新
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				

			    // 需要旋转处理 -- 1、让这颗子树平衡 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)
			    {
				    RotateLR(parent);
		    	}
			    else if (parent->_bf == 2 && cur->_bf == -1)
			    {
			    	RotateRL(parent);
		    	}
			    else
			    {   
                    //能来到这里,说明树之前就出了问题,本身就不是一棵AVL树
			    	assert(false);
		    	}
		}

		return true;
	}

现在我们差的就是旋转处理的实现,目的就是

让这棵子树重新平衡,同时降低这棵子树的高度 

在父节点的bf == 2或者bf == -2的前提下,我们可以单独将这棵子树抽离出来分析,可以分为两种

情况

bf == 2意味着新插入的节点在右子树

 bf == -2则意味着新插入的节点在左子树

但是情况还没有这么简单,新增的节点还可能插入在右子树的右边,或者是左边;在左子树的左边

或者是右边,这四种情况的旋转方式是不同的,具体原因,在后面分析的时候,我们就会有更深的

体会

2.3 左单旋

旋转处理目的是

让这棵子树重新平衡,同时降低这棵子树的高度

现在我们抽象出来这样一个右高左低的模型,调整为怎么样的模型,是降低这棵子树的高度呢? 

 答案是将它折下来,将中间的节点提上来,在保持它仍然是一棵搜索二叉树情况下,形成一个倒v

有了前面的铺垫,我们就可以看具体左单旋的场景(PS:注意此时的30和60这两个数字并不是绝对

意义上的60和30,只是说明60这个节点的值要大于30这个节点的值)

若把30称作parent节点,60称作为subR节点,很容易发现,当往c这棵子树新插入节点的时候,

parent对应的bf就由原来的1变为2,subR(cur)就由原来的0变为1,符合我们之前所定义的需要右

旋的情况

 那具体如何旋转呢?就是按照倒v模型

1.b变成30的右边

2.30变成60的左边

60变成整棵树的根

我们可以举一些具体的例子,毕竟高度为h的树a,b,c毕竟是抽象的

当h == 0时,a,b,c树都为空,新增节点直接插入到60上,按我们之前分析的方式进行旋转

完全可以达到旋转的目的

当h == 1时,a,b,c树都为一个节点,新增节点直接插入到c树的右边或者左边都行,都会导致

parent的bf为2,cur的bf为1,按我们之前分析的方式进行旋转,也完全可以达到旋转的目的

 当h == 2时,情况就会复杂很多,毕竟三个节点,能够组成的情况本身就有3种

不过a,b,c子树并不是任意取的,有一定的限制,即c树必须为x的形状,假如不是x的形状,那往c

树这边插入新节点,是不会导致parent的bf变为2,cur的bf变为1这个右旋前提的

最简单的比如,假如c是y的形状,那新增一个右节点,甚至不需要旋转,直接插入即可 

如果新增一个左节点,此时就要左旋或者左右双旋

而a,b子树可以为x,y,z中的任意一种,总共3*3*1 *4 (4个插入位置)= 36种情况

这里简单列举全部都为x形状的情况

可以看到倒V形仍然适用 

有了思路,代码实现起来就很轻松,按部就班链接节点即可

不过,还有几点需要注意

第一,链接的同时,记得节点的_parent也要调整

第二,subRL可能为空,所以在链接其父亲的时候,要先判别其是否为空,防止空指针解引用

第三,调整时未必是整棵树调整,所以还需要考虑链接会大树的问题

 

第四,平衡因子记得也要调整 

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

		//b变成30的右
		parent->_right = subRL;
		//父节点也需要调整,但subRL可能为空
		if (subRL)
			subRL->_parent = parent;

		//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNode
		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (ppNode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{   
			//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右
			//subR重新链接回爷爷的左或者右
			if (ppNode->_right == parent)
			{
				ppNode->_right = subR;
			}
			else
			{
				ppNode->_left = subR;
			}

			subR->_parent = ppNode;
		}

		//调整平衡因子
		parent->_bf = subR->_bf = 0;
	}

2.4 右单旋

右旋和左旋类似,不过采取的是另外一种左高右低的模型

 不过虽然模型不同,但旋转调整的目的依旧不变

让这棵子树重新平衡,同时降低这棵子树的高度

因此右旋的结果,依旧和左旋保持一致,将中间节点提上去,保证它依旧是搜索二叉树下,形成一

个倒V模型

右单旋的具体场景也和左单旋非常类似

 

 

若把60称作parent节点,30称作为subL节点,很容易发现,当往a这棵子树新插入节点的时候,

parent对应的bf就由原来的-1变为-2,subL(cur)就由原来的0变为-1,符合我们之前所定义的需要右

旋的情况

 

那具体如何旋转呢?就是按照倒v模型

1.b变成60的左边

2.60变成30的右边

30变成整棵树的根

同样的,左旋需要注意的点,右旋也同样要注意

第一,链接的同时,记得节点的_parent也要调整

第二,subRL可能为空,所以在链接其父亲的时候,要先判别其是否为空,防止空指针解引用

第三,调整时未必是整棵树调整,所以还需要考虑链接会大树的问题

第四,平衡因子记得也要调整 

//右旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//b变成60的左
		parent->_left = subLR;
		//父节点也需要调整,但subRL可能为空
		if (subLR)
			subLR->_parent = parent;

		//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNode
		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (ppNode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右
			//subL重新链接回爷爷的左或者右
			if (ppNode->_right == parent)
			{
				ppNode->_right = subL;
			}
			else
			{
				ppNode->_left = subL;
			}

			subL->_parent = ppNode;
		}

		//调整平衡因子
		parent->_bf = subL->_bf = 0;
	}

2.5 LR双旋

之所以会出现双旋概念,就是因为单纯的单旋无法解决某类模型

假如对这类模型进行左旋,充其量也仅仅是做一个镜像对称,没有解决任何问题

 

但上面得到的模型,显然没有满足我们旋转的目的

所以我们才需要采用双旋来解决

先对下面的两个节点来个左旋,变成我们最熟悉的模型,再对其进行右旋

即可变成我们想要的倒V模型,让这棵子树重新平衡,同时降低这棵子树的高度

 

具体的模型如下

 

若把60称作parent节点,30称作为subL节点,很容易发现,当往b,c这两棵子树任意位置新插入节

点的时候,parent对应的bf就由原来的-1变为-2,subL(cur)就由原来的0变为1,符合我们之前所定

义的需要LR双旋的情况

由于前面已经实现过左单旋,和右单旋,所以双旋直接赋用即可

先对30来个左旋

再对90来个右旋

我们遮住其中的变化过程,只看首尾变化的话,也可以进一步理解LR双旋具体如何操作

subLR当根,然后把它的左子树分给subL,将它的右子树分给parent,保证了它仍然是一棵搜索二叉树,并达到高度平衡 

不过虽然完成了调整,但双旋真正的难点在于平衡因子的调整,单纯的单旋,平衡因子都会被调整

为0,但是实际最后的平衡因子,按照图片也知道并不是0

往b插入新节点,和往c插入新节点,最后平衡因子的变化是不同的;h==0时,插入新节点,平衡

因子变化也是不同的,需要分类讨论

当往b插入新节点,b树的高度就为h,则subL的bf就为0,与之相对,parent的bf就要变为1

subLR的bf始终为0

当往c插入新节点,c树的高度就为h,则subL的bf就为-1,与之相对,parent的bf就要变为0

subLR的bf始终为0

当h == 0时,不需要调整,三个节点的bf都为0

//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//记录subLR初始的bf值,后续平衡因子调整需要使用
		int bf = subLR->_bf;
        //先对30来个左旋,再对90来个右旋
		//RotateL(subL);
		RotateL(parent->_left);
		RotateR(parent);

		//平衡因子调整有三种情况,一是b新增,二是c新增,三是h == 0的情况
		//c新增
		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		//b新增
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

2.6 RL双旋

RL双旋和LR双旋就是类似的操作了

它的提出也是因为单选无法解决这类模型,而且的的确确存在这种情况

对它直接进行右旋,只会得到一个镜像模型,当然可以先右旋,再LR双旋,理论上也是可以的

 

类似的,先对下面的两个节点来个右旋,变成我们最熟悉的模型,再对其进行左旋

即可变成我们想要的倒V模型,让这棵子树重新平衡,同时降低这棵子树的高度

具体的模型如下

 

类似操作,先对90左旋

  

再对30右旋

 

我们遮住其中的变化过程,只看首尾变化的话,也可以进一步理解RL双旋具体如何操作

subRL当根,然后把它的左子树分给parent,将它的右子树分给subR,保证了它仍然是一棵搜索二叉树,并达到高度平衡 

   

同样的,RL双旋的平衡因子都会被调整,依旧要对它进行分类讨论调整

当往b插入新节点,b树的高度就为h,则parent的bf就为0,与之相对,subR的bf就要变为1

subLR的bf始终为0

当往c插入新节点,c树的高度就为h,则subR的bf就为0,与之相对,parent的bf就要变为-1

subLR的bf始终为0

当h == 0时,不需要调整,三个节点的bf都为0


	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//记录subLR初始的bf值,后续平衡因子调整需要使用
		int bf = subRL->_bf;
		//先对30来个左旋,再对90来个右旋
		RotateR(parent->_right);
		RotateL(parent);

		//平衡因子调整有三种情况,一是b新增,二是c新增,三是h == 0的情况
		//c新增
		if (bf == 1)
		{
			parent->_bf = -1;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		//b新增
		else if (bf == -1)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

2.7 AVLTree树验证

验证一棵二叉树为AVL树,有三个方面需要验证

1.高度差是否正确

2.每个节点的平衡因子是否正确

3.是否为一棵二叉搜索树,中序遍历是否有序

template <class K,class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
//每个节点中还存一个指向父节点的指针
AVLTreeNode<K, V>* _parent;
pair<K, V> _kv;
int _bf; //balance factor

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->_right;
		}
		//假如插入的值,比当前节点的值小,则往左移动
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	//找到对应插入新节点的位置,建节点
	cur = new Node(kv);
	//将节点链接到AVLTree上
	if (parent->_kv.first > cur->_kv.first)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;

	//更新因子
	while (parent)
	{
		//插入新增节点,会影响祖先的平衡因子
		//首先父亲节点的平衡因子肯定会改变
		if (cur == parent->_right)
		{
			parent->_bf++;
		}
		else
		{
			parent->_bf--;
		}

		//考虑是否要向上更新的问题
		//而是否要向上更新,按照AVLTree的规则就是看爷爷节点的bf是否被破坏
		//假如没有变了,则需要更新;没变,则不需要更新
		if (parent->_bf == 1 || parent->_bf == -1)
		{   
			//继续向上更新
			parent = parent->_parent;
			cur = cur->_parent;
		}
		else if (parent->_bf == 0)
		{
			break;
		}
		else if(parent->_bf == 2 || parent->_bf == -2)
		{
			// 需要旋转处理 -- 1、让这颗子树平衡 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)
			{
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			else
			{
				assert(false);
			}

			break;
		}
		else
		{
			assert(false);
		}
	}
	return true;
}
//中序遍历
void Inorder()
{
	_Inorder(_root);
	cout << endl;
}
//验证AVLTree是否建成功
bool IsBalance()
{
	return _IsBalance(_root);
}
//求树的高度
int Height()
{
	return _Height(_root);
}
private:
int _Height(Node* root)
{
	if (root == NULL)
		return 0;
	int leftH = _Height(root->_left);
	int rightH = _Height(root->_right);
	return leftH > rightH ? leftH + 1 : rightH + 1;
}
//判断右子树和左子树高度差绝对值是否小于1
bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;

	int leftH = _Height(root->_left);
	int rightH = _Height(root->_right);

	//平衡因子是否更新正确
	if (rightH - leftH != root->_bf)
	{
		cout << root->_kv.first << "节点平衡因子异常" << endl;
		return false;

	}
	//从下往上,每一棵子树都是一棵平衡二叉树,则就是一棵平衡二叉树
	return abs(rightH - leftH) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}
//左旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	//b变成30的右
	parent->_right = subRL;
	//父节点也需要调整,但subRL可能为空
	if (subRL)
		subRL->_parent = parent;

	//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNode
	Node* ppNode = parent->_parent;

	subR->_left = parent;
	parent->_parent = subR;

	if (ppNode == nullptr)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{   
		//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右
		//subR重新链接回爷爷的左或者右
		if (ppNode->_right == parent)
		{
			ppNode->_right = subR;
		}
		else
		{
			ppNode->_left = subR;
		}

		subR->_parent = ppNode;
	}

	//调整平衡因子
	parent->_bf = subR->_bf = 0;
}

//右旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	//b变成60的左
	parent->_left = subLR;
	//父节点也需要调整,但subRL可能为空
	if (subLR)
		subLR->_parent = parent;

	//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNode
	Node* ppNode = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

	if (ppNode == nullptr)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右
		//subL重新链接回爷爷的左或者右
		if (ppNode->_right == parent)
		{
			ppNode->_right = subL;
		}
		else
		{
			ppNode->_left = subL;
		}

		subL->_parent = ppNode;
	}

	//调整平衡因子
	parent->_bf = subL->_bf = 0;
}

//左右双旋
void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	//记录subLR初始的bf值,后续平衡因子调整需要使用
	int bf = subLR->_bf;
    //先对30来个左旋,再对90来个右旋
	//RotateL(subL);
	RotateL(parent->_left);
	RotateR(parent);

	//平衡因子调整有三种情况,一是b新增,二是c新增,三是h == 0的情况
	//c新增
	if (bf == 1)
	{
		parent->_bf = 0;
		subLR->_bf = 0;
		subL->_bf = -1;
	}
	//b新增
	else if (bf == -1)
	{
		parent->_bf = 1;
		subLR->_bf = 0;
		subL->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		subLR->_bf = 0;
		subL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

//右左双旋
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	//记录subLR初始的bf值,后续平衡因子调整需要使用
	int bf = subRL->_bf;
	//先对30来个左旋,再对90来个右旋
	RotateR(parent->_right);
	RotateL(parent);

	//平衡因子调整有三种情况,一是b新增,二是c新增,三是h == 0的情况
	//c新增
	if (bf == 1)
	{
		parent->_bf = -1;
		subRL->_bf = 0;
		subR->_bf = 0;
	}
	//b新增
	else if (bf == -1)
	{
		parent->_bf = 0;
		subRL->_bf = 0;
		subR->_bf = 1;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		subRL->_bf = 0;
		subR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

void _Inorder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

	_Inorder(root->_left);
	cout << root->_kv.first << " ";
	_Inorder(root->_right);
}
private:
Node* _root = nullptr;
};

 分别用两组测试代码进行测试

第一组为一固定的具体值,首先通过它来看代码整体是否有误

void Test_AVLTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		/*	if (e == 14)
			{
			int x = 0;
			}*/

		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.Inorder();
	cout << t1.IsBalance() << endl;
}

对应结果

  

第二组为多组随机值,如果能够全部每次通过,则代码整体没有问题

void Test_AVLTree2()
{
	srand(time(0));
	const size_t N = 100000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	//cout << t.Height() << endl;
}

对应结果,由于数据过大,所以这里没有采取中序遍历全部打印

 

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

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

相关文章

怎么从电影中截取动图?试试这个工具

图片、视频等都是当代流行的表达情感、传递信息的一种方式。其中&#xff0c;当属gif动图最受大众的欢迎&#xff0c;它比普通的静态图片画面丰富&#xff0c;又比视频的体积小。那么&#xff0c;如何从视频中截取动图呢&#xff1f;使用GIF中文网的视频转gif&#xff08;https…

通过platform实现阻塞IO来驱动按键控制LED灯的亮灭

通过platform阻塞IO来驱动按键控制LED灯的亮灭 a .应用程序通过阻塞的io模型来读取number变量的值 b.number是内核驱动中的一个变量 c .number的值随着按键按下而改变&#xff08;按键中断)例如number0按下按键number1 ,再次按下按键number0 d .在按下按键的时候需要同时将…

【Leetcode】42.接雨水(困难)

一、题目 1、题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例1: 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6…

雪花算法 — 集群高并发情况下如何保证分布式唯一全局ID生成?

雪花算法 问题 为什么需要分布式全局唯一ID以及分布式ID的业务需求 在复杂分布式系统中&#xff0c;往往需要对大量的数据和消息进行唯一标识&#xff1a; 如在美团点评的金融、支付、餐饮、酒店猫眼电影等产品的系统中数据逐渐增长&#xff0c;对数据库分库分表后需要有一…

接口测试辅助,Fiddler抓取安卓手机https请求(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Fiddler 是一款免…

Java设计模式之行为型-迭代器模式(UML类图+案例分析)

目录 一、基础概念 二、UML类图 三、角色设计 四、案例分析 五、总结 一、基础概念 迭代器模式是一种常用的设计模式&#xff0c;它主要用于遍历集合对象&#xff0c;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露该对象的内部表示。 举个简单的…

第二章:Cyber RT通信机制解析与实践

Cyber RT解析与实践 第二章&#xff1a;Cyber RT通信机制解析与实践 Cyber RT解析与实践 Cyber RT解析与实践一、Cyber RT 通讯机制简介1. 话题2. 服务3. 参数 二、数据通信基础Protobuf1. Protobuf 简介2. Protobuf 创建3. Protobuf 编译4. Protobuf 案例实战 三、Cyber RT 话…

CPU性能指标简览

作为计算机的运算核心和控制核心&#xff0c;CPU&#xff08;Central Processing Unit&#xff09;由运算器、控制器、寄存器和实现其之间联系的数据、控制及状态的总线构成&#xff0c;决定着计算机运算性能强弱。作为信息技术产业的核心基础元器件&#xff0c;CPU的运作可分为…

3-40V输入,2.7V启动,20A电流,PWM\模拟信号调光

应用说明&#xff1a; Hi600X 是一系列外围电路简洁的宽调光比升压恒流驱动器&#xff0c;适用于 3-40V 输入电压范围的 LED 照明领域。 Hi600X 系列芯片&#xff0c;2.7V 启动电压&#xff0c;工作电压范围 5-40V&#xff0c;VIFB反馈电压 0.2V&#xff0c;提高整体转换效率。…

JVM的类加载机制和垃圾回收机制

目录 类加载机制类加载机制的步骤加载验证准备解析初始化 双亲委派模型工作原理双亲委派模型的优点 垃圾回收机制死亡对象的判断可达性分析算法可达性分析算法的缺点引用计数算法循环引用问题 垃圾回收算法标记-清除算法复制算法标记-整理算法分代算法 类加载机制 对于任意一个…

使用 vector + 递归 解决问题

class Solution {const char* To[10] { "","", "abc", "def", "ghi","jkl", "mno", "pqrs","tuv", "wxyz" };//常量字符串初始化string 注意此非定义(缺省值)--实例化…

收好这5个SVG编辑器,轻松实现高效创作

随着设计工作的不断发展&#xff0c;对SVG图形的需求也逐渐增加&#xff0c;SVG编辑器就随之诞生。可市面上的SVG编辑质量参差不齐&#xff0c;设计师无法第一时间找到适合自己的&#xff0c;于是本文就收集整理了5款相对来说比较好用的SVG编辑器为设计师们推荐&#xff0c;一起…

java项目之个人网站(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的个人网站。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&#xf…

golang macaron静态资源访问配置

1、本地文件目录如下&#xff1a; 2、macaron配置 package mainimport ("log""net/http""gopkg.in/macaron.v1" )func myHandler(ctx *macaron.Context) string {return "the request path is: " ctx.Req.RequestURI }func main() …

基于linux下的高并发服务器开发(第一章)- Makefile(2)1.11

03 / 工作原理 ◼ 命令在执行之前&#xff0c;需要先检查规则中的依赖是否存在  如果存在&#xff0c;执行命令  如果不存在&#xff0c;向下检查其它的规则&#xff0c;检查有没有一个规则是用来生成这个依赖的&#xff0c;如 果找到了&#xff0c;则执行该规则中…

PowerShell 报错:因为在此系统上禁止运行脚本。有关详细信息

PowerShell 报错&#xff1a;因为在此系统上禁止运行脚本。有关详细信息 Import-Module : 无法加载文件 D:\\Documents\WindowsPowerShell\Modules\posh-git\1.1.0\posh-git.psm1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.…

k8s 查看程序日志输出

总是和k8s大交道&#xff0c;把相关的命令也学习一下 查看程序日志输出&#xff1a; kubectl logs -f ce202307130100024-gy-decision-xkygl-64f795ff7b-mtr67 --tail1000 不重定向的情况下&#xff0c;可以把多进程下的日志都输出出来。挺好用的 kubectl logs -f pod --tailxx…

稳定币成关键指标,DeFi蓝筹拥抱RWA!下轮牛市会由什么叙事掀起?

从上海升级、BRC20、Meme 乃至老调重弹的减半叙事&#xff0c;每一次热炒都会激起新的市场关注。上半年&#xff0c;稳定币市场经历了一波格局调整&#xff0c;不少 DeFi 项目也出现了一些引人注目的叙事。下轮牛市&#xff0c;会是新一轮“DeFi盛夏”吗&#xff1f; 作为一个半…

如何编写测试用例

用例的五个构成元素&#xff1a; 下面从这五个元素的角度&#xff0c;去剖析如何编写测试用例 用例标题 用例标题就是测试点名称。用例标题是用来说明这个用例的测试目的的&#xff0c;好的用例标题是别人看完你这个用例标题后就知道你这个用例是测什么的。但并不是标题越详…

(自动机)+(卡常)+(前缀和)

edu151 C. Strong Password 样例&#xff1a; s123412341234 m2 s11234 s24321nt[i][j]:有了j以后找下一个数的位置的坐标。void solve() {cin>>s;ll lens.size();vector<array<ll,10>>nt(len1);nt[len].fill(len1);for(int ilen-1;i>0;i--){nt[i]nt[i1]…