【数据结构】【C++】平衡搜索二叉树的模拟实现(AVL树)

news2024/11/20 14:33:39

【数据结构】&&【C++】平衡搜索二叉树的模拟实现(AVL树)

  • 一.AVL树的性质
  • 二.AVL树的模拟实现
      • ①.AVL树结点的定义
      • ②. AVL树的插入
      • ③.平衡因子的更新
      • ④.左单旋
      • ⑤.右单旋
      • ⑥.双旋(左右旋/右左旋)
      • ⑧.AVL树的删除
      • ⑨.检查是否是AVL树
  • 三.完整代码

一.AVL树的性质

AVL树就是平衡搜索二叉树。因为搜索二叉树虽然可以提高查找效率,但当数据有序时,具有缺陷,有可能会退化成单支树,查找元素就相当于在线性表中查找。效率就降低变成O(N)级别。因此为了解决上面的问题,有人发明AVL树来解决上面的问题。
方法:当向二叉搜索树中插入新结点时,如果能保证每个结点的左右子树的高度差的绝对值不超过1.那么这样就可以形成一个高度平衡的树,因为这样可以降低树的高度,从而减少平均搜索长度。

【性质】:

1.AVL树可以是一颗空树
2.或者是一颗平衡的二叉搜索树
3.如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。

如何理解平衡?

1.它的子树都是AVL树
2.它的所有左右子树的高度差的绝对值不超过1.可以是-1,可以是0,可以是1.
3.左右子树的高度差也叫做平衡因子bf。
4.在每次插入结点之前,这颗树就是AVL树。

在这里插入图片描述

二.AVL树的模拟实现

①.AVL树结点的定义

AVL树其实就在搜索二叉树的基础上加了平衡因子。不过要注意的是这颗结点是三叉链,多出一个父指针。这个父指针是方便用来向上更新平衡因子的。
正常来说搜索树是K结构(存一个数据),不过也可以是KV结构的(存两个数据)。我们这里直接用KV结构。也就是结点里存的是两个数据一个K,一个V。
而我们可以直接用pair类来存这个两个数据。然后直接将pair类对象存在结点里。
所有结点里存的是pair类的数据。然后还有左右指针和父指针,还有平衡因子。

template <class K, class V>

struct NodeTree
{
	int _bf;//平衡因子
	pair<K, V> _kv;//结点里存的数据
	NodeTree<K, V>* _left;
	NodeTree<K, V>* _right;
	NodeTree<K, V>* _parent;//为什么要多增加一个父指针呢?为了后面往上更新平衡因子方便

	NodeTree(const pair<K, V>& kv)//构造函数
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{ }

};

②. AVL树的插入

AVL树的插入其实就是在搜索树的插入基础上加上了更新平衡因子这一步。
前面都是和搜索树的插入是基本一样的(区别在于还要调整父指针)

