[C++]AVL树插入和删除操作的实现

news2024/11/16 21:51:58

        AVL树又称为高度平衡的二叉搜索树,是1962年由两位俄罗斯数学家G.M.Adel’son-Vel’skii和E.M.Landis提出的。ALV树提高了二叉搜索树树的搜索效率。为此,就必须每向二叉搜索树插人一个新结点时调整树的结构,使得二叉搜索树保持平衡,从而尽可能降低树的高度,减少树的平均搜索长度。

一、AVL树的概念

        AVL树是自平衡的二叉搜索树,AVL树可以为空树。AVL树的左子树和右子树的高度差的绝对值最大不超过1,每个结点的左右子树都是AVL树。

        平衡因子(balance factor,简称bf):左右子树的高度差即为平衡因子的值,比如左子树高度为1,右子树高度为2,那么平衡因子的计算在这里我们规定为右子树的高度减去左子树的高度,也就是1。

        一棵简单的AVL树的图示:

        红色数字标注的是对应结点的平衡因子。

        因为ALV树的左右子树能够保持高度差不超过1,所以假设AVL树有n个结点,那么该二叉树的高度为O(log_{2}^{}n),平均搜索长度为搜索高度次O(log_{2}^{}n)

二、树结点的定义

//AVL树结点的定义
template<class K,class V>
struct AVLTreeNode
{
	pair<K, V> _kv;	//键值对
	AVLTreeNode<K, V>* _pLeft;
	AVLTreeNode<K, V>* _pRight;
	AVLTreeNode<K, V>* _pParent; //指向父亲结点
	int _bf; //平衡因子

	AVLTreeNode(const pair<K, V>& kv) :
		_kv(kv.first, kv.second),
		_pLeft(nullptr),
		_pRight(nullptr),
		_pParent(nullptr),
		_bf(0)
	{}
};

三、接口声明以及部分接口的定义 

接口声明:

//AVL树
template<class K, class V>
class AVLTree
{
private:
	typedef AVLTreeNode<K, V> Node;
	typedef pair<K, V> ValueType;

	Node* _root;//根结点

	//递归的辅助实现的:
	void _inOrder(Node* root);	//中序遍历
	void destroy(Node* root);	//后序遍历析构
	int _height(Node* root);//计算树的高度
	int _size(Node* root);//计算树的结点个数
	bool _checkTree(Node* root);//检测树的因子是否正确,树是否平衡。
	
	//旋转
    void rotateL(Node* parent);//左单旋
    void rotateR(Node* parent);//右单旋
    void rotateLR(Node* parent);//左右双旋
    void rotateRL(Node* parent);//右左双旋
public:
	
	AVLTree() :_root(nullptr) {}	//构造
	~AVLTree();	//析构

	bool insert(const ValueType& x);	//插入
	bool erase(const K& key);	//删除

	int size();	//计算树的结点个数
	int height();	//计算树的高度

	Node* find(const K& key);	//查找

	void inOrder();	//中序遍历节点并打印val
	bool checkTree();	//检测树的因子是否正确,树是否平衡。
};

部分接口的定义比较简单,直接给出:

//AVL树
template<class K, class V>
class AVLTree
{
private:
	typedef AVLTreeNode<K, V> Node;
	typedef pair<K, V> ValueType;

	Node* _root;//根结点

	//递归的辅助实现的:
	void _inOrder(Node* root)	//中序遍历
	{
		if (!root)
			return;

		_inOrder(root->_pLeft);
		cout << root->_kv.first << " -> " << root->_kv.second << " bf = " << root->_bf << endl;
		_inOrder(root->_pRight);
	}

	void destroy(Node* root)	//后序遍历析构
	{
		if (!root)
			return;

		destroy(root->_pLeft);
		destroy(root->_pRight);
		delete root;
	}

	int _height(Node* root)//计算树的高度
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _height(root->_pLeft);
		int rightHeight = _height(root->_pRight);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int _size(Node* root)	//计算树的结点个数
	{
		if (root == nullptr)
			return 0;

		return _size(root->_pLeft) + _size(root->_pRight) + 1;
	}

