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

2023-11-17

一.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;
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 如何获取正在访问 ASP.NET 应用程序的当前用户?

    为了获取系统中当前登录的用户 我使用以下代码 string opl System Security Principal WindowsIdentity GetCurrent Name ToString 我正在开发一个 ASP NET 应用程
  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • C 编程 - 文件 - fwrite

    我有一个关于编程和文件的问题 while current NULL if current gt Id Doctor 0 current current gt next id doc current gt Id Doctor if curre
  • 以文化中立的方式将字符串拆分为单词

    我提出了下面的方法 旨在将可变长度的文本拆分为单词数组 以进行进一步的全文索引处理 删除停止词 然后进行词干分析 结果似乎不错 但我想听听关于这种实现对于不同语言的文本的可靠性的意见 您会建议使用正则表达式来代替吗 请注意 我选择不使用 S
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • C# 中通过 Process.Kill() 终止的进程的退出代码

    如果在我的 C 应用程序中 我正在创建一个可以正常终止或开始行为异常的子进程 在这种情况下 我通过调用 Process Kill 来终止它 但是 我想知道该进程是否已退出通常情况下 我知道我可以获得终止进程的错误代码 但是正常的退出代码是什
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • WCF 中 SOAP 消息的数字签名

    我在 4 0 中有一个 WCF 服务 我需要向 SOAP 响应添加数字签名 我不太确定实际上应该如何完成 我相信响应应该类似于下面的链接中显示的内容 https spaces internet2 edu display ISWG Signe
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • while 循环中的 scanf

    在这段代码中 scanf只工作一次 我究竟做错了什么 include
  • 对现有视频添加水印

    我正在寻找一种用 C 在视频上加水印的方法 就像在上面写文字一样 图片或文字标签 我该怎么做 谢谢 您可以使用 Nreco 视频转换器 代码看起来像 NReco VideoConverter FFMpegConverter wrap new
  • WPF/C# 将自定义对象列表数据绑定到列表框?

    我在将自定义对象列表的数据绑定到ListBox in WPF 这是自定义对象 public class FileItem public string Name get set public string Path get set 这是列表
  • cmake 将标头包含到每个源文件中

    其实我有一个简单的问题 但找不到答案 也许你可以给我指一个副本 所以 问题是 是否可以告诉 cmake 指示编译器在每个源文件的开头自动包含一些头文件 这样就不需要放置 include foo h 了 谢谢 CMake 没有针对此特定用例的
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new

