C++数据结构:手撕AVL树

news2025/2/25 15:40:05

目录

一. 什么是AVL树

二. AVL树的节点定义

三. AVL树的插入操作

3.1 寻找插入位置

3.2 更新平衡因子

3.3 AVL树的旋转调整

3.4 AVL树插入操作的整体实现

四. AVL树的检验

附录:AVL树的实现完整代码 

AVL树定义代码 -- AVLTree.h

AVL树检验代码 -- test.cpp


一. 什么是AVL树

AVL树,是二叉搜索树的一种特殊形式。一般的二叉搜索树,如果插入节点的数据有序或十分接近有序,那么二叉搜索树就会退化为近似单叉树,这样查找数据的时间复杂度就会变为O(1),从而失去高效查找的能力。

为了解决普通二叉搜索树的这一缺陷,两位俄罗斯数学家G.M.Adelson-Velskii 和E.M.Landis在1962年发明了一种二叉搜索树的平衡模式 -- AVL树,AVL树的左右子树的高度差不超过1,因此可以保证时间复杂度为O(logN)的查找。

AVL树要么为空树,要么满足以下三个条件:

  • 左右子树均为AVL树。
  • 左右子树的高度差(平衡因子)不超过1。
  • 节点的数据满足二叉搜索树(左子树节点小于根节点,右子树节点大于根节点)

其中,平衡因子 = 右子树高度 - 左子树高度。

图1.1 AVL树典型结构

二. AVL树的节点定义

AVL树一般定义为三叉链结构,每个节点包含3个指针,分别为:指向左子树根节点的指针_left、指向右子树根节点的指针_right、指向父亲节点的指针_parent。同时,每个节点还应当记录该节点的平衡因子_bf以及节点数据_kv(键值对)。

图2.1 AVL树的节点结构示意图

代码2.1:(AVL树节点定义)

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	std::pair<K, V> _kv;
	int _bf;   //平衡因子

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

三. AVL树的插入操作

3.1 寻找插入位置

AVL树寻找插入位置的操作规则,与普通的二叉搜索树一致,为:

  • 如果当前位置为nullptr,那么该位置为插入节点的位置。
  • 如果当前节点值大于要插入的值,到该节点的左子树去查找。
  • 如果当前节点值小于要插入的值,到该节点的右子树去查找。
  • 如果当前节点值等于要插入的值,则插入失败。(二叉搜索树一般不允许存在相同的节点)。

代码3.1:(查找插入位置并插入节点)

	//找要插入节点的位置
	Node* cur = _root;   //root为AVL树的根节点
	Node* parent = nullptr;
	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;
		}
	}

	//插入节点
	Node* newNode = new Node(kv);
	if (parent->_kv.first > kv.first)
	{
		parent->_left = newNode;
		newNode->_parent = parent;
	}
	else
	{
		parent->_right = newNode;
		newNode->_parent = parent;
	}

3.2 更新平衡因子

由于插入节点会影响新插入节点的父亲节点的左右子树高度,因此,父亲节点的平衡因子需要更新,平衡因子的更新遵循以下几条规则。

  1. 如果新节点插入在右子树,那么父亲节点的平衡因子+1。
  2. 如果新节点插入在左子树,那么父亲节点的平衡因子-1。
  3. 若插入节点后,parent->_bf == 1或-1成立,说明parent节点原来的平衡因子为0,左右子树高度相同,那么新插入的节点引发了parent的高度变化,但还没有打破平衡,需要继续向上更新。
  4. 如果插入节点后,parent->_bf == 0成立,那么原来父亲节点的平衡因子为-1或1,且插入在了父亲节点较矮的子树中,parent的高度没有发生改变,不继续向上更新。
  5. 若插入节点后,parent->_bf == 2或-2成立,那么parent原来的平衡因子为1或-1,处于平衡的临界状态,继续插入节点平衡被打破,不再满足AVL树的结构要求,需要进行旋转调整(详见3.3)。
  6. 若插入节点后,parent->_bf>=3或parent->_bf<=-3,那说明之前的插入操作存在问题,需要进行检查排错。