	bool _checkTree(Node* root)	//检测树的因子是否正确,树是否平衡。
	{
		if (root == nullptr)
			return true;

		int leftHeight = _height(root->_pLeft);
		int rightHeight = _height(root->_pRight);

		int bf = rightHeight - leftHeight;

		if (root->_bf != bf)
		{
			cout << "结点的因子计算错误:" << root->_kv.first << "的bf为" << root->_bf << " 而正确的bf为" << bf << endl;
			return false;
		}

		if (bf >= 2 || bf <= -2)
		{
			cout << root->_kv.first << "的左右子树高度差超过1,树不平衡" << endl;
			return false;
		}

		return _checkTree(root->_pLeft) && _checkTree(root->_pRight);
	}


	//旋转
    void rotateL(Node* parent);//左单旋
    void rotateR(Node* parent);//右单旋
    void rotateLR(Node* parent);//左右双旋
    void rotateRL(Node* parent);//右左双旋
public:
	
	AVLTree() :_root(nullptr) {}	//构造

	~AVLTree()	//析构
	{
		destroy(_root);
		_root = nullptr;
	}

	bool insert(const ValueType& x);	//插入
	bool erase(const K& key);	//删除

	int size()	//计算树的结点个数
	{
		return _size(_root);
	}

	int height()	//计算树的高度
	{
		return _height(_root);
	}

	Node* find(const K& key)	//查找
	{
		Node* pCur = _root;

		while (pCur)
		{
			if (key > pCur->_kv.first)
				pCur = pCur->_pRight;
			else if (key < pCur->_kv.first)
				pCur = pCur->_pLeft;
			else
				return pCur;
		}

		//找不到
		return nullptr;
	}

	void inOrder()	//中序遍历节点并打印val
	{
		_inOrder(_root);
	}

	bool checkTree()	//检测树的因子是否正确,树是否平衡。
	{
		return _checkTree(_root);
	}
};//AVL树结点的定义
template<class K,class V>
class AVLTreeNode
{
	pair<K, V> _kv;	//键值对
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent; //指向父亲结点
	int _bf; //平衡因子

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

        这里重点介绍的是AVL树的四种旋转以及在插入和删除结点时该如何使用旋转来使搜索树从不平衡变平衡。 

四、AVL树的四种旋转

(一)、左单旋转

        如果在插入新结点之前AVL树的形状如下图所示:

        其中圆形框代表一个结点旁边标注的数字为平衡因子,矩形框表示结点的子树,h是子树的高度。如果h = 0,说明子树为空;如果h > 0说明子树存在。

        此时,该搜索二叉树满足AVL树的要求。

        接着在结点parent的右子树上较高的一侧插入一个新的结点,也就是在γ子树上插入一个新的结点x。这时,由于γ子树的高度发生了变化,需要向上调整双亲结点的平衡因子,因此结点sub的平衡因子从0变为了1,结点parent的平衡因子从1变为了2。此时结点parent的平衡因子大于1,左右子树的高度不平衡。因为parent和sub的平衡因子同号,即正负相同,右子树偏高,需要进行左单旋操作。

        我们需要以结点parent为旋转轴,让sub结点逆时针旋转。让sub的左子树subL成为结点parent的右子树,结点parent成为结点sub的左子树。并更新sub和parent的平衡因子为0。

        旋转后可以看出,结点sub的左右子树高度相同,搜索二叉树又恢复了平衡,结点sub和结点parent的平衡因子都为0。

         左单旋代码:

	//旋转
	void rotateL(Node* parent)//左单旋
	{
		Node* sub = parent->_pRight;
		Node* subL = sub->_pLeft;
		Node* pPrev = parent->_pParent; //parent的前驱结点

		parent->_pRight = subL;	//结点subL成为parent的右子树
		if (subL)	//将结点subL的前驱结点改为结点parent
			subL->_pParent = parent;

		sub->_pLeft = parent;	//parent成为sub的左结点
		parent->_pParent = sub;

		//处理前驱结点prev和结点sub
		if (pPrev == nullptr)	//说明结点parent原来为根结点
		{
			_root = sub;	//sub成为新的根节点
			sub->_pParent = nullptr;
		}
		else
		{
			if (pPrev->_pLeft == parent)
				pPrev->_pLeft = sub;
			else
				pPrev->_pRight = sub;

			sub->_pParent = pPrev;
		}

		//更新平衡因子
		sub->_bf = parent->_bf = 0;
	}

(二)、右单旋转 

        右单旋转其实就是左单旋转对称的情况。

        如果在插入新结点X之前AVL树的形状如下图所示:

        此时,该搜索二叉树满足AVL树的要求。        

        接着在结点parent的左子树上较高的一侧插入一个新的结点,也就是在γ子树上插入一个新的结点x。这时,由于γ子树的高度发生了变化,需要向上调整双亲结点的平衡因子,因此结点sub的平衡因子从0变为了-1,结点parent的平衡因子从-1变为了-2。此时结点parent的平衡因子小于1,左右子树的高度不平衡。因为parentsub的平衡因子同号,即正负相同,左子树偏高,需要进行右单旋操作        我们需要以结点parent为旋转轴,让sub结点顺时针旋转。让sub的右子树subR成为结点parent的左子树,结点parent成为结点sub的右子树,并且更新sub和parent的平衡因子为0。        旋转后可以看出,结点sub的左右子树高度相同,搜索二叉树又恢复了平衡,结点sub和结点parent的平衡因子都为0。

        右单旋转代码:

	void rotateR(Node* parent)//右单旋
	{
		Node* sub = parent->_pLeft;
		Node* subR = sub->_pRight;
		Node* pPrev = parent->_pParent;	//parent的前驱结点

		parent->_pLeft = subR;	//结点subR成为parent的左子树
		if (subR)	//将结点subR的前驱结点改为结点parent
			subR->_pParent = parent;

		sub->_pRight = parent;	//parent成为sub的右结点
		parent->_pParent = sub;

		//处理前驱结点prev和结点sub
		if (pPrev == nullptr)	//说明结点parent原来为根结点
		{
			_root = sub;	//sub成为新的根节点
			sub->_pParent = nullptr;
		}
		else
		{
			if (pPrev->_pLeft == parent)
				pPrev->_pLeft = sub;
			else
				pPrev->_pRight = sub;

			sub->_pParent = pPrev;
		}

		//更新平衡因子
		sub->_bf = parent->_bf = 0;
	}

(三)、左右双旋 

        有的时候,单次旋转不能解决搜索树不平衡的问题。比如下面这个情况:

        单旋根本解决不了问题,旋转两次还回到了起点。这种情况需要进行左右双旋。

         如果在插入新结点X之前AVL树的形状如下图所示:

        此时,该搜索二叉树满足AVL树的要求。

        接着在结点parent的左子树的右子树插入一个新的结点,也就是在β子树或者γ子树(这里选择了β)上插入一个新的结点x。这时,由于β的高度发生了变化,需要向上调整双亲结点的平衡因子,因此结点subR的平衡因子从0变为了-1,结点sub的平衡因子从0变为了1,结点parent的平衡因子从-1变为了-2。此时结点parent的平衡因子小于1,左右子树的高度不平衡。因为parentsub的平衡因子异号,即正负不相同,结点sub的右子树偏高而parent的左子树偏高,需要进行先左后右的双旋操作

以sub为轴,subR逆时针旋转,进行一次左单旋:

        然后再以parent为轴,subR顺时针旋转,进行一次右单旋,并且根据实际情况更新parent,sub以及subR的平衡因子,这里更新后的平衡因子分别为1,0,0。

        我们可以将左右双旋前后的结构进行对比,发现双旋的结果可以概括为:将subR平移上去作为该树的根,subR的左右子树β和γ分配给了sub和parent。

        所以根据双旋结果可以知道,如果新节点x插入到了γ子树下面,那么parent,sub以及subR结点的平衡因子分别为0,-1,0。

        还有一种特殊情况需要处理,上面的情况都是h > 1的情况,如果h = 0的话,AVL树的情况如下。

        此时插入的结点在sub的右边。

        parent的平衡因子小于1,sub的平衡因子等于1,parent和sub的平衡因子异号,需要进行左右双旋。

        此时parent、sub以及subR的平衡因子都为0。

左右双旋代码:

void rotateLR(Node* parent)//左右双旋
{
	Node* sub = parent->_pLeft;
	Node* subR = sub->_pRight;

	//得到subR的平衡因子,
	//如果为-1,则新结点插入到了subR的左边,也就是β下
	//如果为 1,则新节点插入到了subR的右边,也就是γ下
	//如果为0,则是h = 0的情况。
	int bf = subR->_bf;

	//复用左和右单旋的代码哈哈
	rotateL(sub);
	rotateR(parent);

	//这里旋转完后,parent、sub以及subR的平衡因子都被置为0了
	//按照情况调整平衡因子的值

	if (bf == -1) //新节点插入到了subR的左边
		subR->_pRight->_bf = 1;
	else if (bf == 1)	//新节点插入到了subR的右边
		subR->_pLeft->_bf = -1;
	//若bf为0,可以啥也不干。
}