随机推荐

  • 国产操作系统产业

    操作系统是计算机的灵魂 目前国外操作系统品牌几乎垄断了巨大的中国市场 其中在桌面端 移动端的市占率分别超过94 75 98 86 根据Gartner的统计数据 2018年中国的操作系统市场容量在189亿以上 其中国外操作系统品牌几乎在中国市
  • Gossip协议

    Gossip协议 一 Gossip协议 1 1 工作原理 1 2 Gossip优点 1 3 Gossip传播方式 1 3 1 Anti Entropy 反熵 1 3 2 Rumor Mongering 谣言传播 1 3 3 结合 1 4 G
  • 初识C语言(二)

    目录 五 字符串 六 转义字符 七 注释 7 1注释的类型 7 1 1单行注释 7 1 2多行注释 7 2注释的使用方法 7 2 1解释代码功能注释 7 2 2提供代码示例注释 7 2 3禁用或屏蔽代码 八 选择语句 8 1if语句 8 1
  • 利用python3自动在36kr里查找自己感兴趣的内容

    最近常常在36kr网站的快讯及资讯 最新里查看自己感兴趣内容的及时信息 由于快讯及资讯 最新里信息更新得比较及时快速 自己也很难一直盯着看 故想着要是写个脚本让其自动在后天挂着每隔5分钟查询一次 有的话就写入txt档中并在控制台打印出来 这
  • 小孩学创客编程好还是学机器人好

    小孩学创客编程好还是学机器人好 小孩的学习一直都是很多家长们非常关心和重视的一件事情 很多的家长在培养孩子的学习的时候 可以说是十分的用心的 会给孩子选择一些能够有利于孩子成长的课程 就拿现在很多的家长想要孩子去学习机器人编程的课程来说 有
  • 域环境的搭建的详细教程-220109

    参考链接 https mp weixin qq com s src 11 timestamp 1641696209 ver 3547 signature zTIDZEcpq zjwuEuZpbaaAxFfkkVxcLHeX4AuKT78bJ
  • 第九届蓝桥杯 2018年省赛真题 (Java 大学C组 )

    蓝桥杯 2018年省赛真题 Java 大学C组 第一题 哪天返回 第二题 猴子分香蕉 第三题 字母阵列 第四题 第几个幸运数 第五题 书号验证 第六题 打印大X 第七题 缩位求和 第八题 等腰三角形 第九题 小朋友崇拜圈 第十题 耐摔指数
  • RHCE——DNS的正反向解析

    一 实验要求 DNS配置正反向解析 二 实验过程 1 安装软件包 root localhost ll yum install bind y 2 备份bind软件的的配置文件 root localhost yum repos d cp a e
  • CMAKE学习——编译多个文件 & 多个目录

    大型工程会有很多文件 包括类的实现和定义 各种不同的模块交叉在一起 我们怎么用cmake方便的编译呢 例如有这么一个工程 我们现在想要编译的话 如果只选择了main cpp 则会提示 未定义的引用 因为我们头文件和实现分离 但我们只包含了头
  • 【云原生之Docker实战】使用Docker部署jenkins持续集成工具

    云原生之Docker实战 使用Docker部署jenkins持续集成工具 一 jenkins介绍 1 jenkins简介 2 jenkins功能 3 jenkins基本工作图 二 检查本地系统版本 三 检查本地docker状态 1 检查do
  • IDEA运行报错:类文件具有错误的版本 55.0, 应为 52.0 请删除该文件或确保该文件位于正确的类路径子目录中。

    IDEA运行报错 类文件具有错误的版本 55 0 应为 52 0 请删除该文件或确保该文件位于正确的类路径子目录中 如果搜索资料 会看到minor major版本 但其实不叫这个名字 Sun公司会在大的版本升级时增加major数字 小更新或
  • 【python】自动化测试框架--nose

    目录 一 准备 二 nose介绍 三 看个简单的例子了解下 三 nose常用命令简单介绍 1 查看所有nose相关命令 2 执行并捕获输出 3 提供XUnit XML 格式的测试结果 并存储在nosetests xml文件中 主要为jenk
  • 程序员的自我修养--链接、装载与库

    中国科学技术大学软件学院 周艾亭 原创作品版权所有转载请注明出处 第一次接触 程序员的自我修养 的时候 的确怀有一种疑惑的态度的 因为潜意识告诉我 在计算机这一行 更强调的是实践动手 而XXX修养的显然不属于动手操作类 至少不是太适合我的需
  • 数据同步方案

    mysql 数据同步到elastic中 本文中不提及实现 仅提供方案 增量数据同步 方案一 通过logstash 官方提供的工具 快速实现数据同步 值得注意的是选择logstash时需要和elastic的版本做对应 由于elastic 版本
  • 多线程经典案例(生产者--消费者)

    多线程开发中有一个经典的操作案例 就是 生产者 消费者 案例 生产者不的生产产品 消费者不断地取走产品 此案例涉及线程同步 线程休眠 线程等待 线程唤起等操作以及之间是如何搭配使用的方法 示例讲解 本示例模拟中生产者由 厨师 担任 消费者由
  • 如何利用 Selenium 对已打开的浏览器进行爬虫

    大家好 在对某些网站进行爬虫时 如果该网站做了限制 必须完成登录才能展示数据 而且只能通过短信验证码才能登录 这时候 我们可以通过一个已经开启的浏览器完成登录 然后利用程序继续操作这个浏览器 即可以完成数据的爬取了 具体操作步骤如下 1 1
  • QT循环队列实时处理数据(二)

    上一篇多线程介绍的是 QT多线程处理机制 这篇 将对接收数据 实时处理进行分析 QT通过socket通信 从接收缓冲区中读取数据 交给线程进行处理 那么问题来了 如果线程还没有处理完数据 则线程就没有办法继续从缓冲区中取数 那么当数据量过大
  • vue父子组件之间的传值(子传父,父传子)

    vue父子组件之间的传值 子传父 父传子 前提首先需要了解vue中组件之间的父子关系 主组件mainPage vue
  • 个性化定制界面和极简版原装界面,哪一个你用起来更加顺手呢

    个性化定制界面是根据用户的需求和喜好进行定制的 具有很高的灵活性和可定制性 用户可以自由选择界面的颜色 布局 字体等 以及添加或删除特定功能 这种界面能够根据用户的个人喜好和习惯进行定制 使得用户在使用过程中更加舒适和顺手 以下是一些可能的
  • 【数据结构】&&【C++】平衡搜索二叉树的模拟实现(AVL树)

    数据结构 C 平衡搜索二叉树的模拟实现 AVL树 一 AVL树的性质 二 AVL树的模拟实现 AVL树结点的定义 AVL树的插入 平衡因子的更新 左单旋 右单旋 双旋 左右旋 右左旋 AVL树的删除 检查是否是AVL树 三 完整代码 一 A