图3.1 平衡因子调整逻辑

代码3.2:(调整平衡因子)

	//调整平衡因子
	cur = newNode;  //新节点
	while (parent)
	{
		//如果新插入的节点位于右子树,那么父亲节点平衡因子+1
		if (parent->_right == cur)
		{
			++parent->_bf;
		}
		else if (parent->_left == cur)
		{
			//如果新插入的节点位于左子树,那么父亲节点的平衡因子-1
			--parent->_bf;
		}
		else
		{
			//int a = 0;
			assert(false);
		}

		if (parent->_bf == 0)
		{
			//如果插入节点后父亲节点的平衡因子变为0,那么父亲节点原来的平衡因子为1或-1
			//那么插入节点在较矮的一侧,树的高度没有发生变化
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			//说明原来父亲节点的平衡因子为0,插入后树的高度发生变化,要继续向上更改平衡因子
			parent = parent->_parent;
			cur = cur->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//在较长的一边插入,此时结构已不满足AVL树的结构,要进行旋转
			// ......  旋转调整代码略

			break;
		}
		else if (parent->_bf >= 3 || parent->_bf <= -3)
		{
			assert(false);
		}
	}

3.3 AVL树的旋转调整

如果插入新节点后,parent->_bf == 2/-2成立,那么就需要通过旋转调整来使树的结构平衡,对于AVL树的选择,可以分为以下四种情况来讨论。

  1. 如果插入节点在较高右子树的右子树 -- 右右:单左旋调整
  2. 如果插入节点在较高左子树的左子树 -- 左左:单右旋调整
  3. 如果插入节点在较高右子树的左子树 -- 右左:先进行单右旋再进行单左旋(右左双旋调整)
  4. 如果插入节点在较高左子树的右子树 -- 左右:先进行单左旋再进行单右旋(左右双旋调整)

左单旋

  • 将右子节点的左子节点托管给parent的右子节点,然后将parent节点托管给右子节点的左子节点。之后,将原父亲节点和右子节点的平衡因子全部更新为0。
图3.2 左单旋逻辑图

代码3.3:(左单旋)

	void RotateL(Node* parent)   //左单旋函数
	{
		Node* ppNode = parent->_parent;
		Node* pR = parent->_right;
		Node* pRL = pR->_left;

		//右子节点的左子节点托管给父亲节点的右子节点
		parent->_right = pRL;
		if (pRL != nullptr)
		{
			pRL->_parent = parent;
		}
		
		//父亲节点托管给右子节点的左子节点
		pR->_left = parent;
		parent->_parent = pR;

		if (_root == parent)
		{
			_root = pR;
			pR->_parent = nullptr;
		}
		else
		{
			pR->_parent = ppNode;
			if (ppNode->_left == parent)
			{
				ppNode->_left = pR;
			}
			else
			{
				ppNode->_right = pR;
			}
		}

		parent->_bf = 0;
		pR->_bf = 0;
	}

右单旋

  • 将左子节点的右子节点托管给parent的左子节点,然后将parent节点托管给原左子节点的右子节点,更新原parent节点和左子节点的平衡因子为1。
图3.3 右单旋逻辑图

代码3.4:(右单旋) 

	void RotateR(Node* parent)    //右单旋函数
	{
		Node* ppNode = parent->_parent;
		Node* pL = parent->_left;
		Node* pLR = pL->_right;

		//将左子节点的右子节点托管给父亲节点的左子节点
		parent->_left = pLR;
		if (pLR != nullptr)
		{
			pLR->_parent = parent;
		}

		//将父亲节点托管给左子节点的右子节点
		pL->_right = parent;
		parent->_parent = pL;

		if (_root == parent)
		{
			_root = pL;
			pL->_parent = nullptr;
		}
		else
		{
			pL->_parent = ppNode;
			if (ppNode->_left == parent)
			{
				ppNode->_left = pL;
			}
			else
			{
				ppNode->_right = pL;
			}
		}

		pL->_bf = 0;
		parent->_bf = 0;
	}

右左双旋