 (四)、右左双旋

        右左双旋的情况就是左右双旋情况的对称。这里快速用几张图演示一下。

        假设有这样一颗AVL树:

        若将新结点插入到γ下,进行右左单旋。更新parent、sub、subR的平衡因子为0,1,0。

       若将新结点插入到β下,进行右左单旋,更新parent、sub、subR的平衡因子为-1,0,0。

        当h = 0时。 进行右左单旋,更新parent、sub、subR的平衡因子为0,0,0。

 右左单旋代码:

void rotateRL(Node* parent)//右左双旋
{
	Node* sub = parent->_pRight;
	Node* subL = sub->_pLeft;

	//得到subL的平衡因子,
	//如果为-1,则新结点插入到了subL的左边,也就是γ下
	//如果为 1,则新节点插入到了subL的右边,也就是β下
	//如果为0,则是h = 0的情况。
	int bf = subL->_bf;

	//继续复用左和右单旋
	rotateR(sub);
	rotateL(parent);

	if (bf == -1) //新节点插入到了subL的左边
		subL->_pRight->_bf = 1;
	else if (bf == 1)	//新节点插入到了subL的右边
		subL->_pLeft->_bf = -1;
	//若bf为0,可以啥也不干。
}

五、插入操作

        在实现了四种旋转后,可以进行插入操作了。

        首先需要找到新结点插入的位置,然后从插入的位置起向上回溯调整双亲结点的平衡因子。如果插入的结点在双亲结点的左边,那么双亲结点的平衡因子减一,如果插入的结点在双亲结点的右边,那么双亲结点的平衡因子加一。

        每一次回溯调整了双亲结点的平衡因子后,需要判断是否还需要进行向上调整或是否需要进行旋转操作。

        平衡因子有以下几种情况和处理方法。

parent调整后的平衡因子

因此得出parent之前的平衡因子是

说明

需要进行的操作

-2

-1

左子树偏高,搜索树变得不平衡了。

若parent的平衡因子和child的平衡因子同号,需要进行一次右单旋;若异号,需要进行一次左右双旋。旋转后结束向上调整。

2

1

右子树偏高,搜索树变得不平衡了。

若parent的平衡因子和child的平衡因子同号,需要进行一次左单旋;若异号,需要进行一次右左双旋。旋转后结束向上调整。

-1

0

以parent为根的这棵树高度发生了变化。

需要继续回溯调整平衡因子。

1

0

以parent为根的这棵树高度发生了变化。

需要继续回溯调整平衡因子。

0

-1或1

插入新结点后,以parent为根的这棵树的高度和之前相比没发生变化。

结束向上调整的操作。

插入的代码如下:

bool insert(const ValueType& x)	//插入
{
	//找到x所插入的位置
	Node* pCur = _root;
	Node* pPos = nullptr;

	while (pCur)
	{
		pPos = pCur;

		//x的first小往左走,大往右走
		if (pCur->_kv.first > x.first)
			pCur = pCur->_pLeft;
		else if (pCur->_kv.first < x.first)
			pCur = pCur->_pRight;
		else
			return false;	//插入的值已存在,插入失败
	}
	
	//如果树为空,那么插入的结点即为根节点
	if (pPos == nullptr)
	{
		_root = new Node(x);
		return true;
	}

	//插入该结点
	Node* pChild = new Node(x);
	if (x.first > pPos->_kv.first)
	{
		pPos->_pRight = pChild;
		pPos->_pRight->_pParent = pPos;
	}
	else
	{
		pPos->_pLeft = pChild;
		pPos->_pLeft->_pParent = pPos;
	}

	//向上调整平衡因子
	Node* pParent = pPos;

	while (pParent)	//最坏的情况调整到根
	{
		//插入的结点在左子树,双亲结点的平衡因子--
		if (pParent->_pLeft == pChild)	
		{
			pParent->_bf--;
		}
		else //插入的结点在右子树,双亲节点的平衡因子++
		{
			pParent->_bf++;
		}

		//判断是否需要进行旋转或结束向上调整操作
		if (pParent->_bf == 0)
			break;	//结束向上调整
		else if (pParent->_bf == -2)  //左子树偏高,需要进行旋转
		{
			if (pChild->_bf == -1)	//同号,进行右单旋
				rotateR(pParent);
			else if (pChild->_bf == 1)  //异号,进行左右单旋
				rotateLR(pParent);

			break;
		}
		else if (pParent->_bf == 2)	//右子树偏高
		{
			if (pChild->_bf == 1)	//同号,进行左单旋
				rotateL(pParent);
			else if (pChild->_bf == -1)  //异号,进行右左单旋
				rotateRL(pParent);

			break;
		}
		else if (pParent->_bf == -1 || pParent->_bf == 1)	//需要继续向上调整
		{
			pChild = pParent;
			pParent = pParent->_pParent;
		}
		else
		{
			//因子情况出错
			assert(false);
		}

	}
	return true;
}

六、删除操作 

AVL树的删除操作和二叉搜索树的删除操作类似。

1、如果待删除的结点pos最多只有一个孩子child(当pos没有孩子时child指向空),pos结点的双亲结点为parent,只需要将parent原指向pos的指针改为指向pos的孩子child,然后再删除pos结点即可。

2、如果待删除的结点pos有两个孩子(都不为空),则需要pos结点的数据和pos结点的右子树的最小值结点rightMin左子树的最大值leftMax结点的值进行交换,然后将pos指向rightMinleftMax,这个时候pos指向的结点最多只有一个孩子child,这样就转化为了第一种情况的删除操作。