bool Insert(const pair<K, V>& kv)
	{
		//AVL树的插入就是搜索树的插入+更新平衡因子
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		//说明该二叉树不是空树,那么就进行比较找到位置
		Node* cur = _root;
		Node* parent = nullptr;
		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为空了,表明位置已经找到了
		cur = new Node(kv);

		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//注意这个是三叉链,还要注意父指针
		cur->_parent = parent;

AVL最核心的步骤就在于平衡因子的调整!
当插入一个结点时,插入结点的平衡因子肯定为0。

1.当插入结点在父节点的右边时,那么父节点的平衡因子bf就要++。
2.当插入结点在于父节点的左边时,那么父节点的平衡因子bf就要–。

在这里插入图片描述

3.当父节点的平衡因子等于0时,这说明原来的父结点的平衡因子原来肯定是1或者-1,然后插入一个结点后,parent的平衡因子加加或者减减变成0了。弥补上parent的另外一个缺漏的结点。但要注意该子树的高度并没有变化。只是在另外一端加上一个结点。这个时候子树的高度没有改变,那么就不会影响上面的祖先的平衡因子的改变,就不需要往上更新。
4.当父结点的平衡因子等于1或者-1时,这说明原来的父节点的平衡因子一定是0,然后插入一个结点后,parent的平衡因子加加或者减减变成1或者-1。一旦平衡因子变成-1或者1,就说明这颗子树的高度改变了,子树的高度改变了就会影响祖先的高度,就会影响祖先的平衡因子,所有就要往上继续更新平衡因子。
5.向上更新平衡因子后,如果parent的平衡因子等于-2或者2,这说明这颗树"生病"了。不健康了,也就是parent所在的这颗子树高度严重不平衡了,正常的AVL树的高度差是的绝对值不超过1。那么我们就要对这颗子树进行调整。利用旋转让子树平衡。
6.当更新到根节点后,就不要再往上更新了。

在这里插入图片描述

③.平衡因子的更新

平衡因子的更新需要理解下面的更新思路:
在这里插入图片描述

        while (parent)//往上不断更新,直到更新到根结点,根结点上面就不需要更新了
		{
			if (cur == parent->_left)//当插入的结点在parent结点的左边时,parent的平衡因子减减
			{
				parent->_bf--;
			}
			else//当插入的结点在parent结点的左边时,parent的平衡因子加加
			{
				parent->_bf++;
			}
			if (parent->_bf == 0)//更新后,如果parent的平衡因子等于0,说明这颗子树的高度并没有改变,不需要更新平衡上面的平衡因子了。直接出去
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)//更新后,如果parent的平衡因子等于1或者-1,说明这棵树的高度发生变化,需要往上更新那写祖先结点的平衡因子
			{
				//说明该子树高度改变,会影响祖先,需要往上更新平衡因子
				cur = parent;
				parent = parent->_parent;
			
			}
			else if (parent->_bf == 2 || parent->_bf == -2)//更新后,如果parent的平衡因子等于-2或者2,说明这颗子树的高度已经严重不平衡了,需要我们使用旋转的手段来让它平衡!
			{
				//说明这个树生病了,需要调整,旋转,将树平衡
				//要求旋转后,仍然是一个颗搜索树
				// 旋转后平衡,高度变低
	            … …… …… ……
	            … …… …… ……
			else//如果还出现其他情况,就说明在插入之前这颗树就已经不平衡了,但还是要判断一下。
			{
					assert(false);
			}
			
		}
		return true;
	}

当parent的平衡因子为2或者-2,说明这个树生病了高度不平衡了,需要调整,旋转,将树平衡。
而旋转时需要注意下面的细节:
1.旋转后仍然保持是搜索树。
2.旋转后变平衡,高度会降低。

那怎么旋转呢?
旋转的情况有很多种,有要左单旋的,有需要右单旋的,甚至要双旋。
我们一一来分类研究:

④.左单旋

什么情况会发生左旋呢?

①当单纯的右边高时,当parent更新到2时就会发生左旋!
②即parent的平衡因子为2,cur的平衡因子是1,这样就是单纯的右边高。

在这里插入图片描述
所以左旋的核心就是两个步骤:

①核心就是让cur的左结点成为parent的右结点,
②让parent成为cur的左结点。

旋转细节:

①首先要找到cur的位置和cur的左节点curleft。
②将cur的左节点curleft成为parent的右结点,要注意要将curleft的父指针也要指向parent(在curleft结点存在的前提下)
③将parent成为cur的左结点,要注意将parent的父指针改成cur。
④最后cur成为父结点位置,也要注意调整cur的父指针

这样做是因为要求旋转后还要保持是搜索树!

在这里插入图片描述
在这里插入图片描述

左单旋之后,cur结点就会充当父结点,而旋转后高度会降低,并且cur和parent的平衡因子都为0!
不过要注意这里的图形可能只是一颗子树,而不是整棵树,所以当cur为父节点时,cur的父指针有两种情况,一种就是原来parent的位置就是根结点,即parent的父指针就是空。那么cur的父指针就是空。当原来的parent的位置不是根结点,原来的parent的父指针就是cur的父指针。

