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

目录

一. 什么是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;
}
  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值