         删除结点后,子树高度的变化会影响到路径上的祖宗结点的平衡因子,所以需要从删除的结点pos的位置向上回溯更新双亲结点parent的平衡因子。如果删除的结点在双亲结点parent的左边,则parent的平衡因子加一,如果删除的结点在双亲结点parent的右边,则平衡因子减一。然后根据平衡因子更新后的值判断还需不需要继续进行向上调整或旋转操作。

        下面来考察平衡因子变化的几种情况和需要对其进行的操作。

        情况1:当双亲结点parent的平衡因子原来为0时,删除一个结点后,平衡因子更新为-1或1。

        在parent的左或右子树中删除一个结点,并且更新parent的平衡因子。

        或者:

        删除后发现以parent为根的这棵树的高度和之前相比没有发生变化,还是h,所以结束向上调整平衡因子的操作。

        情况二: 当双亲结点parent的平衡因子原来为-1或1时,删除一个结点后,parent的平衡因子更新为0。

        或者:

        此时,以parent为根的这棵子树的高度和原来相比高度减少了1,所以我们需要继续向上回溯调整双亲结点的平衡因子。

        情况3:parent原来的平衡因子为-1或1,删除结点后,parent结点的平衡因子更新为了-2或2,说明删除的结点在parent较矮的子树上,造成了parent的左右子树不平衡,需要进行旋转操作。

        当parent原来的平衡因子为1的情况:

1、parent平衡因子为1,sub结点的平衡因子为0。

        删除的结点在左子树上,parent的平衡因子变为2。

        以parent为轴,sub逆时针旋转,进行一次左单旋转操作。

        旋转后更新parent和sub的平衡因子分别为1和-1,此时以sub为根的树的高度和原来相比没有变化,高度为h + 2,可以结束向上回溯过程了。

2、parent平衡因子为1,sub结点的平衡因子为1。

        删除的结点在左子树上,parent的平衡因子变为2。

        再以parent为轴,sub逆时针旋转,进行一次左单旋转操作。

        旋转后更新parent和sub的平衡因子分别为0和0,此时以sub为根的树的高度和原来相比减少了1,高度为h + 1,需要继续回溯进行向上调整平衡因子的操作。

3、parent的平衡因子为1,sub的平衡因子为-1,subL的平衡因子可以为-1或0或1。

       当删除的结点在parent的左子树上,parent的平衡因子变为2。

        因为parent的平衡因子和sub的平衡因子异号,所以需要进行右左双旋操作。旋转过程:

        以sub为轴,subL顺时针旋转,进行一次右单旋转。

        以parent为轴,subL逆时针旋转,进行一次左单旋转。

        如果α子树的高度为h - 1,β子树的高度为h -2,那么parent,sub,subL的平衡因子分别为0,1,0。