//    不平衡的树情况有很多种
	if (parent->_bf == 2 && cur->_bf == 1)//这种情况是单纯右边高,左单旋即可解决
	{
		RotateL(parent);
	}
	    void RotateL(Node* parent)//左单旋
	{
	
		Node* cur = parent->_right;
		Node* pp = parent->_parent;//要提前记录parent的父指针,因为最后cur位置成为父结点的位置,那么cur的父指针是谁呢?需要用pp来判断
		Node* curleft = cur->_left;
		parent->_right = curleft;
		if (curleft)//如果cur的左节点不存在那就不用弄
		{
			curleft->_parent = parent;//调整父指针
		}
		cur->_left = parent;
	
		parent->_parent = cur;//调整父指针
	
		
	
		if (parent==_root)
		{
			//那么这样cur就是根结点了
			_root = cur;
			cur->_parent = nullptr;
		}
		else//cur就不是根结点,pp是它的父指针,要根据原来parent的位置来确定cur在pp的左边还是右边
		{
			if (pp->_left == parent)
			{
				pp->_left = cur;
			}
			else
			{
				pp->_right = cur;
			}
	
			cur->_parent = pp;
		
		}
		cur->_bf = parent->_bf = 0;
			//旋转后cur和parent bf都为0
	}

⑤.右单旋

什么情况会发生右旋呢?

①当单纯的左边高时,当parent更新到-2时就会发生右旋!
②即parent的平衡因子为-2,cur的平衡因子是-1,这样就是单纯的左边高。

在这里插入图片描述

右旋的核心步骤

①将cur的右边给parent的左边。
②将parent作为cur的右边

右旋的细节:

①首先找到cur的位置,和cur的右结点curright。
②将cur的右边curright给parent的左边,要注意将curright的父指针也要指向parent.(curright结点存在的前提下)
③将parent作为cur的右边,要注意将parent的父指针也要指向cur。
④当cur变成父结点后,也要注意cur的父指针的调整。
⑤最后旋转后,cur和parent的平衡因子都为0。

在这里插入图片描述
右单旋之后,cur结点就会充当父结点,而旋转后高度会降低,并且cur和parent的平衡因子都为0!
不过要注意这里的图形可能只是一颗子树,而不是整棵树,所以当cur为父节点时,那cur的父指针是谁呢?有两种情况,一种就是原来parent的位置就是根结点,即parent的父指针就是空。那么cur的父指针就是空。当原来的parent的位置不是根结点,原来的parent的父指针就是cur的父指针。

else if (parent->_bf == -2 && cur->_bf == -1)//这种情况是单纯左边高,右单旋即可解决
	{
		RotateR(parent);
	}
void RotateR(Node* parent)//右单旋
	{
		Node* cur = parent->_left;
       Node* ppnode = parent->_parent;//提前记录parent的父指针,因为后面会修改parent的父指针位置
		Node* curright = cur->_right;
		
		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;//调整curright的父指针
		}
		
		cur->_right = parent;
		parent->_parent = cur;//调整parentt的父指针

		if (ppnode == nullptr)
		{
			//说明cur就变成根节点了
			_root = cur;
			cur->_parent = nullptr;
		}
		else//说明ppnode就是cur的父指针,但需要根据parent和ppnode位置来判断
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;//调整cur的父指针
		}
		cur->_bf = parent->_bf = 0;
	}

⑥.双旋(左右旋/右左旋)

双旋发生在什么情况下呢?

①不是单纯一边高时,出现曲折,不是一条直线样子。
②比如parent的平衡因子为2,cur的平衡因子为-1时,就说明parent相对于cur在左边,而新增结点相对于cur是在右边,这样的情况就不是一条直线,就需要先使用左旋让这条曲线变成直线,也就是单纯的左边高,然后再使用右旋对这个直线进行旋转!
③比如parent的平衡因子是-2,cur的平衡因子为1,就说明parent相对于cur在右边,而新增结点相遇于cur在左边,所以需要先用右旋让曲折地方变直也就变成了单纯的右边高,然后再用左旋对对这个直线旋转平衡!

双旋的核心步骤:

①就是复用两个单旋的核心步骤
②要根据图形来判断先左旋还是先右旋
③根据曲折点的平衡因子来确定谁的平衡因子需要调整,因双旋就是复用了两个单旋,最后cur和parent的平衡因子都会变成0,这个显然不行的。

双旋的细节