先对parent节点的右子节点pR进行右单旋操作,然后对parent节点进行左单旋操作。根据pR的左子节点pRL的平衡因子(三种情况讨论),确定旋转后每个节点的平衡因子进行更新。

图3.4 右左双旋的三种情况

 代码3.5:(右左双旋)

	void RotateRL(Node* parent)   //右左双旋函数
	{
		Node* pR = parent->_right;
		Node* pRL = pR->_left;
		int bf = pRL->_bf;    //右子节点的左子节点的平衡因子

		RotateR(pR);  //对右子节点进行右单旋
		RotateL(parent);  //对父亲节点进行左单旋

		//更新平衡因子
		if (bf == 1)
		{
			pR->_bf = 0;
			parent->_bf = -1;
			pRL->_bf = 0;
		}
		else if (bf == -1)
		{
			pR->_bf = 1;
			parent->_bf = 0;
			pRL->_bf = 0;
		}
		else if(bf == 0)
		{
			pRL->_bf = 0;
			pR->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

左右双旋

先针对parent节点的左子节点pL进行单左旋,然后再对parent节点进行单右旋,最后根据pL节点的右子节点pLR的平衡因子,更改每个节点的平衡因子。

图3.5 左右双旋的三种情况

代码3.6:(左右双旋)

	void RotateLR(Node* parent)
	{
		Node* pL = parent->_left;
		Node* pLR = pL->_right;
		int bf = pLR->_bf;

		RotateL(pL);
		RotateR(parent);   //前后执行左右单旋

		if (bf == 1)
		{
			pL->_bf = -1;
			parent->_bf = 0;
			pLR->_bf = 0;
		}
		else if (bf == -1)
		{
			pL->_bf = 0;
			parent->_bf = 1;
			pLR->_bf = 0;
		}
		else if(bf == 0)
		{
			pL->_bf = 0;
			parent->_bf = 0;
			pLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

3.4 AVL树插入操作的整体实现

通过3.1~3.3的分析,我们可以总结出,AVL树的插入操作步骤如下:

  1. 查找插入位置,找到了就新建节点插入,找不到函数终止运行。
  2. 调整平衡因子。
  3. 如果出现parent->_bf == 2或parent->_bf == -2,那么就对AVL树进行旋转调整。

代码3.7:(AVL树插入操作的主函数)

    bool insert(const std::pair<K, V>& kv)  //节点插入函数
	{
		if (_root == nullptr)
		{
			Node* newNode = new Node(kv);   //新节点
			_root = newNode;
			return true;
		}

		//找要插入节点的位置
		Node* cur = _root;
		Node* parent = nullptr;
		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;
			}
		}

		//插入节点
		Node* newNode = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = newNode;
			newNode->_parent = parent;
		}
		else
		{
			parent->_right = newNode;
			newNode->_parent = parent;
		}

		//调整平衡因子
		cur = newNode;  //新节点
		while (parent)
		{
			//如果新插入的节点位于右子树,那么父亲节点平衡因子+1
			if (parent->_right == cur)
			{
				++parent->_bf;
			}
			else if(parent->_left == cur)
			{
				//如果新插入的节点位于左子树,那么父亲节点的平衡因子-1
				--parent->_bf;
			}
			else
			{
				//int a = 0;
				assert(false);
			}
			
			if (parent->_bf == 0)
			{
				//如果插入节点后父亲节点的平衡因子变为0,那么父亲节点原来的平衡因子为1或-1
				//那么插入节点在较矮的一侧,树的高度没有发生变化
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//说明原来父亲节点的平衡因子为0,插入后树的高度发生变化,要继续向上更改平衡因子
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//在较长的一边插入,此时结构已不满足AVL树的结构,要进行旋转
				//旋转要分4种情况讨论

				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 >= 3 || parent->_bf <= -3)
			{
				assert(false);
			}
			
		}

		return true;
	}

四. AVL树的检验

要验证通过插入节点创建的AVL树是否正确,应当通过下面两重检验:

  • 验证其为搜索二叉树。
  • 验证其为平衡树。

验证搜索二叉树

搜索二叉树的检验,只需对二叉树进行中序遍历,如果得到的结果为升序排列的数据,那就说明该树为搜索二叉树。

代码4.1:(中序遍历)

	//中序遍历函数
	void InOrder()
	{
		_InOrder(_root);
		std::cout << std::endl;
	}
    
    //子函数
    void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		std::cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

验证平衡树

平衡树的验证也需要以下两重检验:

  • 检验左右子树的高度差是否不超过1,如果任何一颗子树的左右子树高度差超过1,则不满足平衡树的结构要求。
  • 检验平衡因子是否正确,算出 右子树高度 - 左子树高度 的值,与本节点的平衡因子比较,看是否相等,不等就不是平衡树。

代码4.2:(平衡树检验)

	bool isAVLTree()  //检查是否为AVL树(平衡树)
	{
		return _isAVLTree(_root);
	}

	bool _isAVLTree(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHigh = TreeHigh(root->_left);
		int rightHigh = TreeHigh(root->_right);   //调用TreeHigh函数求左右子树高度
		
		if (root->_bf != rightHigh - leftHigh)
		{
			std::cout << "平衡因子异常" << " ";
			return false;
		}
		
		return abs(rightHigh - leftHigh) < 2 && 
                _isAVLTree(root->_left) && 
                _isAVLTree(root->_right);
	}

代码4.3:(求二叉树的高度)

	int TreeHigh(Node* root)
	{
		if (nullptr == root)
		{
			return 0;
		}

		int left = TreeHigh(root->_left);
		int right = TreeHigh(root->_right);

		return left > right ? left + 1 : right + 1;
	}

附录:AVL树的实现完整代码 

AVL树定义代码 -- AVLTree.h

//AVLTree.h
#pragma once

#include<iostream>
#include<utility>
#include<assert.h>
#include<math.h>

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	std::pair<K, V> _kv;
	int _bf;   //平衡因子

	AVLTreeNode(const std::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 std::pair<K, V>& kv)  //节点插入函数
	{
		if (_root == nullptr)
		{
			Node* newNode = new Node(kv);   //新节点
			_root = newNode;
			return true;
		}

		//找要插入节点的位置
		Node* cur = _root;
		Node* parent = nullptr;
		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;
			}
		}

		//插入节点
		Node* newNode = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = newNode;
			newNode->_parent = parent;
		}
		else
		{
			parent->_right = newNode;
			newNode->_parent = parent;
		}

		//调整平衡因子
		cur = newNode;  //新节点
		while (parent)
		{
			//如果新插入的节点位于右子树,那么父亲节点平衡因子+1
			if (parent->_right == cur)
			{
				++parent->_bf;
			}
			else if(parent->_left == cur)
			{
				//如果新插入的节点位于左子树,那么父亲节点的平衡因子-1
				--parent->_bf;
			}
			else
			{
				//int a = 0;
				assert(false);
			}
			
			if (parent->_bf == 0)
			{
				//如果插入节点后父亲节点的平衡因子变为0,那么父亲节点原来的平衡因子为1或-1
				//那么插入节点在较矮的一侧,树的高度没有发生变化
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//说明原来父亲节点的平衡因子为0,插入后树的高度发生变化,要继续向上更改平衡因子
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//在较长的一边插入,此时结构已不满足AVL树的结构,要进行旋转
				//旋转要分4种情况讨论

				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 >= 3 || parent->_bf <= -3)
			{
				assert(false);
			}
			
		}

		return true;
	}

	//中序遍历函数
	void InOrder()
	{
		_InOrder(_root);
		std::cout << std::endl;
	}

	bool isAVLTree()  //检查是否为AVL树
	{
		return _isAVLTree(_root);
	}