        如果α子树的高度为h - 2,β子树的高度为h -1,那么parent,sub,subL的平衡因子分别为-1,0,0。

        如果α子树的高度为h - 1,β子树的高度为h -1,那么parent,sub,subL的平衡因子分别为0,0,0。

        旋转后以subL为根的这棵树的高度和原来的高度相比减少了1,为h + 1,需要继续回溯进行向上调整的操作。

        上面是parent原来的平衡因子为1的情况。

        然后就是当parent原来的平衡因子为-1的情况:处理的方法就是上面的所有情况的对称,这里快速用图演示过程,细节不再赘述。

1、parent平衡因子为-1,sub结点的平衡因子为0。

2、parent平衡因子为-1,sub结点的平衡因子为-1。

3、parent平衡因子为-1,sub结点的平衡因子为1。

代码如下:

bool erase(const K& key)	//删除
{
	Node* pPos = _root;

	//找到要删除的结点
	while (pPos)
	{
		if (pPos->_kv.first > key)
			pPos = pPos->_pLeft;
		else if (pPos->_kv.first < key)
			pPos = pPos->_pRight;
		else
			break;
	}


	if (pPos == nullptr)	//找不到要删除的结点
		return false;

	//如果要删除的结点有两个孩子,需要转化为情况一
	if (pPos->_pLeft && pPos->_pRight)	
	{
		Node* pRightMin = pPos->_pRight;	
		//寻找pPos右子树的最小结点
		while (pRightMin->_pLeft)
		{
			pRightMin = pRightMin->_pLeft;
		}

		//交换pPos和pRightMin的值
		pPos->_kv = pRightMin->_kv;

		pPos = pRightMin;	//转化为删除结点pRightMin
	}

	//此时pPos至少有一个孩子,先不删除pPos,当向上调整好平衡因子后再回来删除

	//如果删除的结点是根结点
	if (pPos->_pParent == nullptr)
	{
		Node* pChildOfPos = nullptr;
		if (pPos->_pLeft != nullptr)
			pChildOfPos = pPos->_pLeft;
		else
			pChildOfPos = pPos->_pRight;

		//让根的子树的根成为整棵树的根,根可能没有孩子,会为空
		_root = pChildOfPos;

		if(pChildOfPos)
			pChildOfPos->_pParent = nullptr;

		delete pPos;
		return true;
	}

	//先向上调整好祖先的平衡因子再回来删除pPos
	Node* pChild = pPos;
	Node* pParent = pPos->_pParent;

	while (pParent)	//最坏向上回溯到根节点
	{
		if (pParent->_pLeft == pChild)	//待删除的结点在parent的左边
			pParent->_bf++;
		else  //待删除的结点在parent的右边
			pParent->_bf--;

		//根据因子的情况判断是否需要继续进行向上调整或旋转

		if (pParent->_bf == -1 || pParent->_bf == 1)//情况一:删除结点后高度不变
			break;
		else if (pParent->_bf == 0)//情况二:需要继续进行向上调整
		{
			pChild = pParent;
			pParent = pParent->_pParent;
		}
		else if (pParent->_bf == -2 || pParent->_bf == 2)	//情况三:旋转
		{
			Node* sub = nullptr;
			if (pParent->_bf == -2)
				sub = pParent->_pLeft;
			else
				sub = pParent->_pRight;

			if (pParent->_bf == 2 && sub->_bf == 0)	//左单旋,旋转后高度不变
			{
				rotateL(pParent);
				//更新因子
				pParent->_bf = 1;
				sub->_bf = -1;
				break;
			}
			else if (pParent->_bf == 2 && sub->_bf == 1)	//左单旋,旋转后高度变化
			{
				rotateL(pParent);
				//需要继续进行向上调整
				pParent = sub; //旋转后sub为新的根节点,所以要从这里开始回溯
				pChild = pParent;
				pParent = pParent->_pParent;
			}
			else if (pParent->_bf == 2 && sub->_bf == -1)	//右左双旋,旋转后高度变化
			{
				rotateRL(pParent);
				//需要继续进行向上调整
				pParent = pParent->_pParent; //旋转后parent的parent为新的根节点,所以要从这里开始回溯
				pChild = pParent;
				pParent = pParent->_pParent;
			}
			else if (pParent->_bf == -2 && sub->_bf == 0)	//右单旋,旋转后高度不变
			{
				rotateR(pParent);
				//更新平衡因子
				pParent->_bf = -1;
				sub->_bf = 1;
				break;
			}
			else if (pParent->_bf == -2 && sub->_bf == -1)	//右单旋,旋转后高度变化
			{
				rotateR(pParent);
				//需要继续进行向上调整
				pParent = sub; //旋转后sub为新的根节点,所以要从这里开始回溯
				pChild = pParent;
				pParent = pParent->_pParent;
			}
			else if (pParent->_bf == -2 && sub->_bf == 1)	//左右双旋,旋转后高度变化
			{
				rotateLR(pParent);
				//需要继续进行向上调整
				pParent = pParent->_pParent; //旋转后parent的parent为新的根节点,所以要从这里开始回溯
				pChild = pParent;
				pParent = pParent->_pParent;
			}
			else
			{
				assert(false);
			}
				
		}
		else  //因子情况不存在
		{
			assert(false);
		}
			
	}

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

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

相关文章

JS简介 JS特点

JS简介 Javascript是一种由Netscape(网景)的LiveScript发展而来的原型化继承的面向对象的动态类型的区分大小写的 客户端脚本语言 &#xff0c;主要目的是为了解决服务器端语言&#xff0c;遗留的速度问题&#xff0c;为客户提供更流畅的浏览效果。 JS特点 JS是一种运行于浏览器…

注册中心 Eureka Nacos

文章目录 目录 文章目录 1. 什么是注册中心? 2.常见的注册中心 3 . Eureka 4 . Nacos 5 . Nacos与Eureka的区别 总结 1. 什么是注册中心? 在最初的架构体系中, 集群的概念还不那么流行, 且机器数量也比较少, 此时直接使用DNSNginx就可以满足几乎所有服务的发现. 相…

ABAP正则表达式 特殊字符处理

REPLACE ALL OCCURRENCES OF REGEX [[:space:]] IN <fs_purhdinfo>-cell_value WITH ."可去掉空格或回车键 REPLACE ALL OCCURRENCES OF &#xff1a; IN <fs_purhdinfo>-cell_value WITH ."可去掉空格或回车键 REPLACE ALL OCCURRENCES OF R…

如何构建高效办公管理系统——Java SpringBoot实战教程,2025年最新设计理念

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

413力扣周赛

3274. 检查棋盘方格颜色是否相同 - 给你两个字符串 coordinate1 和 coordinate2&#xff0c;代表 8 x 8 国际象棋棋盘上的两个方格的坐标。以下是棋盘的参考图。 如果这两个方格颜色相同&#xff0c;返回 true&#xff0c;否则返回 false。分析问题&#xff1a; 由图知&…

在安卓和Windows下使用Vizario H264 RTSP

Unity2021.3.35f1&#xff0c;运行模式为ENGINE_SERVER 1.环境设置 Windows设置 安卓设置 2.代码修改 ConnectionProperties中的server必须与真实IP一样&#xff0c;所以需要新增一个获取IP的函数 public string GetLocalIPAddress(){IPHostEntry host;string localIP &quo…

缓解webclient频繁报‘Connection prematurely closed BEFORE response’的问题

现象&#xff1a; 我在Java代码中使用org.springframework.web.reactive.function.client.WebClient进行网络请求&#xff0c;一开始会有比较多的偶发报错&#xff1a;Connection prematurely closed BEFORE response&#xff0c;网络连接莫名其妙就断了。 处理&#xff1a; …

JDBC以及事务

内容概要&#xff1a; 了解JDBC是什么&#xff0c;以及定义&#xff0c;它有什么好处掌握使用JDBC访问数据库掌握使用JDBC进行增删改查掌握数据库注入问题&#xff0c;以及怎么解决数据库注入问题掌握事务的使用&#xff0c;以及为什么需要事务。理解事务的四大特性&#xff1…

InternLM模型部署教程

一、模型介绍 interlm是一系列多语言基础模型和聊天模型。 InternLM2.5 系列&#xff0c;具有以下特点&#xff1a; 出色的推理能力 &#xff1a;数学推理性能达到世界先进水平&#xff0c;超越 Llama3、Gemma2-9B 等模型。1M 上下文窗口 &#xff1a;在 1M 长上下文中几乎完…

【Qt】Qt 网络 | HTTP

文章目录 HTTP Client核心APIQNetworkAccessManagerQNetworkRequestQNetworkReply 代码示例 本文不涉及 HTTP 的相关前置知识&#xff0c;前置知识可参看 URL概念及组成 HTTP请求 HTTP响应及Cookie原理 HTTP Client 进行 Qt 开发时&#xff0c;和服务器之间的通信很多时候也会…

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界!

随着编程语言的不断演进&#xff0c;Python已经成为开发者们心目中的“瑞士军刀”。它的简洁易用、强大的库支持、广泛的应用领域&#xff0c;让它在人工智能、数据分析、网络爬虫、自动化办公等领域展现了无与伦比的优势。那么&#xff0c;如何深入掌握Python这门语言并用它解…

Stable Diffusion【提示词】【居家设计】:AI绘画给你的客厅带来前所未有的视觉盛宴!

前言 参数设置大模型&#xff1a;RealVisXL V4.0 Lightning采样器&#xff1a;DPM SDE Karras采样迭代步数&#xff1a;5CFG&#xff1a;2图片宽高&#xff1a;1024*1024反向提示词&#xff1a;(octane render, render, drawing, anime, bad photo, bad photography:1.3),(wor…

c++编程(24)——map的模拟实现

欢迎来到博主的专栏&#xff1a;c编程 博主ID&#xff1a;代码小号 文章目录 map的底层红黑树的节点 map的模拟实现map的查找与插入map的迭代器 map的底层 map的底层是一个红黑树&#xff0c;关于红黑树的章节博主写在了数据结构专栏当中&#xff0c;因此不再赘述。 templat…

网络安全服务基础Windows--第8节-DHCP部署与安全

DHCP协议理解 定义&#xff1a;DHCP&#xff1a;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff0c;是⼀个应⽤在局域⽹中的⽹络协议&#xff0c;它使⽤UDP协议⼯作。 67&#xff08;DHCP服务器&#xff09;和68&#xff08;DHCP客户端&#xff0…

C语言:常用技巧及误用

一、字符串存储在数组中 int main() {char* arr[7] {"xiaoming","zhangsan","李四"};printf("%s\n", arr[0]);printf("%s\n", arr[2]);return 0; } 二、scanf()函数用法 2.1 scanf()输入字符串 int main() {char arr[10…

raksmart香港大带宽服务器地址

RAKsmart香港大带宽服务器的地址是由RAKsmart公司提供的香港机房所在地&#xff0c;具体地址未在公开资料中披露&#xff0c;但其主要特点是提供高带宽且不限制流量的服务。 RAKsmart是一家成立于2012年的美国公司&#xff0c;其香港机房以提供大带宽、直连内地的优化线路和丰富…

wincc 远程和PLC通讯方案

有 5个污水厂 的数据需要集中监控到1个组态软件上,软件是WINCC。每个污水厂监控系统都是独立的&#xff0c;已经投入运行了&#xff0c; 分站也是WINCC 和西门子PLC 。采用巨控远程模块的话&#xff0c;有两种方式&#xff1a;一种是从现场的PLC取数据&#xff0c;一种是从分站…

HubliderX将Vue3离线包打包生成App,以及解决打包后的APP出现白屏的问题(简单示例)

一、准备 HBuilderX官网&#xff0c;先去官网下载需要的工具到vue项目中把rooter的模式由“history”改为“hash”&#xff0c;否则在本地真机调试时会出现白屏 更改 vue.config.js文件&#xff0c;不修改的话&#xff0c;同样会出现白屏&#xff08;原因&#xff1a;app打开需…

java对接斑马打印机打印标签

JAVA对接斑马打印机打印RFID标签和普通标签 1、打印RFID标签 在打印RFID标签时&#xff0c;如果机器在没有校准的情况下进行打印标签&#xff0c;此时如果还需要获取到RFID的epc值&#xff0c;那么打印机返回的EPC值&#xff0c;有可能不是当前标签的epc值。考虑到此种情形&a…

技能 | next.js服务端渲染技术

哈喽小伙伴们大家好,我是程序媛小李,今天为大家分享一项前端开发中比较主流的服务端渲染技术:next.js 首先,next.js是什么? 通俗来讲,它就是一个React框架, 它能干啥?它能实现服务端渲染. 什么是服务端渲染? 一句话它就是在服务端生成整个页面的内容,用户在客户端只需要…