①先对cur进行旋转,再对parent进行旋转。
②平衡因子的调整,是根据插入位置的平衡因子来决定的,当插入在右边时,插入点的平衡因子就为1,那么最后由于右边这个结点会分给cur。所以最好cur的平衡因子就为0,而parent右边就缺少结点,所以parent的平衡因子就变成-1.
当插入左边时,插入点的平衡因子就为-1,那么最好由于左边的结点会分给parent,那么parent的平衡因子就为0,而cur左边就缺少结点,cur的平衡因子就为1.那么如果当插入位置的平衡因子为0,那么就不需要调整了。

在这里插入图片描述
在这里插入图片描述
以上是右左双旋的情况,也就是当parent的平衡因子为2,cur的平衡因子为-1时会发生!

else if (parent->_bf == 2 && cur->_bf == -1)//这种情况就是不是单纯的一边高了,而是出现曲线,一边高一边低,需要使用双旋这里需要先对cur使用右旋,再对parent使用左旋
	{
		RotateRL(parent);
	
	}
void RotateRL(Node* parent)
	{

		//双旋后注意还要调整平衡因子,因为双旋后将cur parent的平衡因子都置0了不合理
		//根据curleft的平衡因子来确定谁的平衡因子需要调整
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		RotateR(parent->_right);//先对cur使用右旋
		RotateL(parent);//再对parent左旋
		if (curleft->_bf == 0)//根据插入位置的平衡因子来确定是否需要调整平衡因子。
		{
			curleft->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 0;
		}
		else if (curleft->_bf == -1)
		{
			cur->_bf = 1;
			parent->_bf = 0;
			curleft->_bf = 0;
		}
		else if (curleft->_bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
			curleft->_bf = 0;
		}
		//双旋后,就可以直接break了,
		else
		{
			assert(false);
		}
	}

那么另一种的左右双旋呢?当parent的平衡因子为-2,cur的平衡因子为1时就会发生左右双旋!
在这里插入图片描述

else if (parent->_bf == -2 && cur->_bf == 1)//这种情况也是折线,不是单纯的一边高,需要使用双旋,先使用左旋再使用右旋
		{
			RotateLR(parent);

		}
		void RotateLR(Node* parent)
     	{
		
		//先记录位置再能旋转
		// 
		//最后还需要调整平衡因子
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		RotateL(parent->_left);//先对cur使用左旋
		RotateR(parent);//再对parent使用右旋
		if (curright->_bf == 0)
		{
			//减少耦合关系
			cur->_bf = 0;
			parent->_bf = 0;
			curright->_bf = 0;
		}
		else if (curright->_bf == -1)
		{
			cur->_bf = 0;
			parent->_bf = 1;
			curright->_bf = 0;
		}
		else if (curright->_bf == 1)
		{
			cur->_bf = -1;
			parent->_bf = 0;
			curright->_bf = 0;
		}
		//双旋后,就可以直接break了,
	
	}

⑧.AVL树的删除

这里的删除也是在搜索树删除结点的基础上再进行更新平衡因子的,原理是一致的,如果有兴趣可以搞一搞,这里只将大概思路列出来:

①按照搜索树的删除进行处理
②更新平衡因子
③出现异常后,就进行旋转处理

⑨.检查是否是AVL树