private:
	bool _isAVLTree(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHigh = TreeHigh(root->_left);
		int rightHigh = TreeHigh(root->_right);
		
		if (root->_bf != rightHigh - leftHigh)
		{
			std::cout << "平衡因子异常" << " ";
			return false;
		}
		
		return abs(rightHigh - leftHigh) < 2 && _isAVLTree(root->_left) && _isAVLTree(root->_right);
	}

	int TreeHigh(Node* root)
	{
		if (nullptr == root)
		{
			return 0;
		}

		int left = TreeHigh(root->_left);
		int right = TreeHigh(root->_right);

		return left > right ? left + 1 : right + 1;
	}

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

		_InOrder(root->_left);
		std::cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	void RotateL(Node* parent)   //左单旋函数
	{
		Node* ppNode = parent->_parent;
		Node* pR = parent->_right;
		Node* pRL = pR->_left;

		//右子节点的左子节点托管给父亲节点的右子节点
		parent->_right = pRL;
		if (pRL != nullptr)
		{
			pRL->_parent = parent;
		}
		
		//父亲节点托管给右子节点的左子节点
		pR->_left = parent;
		parent->_parent = pR;

		if (_root == parent)
		{
			_root = pR;
			pR->_parent = nullptr;
		}
		else
		{
			pR->_parent = ppNode;
			if (ppNode->_left == parent)
			{
				ppNode->_left = pR;
			}
			else
			{
				ppNode->_right = pR;
			}
		}

		parent->_bf = 0;
		pR->_bf = 0;
	}

	void RotateR(Node* parent)    //右单旋函数
	{
		Node* ppNode = parent->_parent;
		Node* pL = parent->_left;
		Node* pLR = pL->_right;

		//将左子节点的右子节点托管给父亲节点的左子节点
		parent->_left = pLR;
		if (pLR != nullptr)
		{
			pLR->_parent = parent;
		}

		//将父亲节点托管给左子节点的右子节点
		pL->_right = parent;
		parent->_parent = pL;

		if (_root == parent)
		{
			_root = pL;
			pL->_parent = nullptr;
		}
		else
		{
			pL->_parent = ppNode;
			if (ppNode->_left == parent)
			{
				ppNode->_left = pL;
			}
			else
			{
				ppNode->_right = pL;
			}
		}

		pL->_bf = 0;
		parent->_bf = 0;
	}

	void RotateRL(Node* parent)   //右左双旋函数
	{
		Node* pR = parent->_right;
		Node* pRL = pR->_left;
		int bf = pRL->_bf;    //右子节点的左子节点的平衡因子

		RotateR(pR);  //对右子节点进行右单旋
		RotateL(parent);  //对父亲节点进行左单旋

		//更新平衡因子
		if (bf == 1)
		{
			pR->_bf = 0;
			parent->_bf = -1;
			pRL->_bf = 0;
		}
		else if (bf == -1)
		{
			pR->_bf = 1;
			parent->_bf = 0;
			pRL->_bf = 0;
		}
		else if(bf == 0)
		{
			pRL->_bf = 0;
			pR->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateLR(Node* parent)
	{
		Node* pL = parent->_left;
		Node* pLR = pL->_right;
		int bf = pLR->_bf;

		RotateL(pL);
		RotateR(parent);   //前后执行左右单旋

		if (bf == 1)
		{
			pL->_bf = -1;
			parent->_bf = 0;
			pLR->_bf = 0;
		}
		else if (bf == -1)
		{
			pL->_bf = 0;
			parent->_bf = 1;
			pLR->_bf = 0;
		}
		else if(bf == 0)
		{
			pL->_bf = 0;
			parent->_bf = 0;
			pLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

private:
	Node* _root = nullptr;  //根节点
};

AVL树检验代码 -- test.cpp

//test.cpp
#include<iostream>
#include<stdlib.h>
#include<time.h>
#include "AVLTree.h"

void TestAVLTree1()
{
	AVLTree<int, int> at;
	int arr[] = { 10, 9, 7 };

	for (auto& e : arr)
	{
		at.insert(std::make_pair(e, 0));
	}

	int a = 0;
}

void TestAVLTree2()
{
	AVLTree<int, int> at;
	//srand(time(nullptr));

	for (int i = 0; i < 1000; ++i)
	{
		int e = rand();
		std::cout << e << " " << "num=" << i << std::endl;
		at.insert(std::make_pair(e, i));

		bool ret = at.isAVLTree();
		if (!ret)
		{
			std::cout << "不是AVL树" << " ";
		}
		else
		{
			std::cout << "是AVL树" << " ";
		}
		std::cout << std::endl;
	}

	at.InOrder();
}

int main()
{
	TestAVLTree2();
	return 0;
}

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

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

相关文章

你了解PostProcessor机制吗?

Spring框架对于后置处理器的最佳实践 PostProcessor译为后置处理器&#xff0c;大多数开发人员都使用过springboot对后置处理器的实例级别实践&#xff0c;也就是BeanPostProcessor接口。其实spring还提供了两种容器级别的实践&#xff1a;BeanDefinitionRegistryPostProcesso…

今天试了试chatgpt

今天试了试chatgpt&#xff0c;真是服了 arcade&#xff1f; Arcade是一个Python游戏开发库&#xff0c;它提供了一系列的工具和函数&#xff0c;可以帮助开发者快速地创建2D游戏。以下是Arcade的一些特点&#xff1a; 简单易用&#xff1a;Arcade提供了简单易用的API&#x…

egg3.0连接egg-mongoose插入一条数据、插入多条数据

插入一条数据 app/router.js use strict;/*** param {Egg.Application} app - egg application*/ module.exports app > {const { router, controller } app;router.get(/, controller.home.index);router.get(/role, controller.role.index);router.post(/role/add, co…

【ChatGPT】如何用十分钟部署一个属于自己的chatgpt网站

&#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是Zeeland&#xff0c;全栈领域优质创作者。&#x1f4dd; CSDN主页&#xff1a;Zeeland&#x1f525;&#x1f4e3; 我的博客&#xff1a;Zeeland&#x1f4da; Github主页: Undertone0809 (Zeeland) (github.com)&…

【两个月算法速成】day03-链表

目录 203. 移除链表元素 题目链接 思路 代码 206. 反转链表 题目链接 思路 代码 总结 203. 移除链表元素 题目链接 力扣 思路 如下图所示就是移除链表的过程 但是值得注意的是&#xff0c;移除头节点和其他位置的节点是不一样的&#xff0c;以为头结点前面没有节点。…

每日学术速递4.24

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Collaborative Diffusion for Multi-Modal Face Generation and Editing(CVPR 2023) 标题&#xff1a;多模态人脸生成和编辑的协同扩散 作者&#xff1a;Ziqi Huang, Kelvin C.K. …

Vue3进阶使用详解(node.js、Vue3路由基础项目、axios的使用详细(实现数据分页---前后端分离)、axios加载失败)

Vue3进阶使用详解(node.js、Vue3路由基础项目、axios的使用详细(实现数据分页—前后端分离)、axios加载失败) Vue cli CLI是Commond-Line Interface&#xff0c;翻译为命令界面&#xff0c;又称脚手架。VueCLI是一个官方发布vue.js项目脚手架。使用VueCLI可以快速搭建vue开发…

【IAR工程】STM8S基于ST标准库读取DHT11数据

【IAR工程】STM8S基于ST标准库读取DHT11数据 ✨申明&#xff1a;本文章仅发表在CSDN网站&#xff0c;任何其他网站&#xff0c;未注明来源&#xff0c;见此内容均为盗链和爬取&#xff0c;请多多尊重和支持原创!&#x1f341;对于文中所提供的相关资源链接将作不定期更换。&…

HTTP协议 GET和POST区别 请求响应 Fiddler postman ajax

&#x1f496; 欢迎来阅读子豪的博客&#xff08;JavaEE篇 &#x1f934;&#xff09; &#x1f449; 有宝贵的意见或建议可以在留言区留言 &#x1f4bb; 欢迎 素质三连 点赞 关注 收藏 &#x1f9d1;‍&#x1f680;码云仓库&#xff1a;补集王子的代码仓库 不要偷走我小火…

Mac下nvm安装使用

​欢迎光临我的博客查看最新文章: https://river106.cn 1、简介 nvm 是 Mac 下的 node.js 管理工具。可以通过 nvm 安装和切换不同版本的 node.js。 官网&#xff1a;https://nvm.uihtm.com/ github&#xff1a;https://github.com/nvm-sh/nvm 2、安装 curl -o- https://raw…

移动端适配rem方案

做移动端的适配我们就是要考虑&#xff0c;对于不同大小的手机屏幕&#xff0c;怎么动态改变页面布局中所有盒子的宽度高度、字体大小等。 这个问题我们可以使用相对单位rem。 那么什么是 rem&#xff1f; rem&#xff08;font size of the root element&#xff09;是指相对…

Linux-中断和时间管理(上)

目录 中断的进入过程 中断的进入过程 为方便实验&#xff0c;本章以配套的目标板 FS4412为例来介绍 Linux 的中断子系统&#xff0c;并且编写相应的中断处理程序。FS4412 上的处理器是 SAMSUNG公司的 Exynos4412&#xff0c;该处理器使用的是4核的 Cortex-A9&#xff0c;&…

c++Lambda匿名函数

cLambda匿名函数 &#xff08;1&#xff09; 定义a. [外部变量方位方式说明符]b. (参数)c. mutabled.noexcept/throw()e.->返回值类型f.函数体 2&#xff09;c11中的拉姆达表达式中的&#xff08;&#xff09;可以省略吗 所谓匿名函数&#xff0c;简单地理解就是没有名称的函…

《C++ Primer Plus》(第6版)第17章编程练习

《C Primer Plus》&#xff08;第6版&#xff09;第17章编程练习 《C Primer Plus》&#xff08;第6版&#xff09;第17章编程练习1. 计算输入流中第一个\$之前的字符数目2. 将键盘输入&#xff08;直到模拟的文件尾&#xff09;复制到通过命令行指定的文件中3. 将一个文件复制…

完全免费的基于区块链和 IPFS 的去中心化博客平台

一、前言 xLog是一个基于Crossbell区块链的博客解决方案&#xff0c;专注于Web3数据由用户掌控。Crossbell是一个基于Web3技术的去中心化博客平台&#xff0c;用户可以在该平台上发布文章并进行交流和创作。社区提供多种交流平台和有奖创作活动。 xLog是基于 Crossbell 区块链…

【AI回复】“我问它,你对五一调休怎么看”

前言 马上就要到五一啦&#xff0c;放假打算去哪里玩呢&#xff1f; “我肯定是宅在家里写博客啊” 最近五一调休在某博上引起大家的共鸣&#xff0c;看了评论那叫一个惨不忍睹哇。 因为我比较对AI感兴趣&#xff0c;所以想看看它是怎么看待调休的。 首先&#xff0c;在百度…

【UE】简易的水材质

引擎版本&#xff1a;4.26 效果 步骤 1. 创建一个材质&#xff0c;命名为“M_Water” 2. 打开“M_Water”&#xff0c;将混合模式设为半透明&#xff0c; 光照模式设为表面半透明体积&#xff0c;在这种模式下我们可以使用金属度、粗糙度等接口 3. 创建一个4维常量节点&…

Android 基于NumberPicker自定义弹出窗口Dialog整合日期选择器

Android实现把年月选择器放到AlertDialog中_左眼看成爱的博客-CSDN博客 Android使用NumberPicker实现年月滚动选择器_左眼看成爱的博客-CSDN博客 前面两篇文章我们分别讲了 1&#xff0c;如何用NumberPicker实现年月选择器 2&#xff0c;如何把1中的用NumberPicker实现的年…

基于DE2-115平台实现VGA显示器的显示实验

目录 什么是VGA协议VGA显示原理VGA时序图VGA参数图实验记录准备PLLROM取模代码data_drive.vkey_debounce.vvga_drive.vvga_top.v 实验现象 什么是VGA协议 这一部分摘录自野火的征途Pro《FPGA Verilog开发实战指南——基于Altera EP4CE10》2021.7.10&#xff08;上&#xff09;…

ctfshow web入门phpcve web311-315

1.web311 通过抓包发现php版本时为PHP/7.1.33dev 漏洞cve2019-11043 远程代码执行漏洞 利用条件&#xff1a; nginx配置了fastcgi_split_path_info 受影响系统&#xff1a; PHP 5.6-7.x&#xff0c;Nginx>0.7.31 下载工具进行利用 需要安装go环境 yum install golang -y …