我们可以通过一个函数来判断我们的这颗树是否是AVL树,那写一个什么样的函数判断呢?
根据AVL树的性质:树的左右子树的高度差绝对值不超过1.我们可以将每个子树的高度都计算出来,然后用右子树减去左子树来算这颗子高度差,然后与平衡因子来比较就可以看出来是否一致。


	int Heigh(Node* root)//用来检查树的高度的
	{
 		if (root == nullptr)
			return 0;

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

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

	bool _isbalance(Node* root)//用来检查树是否平衡的
	{
		if (root == nullptr)
			return true;

		int highL = Heigh(root->_left);
		int highR = Heigh(root->_right);

		if (highR - highL != root->_bf)
		{
			cout << "平衡因子异常:" <<root->_kv.first<<"-"<< root->_bf << " " << endl;
			return false;
		}
		return abs(highR-highL)<2
			&& _isbalance(root->_left)
			&& _isbalance(root->_right);

	}
	bool isbalance()
	{
		return _isbalance(_root);
	}

三.完整代码

#pragma once
#include <iostream>
using namespace std;
#include<assert.h>
//首先定义结点

template <class K, class V>

struct NodeTree
{
	int _bf;//平衡因子
	pair<K, V> _kv;//结点里存的数据
	NodeTree<K, V>* _left;
	NodeTree<K, V>* _right;
	NodeTree<K, V>* _parent;//为什么要多增加一个父指针呢?为了后面往上更新平衡因子方便

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

};

template <class K, class V>
class AVLTree
{

	typedef NodeTree<K, V> Node;
public:

	bool Insert(const pair<K, V>& kv)
	{
		//AVL树的插入就是搜索树的插入+更新平衡因子
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		//说明该二叉树不是空树,那么就进行比较找到位置
		Node* cur = _root;
		Node* parent = nullptr;
		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为空了,表明位置已经找到了
		cur = new Node(kv);

		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//注意这个是三叉链,还要注意父指针
		cur->_parent = parent;
		//正常插入结点已经完成,接下来AVL树还需要更新平衡因子
		while (parent)//往上不断更新,直到更新到根结点,根结点上面就不需要更新了
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//说明该子树高度改变,会影响祖先,需要往上更新平衡因子
				cur = parent;
				parent = parent->_parent;
			
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//说明这个树生病了,需要调整,旋转,将树平衡
				//核心就是让cur的左结点成为parent的右结点,让parent成为cur的左结点。
				//要求旋转后,仍然是一个颗搜索树
				// 旋转后平衡,高度变低


				//不平衡的树情况有很多种
				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)//这种情况就是不是单纯的一边高了,而是出现曲线,一边高一边低,需要使用双旋这里需要先对cur使用右旋,再对parent使用左旋
				{
					RotateRL(parent);

				}
				else if (parent->_bf == -2 && cur->_bf == 1)//这种情况也是折线,不是单纯的一边高,需要使用双旋,先使用左旋再使用右旋
				{
					RotateLR(parent);

				}
				//最后调整完break出去
				break;
			}
			else
				{
					assert(false);
				}
			
		}
		return true;
	}
	void RotateL(Node* parent)//左单旋
	{

		Node* cur = parent->_right;
		
		Node* curleft = cur->_left;
		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;
		}
		cur->_left = parent;
		Node* pp = parent->_parent;
		parent->_parent = cur;



		if (parent==_root)
		{
			//那么这样cur就是根结点了
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pp->_left == parent)
			{
				pp->_left = cur;
			}
			else
			{
				pp->_right = cur;
			}

			cur->_parent = pp;
			//旋转后cur和parent bf都为0?
		}
		cur->_bf = parent->_bf = 0;
	}
	void RotateR(Node* parent)//右单旋
	{
		Node* cur = parent->_left;

		Node* curright = cur->_right;
		
		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;
		}
		Node* ppnode = parent->_parent;
		cur->_right = parent;
		parent->_parent = cur;

		if (ppnode == nullptr)
		{
			//说明cur就变成根节点了
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		cur->_bf = parent->_bf = 0;
	}
	void RotateRL(Node* parent)
	{

		//双旋后注意还要调整平衡因子,因为双旋后将cur parent的平衡因子都置0了不合理
		//根据curleft的平衡因子来确定谁的平衡因子需要调整
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		RotateR(parent->_right);//先对cur使用右旋
		RotateL(parent);//再对parent左旋
		if (curleft->_bf == 0)
		{
			curleft->_bf = 0;
			cur->_bf = 0;
			parent->_bf = 0;
		}
		else if (curleft->_bf == -1)
		{
			cur->_bf = 1;
			parent->_bf = 0;
			curleft->_bf = 0;
		}
		else if (curleft->_bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
			curleft->_bf = 0;
		}
		//双旋后,就可以直接break了,
		else
		{
			assert(false);
		}
	}
	void RotateLR(Node* parent)
	{
		
		//先记录位置再能旋转
		// 
		//最后还需要调整平衡因子
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		RotateL(parent->_left);//先对cur使用左旋
		RotateR(parent);//再对parent使用右旋
		if (curright->_bf == 0)
		{
			//减少耦合关系
			cur->_bf = 0;
			parent->_bf = 0;
			curright->_bf = 0;
		}
		else if (curright->_bf == -1)
		{
			cur->_bf = 0;
			parent->_bf = 1;
			curright->_bf = 0;
		}
		else if (curright->_bf == 1)
		{
			cur->_bf = -1;
			parent->_bf = 0;
			curright->_bf = 0;
		}
		//双旋后,就可以直接break了,
	
	}
	
	

	int Heigh(Node* root)//用来检查树的高度的
	{
 		if (root == nullptr)
			return 0;

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

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

	bool _isbalance(Node* root)//用来检查树是否平衡的
	{
		if (root == nullptr)
			return true;

		int highL = Heigh(root->_left);
		int highR = Heigh(root->_right);

		if (highR - highL != root->_bf)
		{
			cout << "平衡因子异常:" <<root->_kv.first<<"-"<< root->_bf << " " << endl;
			return false;
		}
		return abs(highR-highL)<2
			&& _isbalance(root->_left)
			&& _isbalance(root->_right);

	}
	bool isbalance()
	{
		return _isbalance(_root);
	}

private:
	Node* _root=nullptr;
};

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

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

相关文章

中国电子科技集团公司第十四研究所(中电14)部门科室介绍、能力要求、待遇薪资

0.基本 雷达中电第一所南京 1.一部&#xff08;总体部&#xff09; 与军队对接需求雷达的选型和交付能力要求&#xff1a;担责任、知识面广&#xff08;天线、射频、信号处理、数据处理&#xff09;、学习能力、对外沟通能力科室&#xff1a; 101&#xff1a;空军&#xff0…

项目无故启动不了

隔了一个周末回来上班&#xff0c;启动项目&#xff0c;发现项目启动不了&#xff0c;根本没有动过代码。 报错&#xff1a; 解决方案&#xff1a; 代码没有改过&#xff0c;无缘无故启动不了项目&#xff0c;肯定是环境的问题。 找到这个类所在的依赖&#xff0c; 删掉重新…

Java的XWPFTemplate工具类导出word.docx的使用

依赖 <!-- word导出 --><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.7.3</version></dependency><!-- 上面需要的依赖--><dependency><groupId>org.ap…

华为云云耀云服务器L实例评测| CloudExplorer Lite轻量级云平台管理华为云云耀云服务器L实例

华为云云耀云服务器L实例评测&#xff5c; CloudExplorer Lite轻量级云平台管理华为云云耀云服务器L实例 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点1.3 云耀云服务器L实例使用场景 二、 CloudExplorer Lite介绍2.1 CloudExplorer Lite简介…

练习接口测试详细步骤

最近一段时间学了Python语言&#xff0c;重新学了 Java&#xff0c;js&#xff0c;html语言&#xff0c;CSS&#xff0c;linux&#xff0c;一堆测试工具&#xff1b;唉&#xff5e; 在接触接口测试过程中补了很多课&#xff0c; 终于有点领悟接口测试的根本&#xff1b; 偶是…

为何不建议使用Java自带的线程池

Executors Executors是java自带的线程池。Executors 里面默认提供的几个线程池是有一些弊端的&#xff0c;如果是不懂多线程、或者是新手直接盲目使用&#xff0c;就可能会造成比较严重的生产事故。 Executors.newFixedThreadPool(10); Executors.newSingleThreadExecutor();…

Python语义分割与街景识别(3):数据集准备

前言 本文主要用于记录我在使用python做图像识别语义分割训练集的过程&#xff0c;由于在这一过程中踩坑排除BUG过多&#xff0c;因此也希望想做这部分内容的同学们可以少走些弯路。 本文是python语义分割与街景识别第三篇&#xff0c;关于数据集准备的内容。 一、自己制作数…

【transformer】动手学ViT

AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE 摘要Method实验代码-基于pytorchTraining Visual Transformer on Dogs vs Cats Data注释一些词汇 ICLR2021 一幅图像值16x16个字&#xff1a;用于图像识别的transformers 将纯Transformer结构运用…

基于SSM的中小企业人力资源管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

计算机网络第五章——传输层(下)

安得广厦千万间&#xff0c;大庇天下寒士俱欢颜 文章目录 按序的不丢失的&#xff0c;这个校验也是二进制求反码来判断有没有发送错误&#xff0c; TCP传输的时候就是以一个字节为单位&#xff0c;所以就会把一个字节编一个序号&#xff0c;对于一个文件第一个需要是多少是可以…

MySQL的权限管理与远程访问

MySQL的权限管理 1、授予权限 授权命令&#xff1a; grant 权限1,权限2,…权限n on 数据库名称.表名称 to 用户名用户地址 identified by ‘连接口令’; 该权限如果发现没有该用户&#xff0c;则会直接新建一个用户。 比如 grant select,insert,delete,drop on atguigudb.…

Prometheus+Grafana可视化监控【主机状态】

文章目录 一、介绍二、安装Prometheus三、安装Grafana四、Pronetheus和Grafana相关联五、监控服务器状态六、常见问题 一、介绍 Prometheus是一个开源的系统监控和报警系统&#xff0c;现在已经加入到CNCF基金会&#xff0c;成为继k8s之后第二个在CNCF托管的项目&#xff0c;在…

RunnerGo:让你的性能测试变得轻松简单

在当今这个数字化时代&#xff0c;应用程序的性能至关重要。一款可靠的性能测试工具&#xff0c;能够为企业带来无数的好处。最近&#xff0c;一款名为RunnerGo的开源性能测试工具备受瞩目。本文将详细介绍RunnerGo的特点、优势以及如何解决性能测试中的痛点。 RunnerGo产品介绍…

OpenLayers入门,读取wkt格式数据,OpenLayers解析并显示wkt格式的要素数据

专栏目录: OpenLayers入门教程汇总目录 前言 本章介绍OpenLayers如何解析并显示wkt格式的要素数据。 使用Point(点)、(LINESTRING)线,和(POLYGON)多变形的wkt数据进行演示。 wkt介绍请参考博主另一篇文章《GIS入门,WKT格式详解》. 二、依赖和使用 "ol": …

【SpringMVC】拦截器JSR303的使用

目录 一、JSR303 1.1.什么是JSR303 1.2.为什么使用JSR303 1.3.常用注解 1.4.Validated与Valid区别 1.5.JSR快速入门 1.5.1.导入依赖 1.5.2.配置校验规则 1.5.3.入门案例 二、拦截器 2.1什么是拦截器 2.2.拦截器与过滤器 2.3.应用场景 2.4.拦截器快速入门 2.4.1.…

网络安全知识

一、什么是网络安全&#xff1f; 网络安全&#xff0c;通常指计算机网络的安全&#xff0c;实际上也可以指计算机通信网络的安全。计算机通信网络是将若干台具有独立功能的计算机通过通信设备及传输媒体互连起来&#xff0c;在通信软件的支持下&#xff0c;实现计算机间的信息传…

第七届蓝帽杯初赛 取证部分复现

取证案情介绍&#xff1a; 2021年5月&#xff0c;公安机关侦破了一起投资理财诈骗类案件&#xff0c;受害人陈昊民向公安机关报案称其在微信上认识一名昵称为yang88的网友&#xff0c;在其诱导下通过一款名为维斯塔斯的APP&#xff0c;进行投资理财&#xff0c;被诈骗6万余万元…

LLFormer 论文阅读笔记

Ultra-High-Definition Low-Light Image Enhancement: A Benchmark and Transformer-Based Method 这是南京大学在AAAI 2023发表的一篇AAAI2023 超高清图像暗图增强的工作。提出了一个超高清暗图增强数据集&#xff0c;提供了4K和8K的图片&#xff0c;同时提出了一个可用于暗图…

新掌门蔡崇信,能否“再救”阿里?

文 | 琥珀消研社 作者 | 积溪 琥珀消研社快评&#xff1a;蔡崇信成了阿里最新一届CEO&#xff0c;从幕后走到了台前&#xff0c;阿里的神话还会被续写吗&#xff1f;#阿里 #马云 #蔡崇信 外面人常说 淘宝是靠败家女人撑大的 但你要让马爸爸讲几句 他肯定会说 阿里现在能…

哨兵1号(Sentinel-1)SAR卫星介绍

1. 哥白尼计划 说起欧空局的哨兵1号&#xff0c;就不得不先说一下欧空局的“哥白尼计划”。 欧空局的哥白尼计划&#xff08;Copernicus Programme&#xff09;是欧空局与欧盟合作的一项极其重要的地球观测计划。该计划旨在提供免费开放的、可持续的地球观测数据&#xff0c…