C++ 实现AVL树

目录​​​​​​​

0.二叉搜索树

1.AVL树的概念

 2.AVL树节点的定义

 3.AVL树的插入

4.AVL树的旋转逻辑

5.判断是否符合AVL树

6.完整代码

7.数据测试 


0.二叉搜索树

C++ 搜索二叉树-CSDN博客


1.AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年
发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树。
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)。

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
O(logn),搜索时间复杂度O(logn)


 2.AVL树节点的定义

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

	int _bf;  // 平衡因子

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

以下是该类的详细解释:

  1. 模板参数KV是两个类型参数,分别表示节点中键(Key)和值(Value)的类型。

  2. 成员变量

    • _left:指向左子节点的指针。
    • _right:指向右子节点的指针。
    • _parent:指向父节点的指针。注意,有些AVL树实现可能不包括此成员,但在某些情况下,如需要快速向上遍历树时,它是有用的。
    • _kv:一个pair,包含键和值。
    • _bf(平衡因子):用于表示该节点左子树和右子树的高度差。平衡因子的值可以是-1、0或1。如果AVL树中的任何节点的平衡因子超过这个范围,树就会失去平衡,需要进行旋转操作来恢复平衡。

 3.AVL树的插入

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		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 = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//...
		// 更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0) // 1 -1 -> 0
			{
				// 更新结束
				break;
			}
			else if(parent->_bf == 1 || parent->_bf == -1)  // 0 -> 1 -1
			{
				// 继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2) // 1 -1 -> 2 -2
			{
				// 当前子树出问题了,需要旋转平衡一下
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);//右单旋转
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);//、左单旋转
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);//右左二次旋转
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);//左右二次旋转
				}
				
				break;
			}
			else
			{
				// 理论而言不可能出现这个情况
				assert(false);
			}
		}
private:
    Node* _root =nullptr;
};

代码解释:

  1. 检查根节点是否为空:如果根节点为空,则创建一个新节点作为根节点并返回true。
  2. 遍历树以找到插入位置:使用parent和cur指针来遍历树,直到找到一个空位置(即cur为nullptr)或找到与key相等的节点。
  3. 处理重复键:如果找到与key相等的节点,函数返回false,表示插入失败(因为BST中通常不允许重复的键)。
  4. 插入新节点:在遍历结束后,根据parent节点的键值来决定新节点是应该作为左子节点还是右子节点。
  5. 更新平衡因子
    • 从插入节点的父节点开始,向上遍历树,更新每个遍历到的节点的平衡因子。
    • 如果新节点是父节点的左子节点,则父节点的平衡因子减1;如果是右子节点,则加1。
  6. 检查是否需要旋转
    • 在更新平衡因子的过程中,检查每个节点的平衡因子是否超出了允许的范围(-1, 0, 1)。
    • 如果某个节点的平衡因子为2或-2,则需要进行旋转操作来恢复树的平衡。
  7. 执行旋转操作
    • 根据不平衡节点的子节点的平衡因子,确定是进行左旋转还是右旋转。
    • 执行旋转操作,可能需要同时考虑子树的旋转。
    • 旋转后,重新计算并设置相关节点的平衡因子。
  8. 继续向上更新
    • 在旋转后,继续向上遍历树,检查并更新平衡因子,直到根节点或遇到一个平衡因子为0或±1的节点为止。
  9. 结束插入
    • 如果在遍历过程中没有遇到需要旋转的情况,或者已经通过旋转恢复了树的平衡,那么插入操作就完成了。

4.AVL树的旋转逻辑

1.不用旋转的理想状况:

每个节点的平衡因子都在允许的范围(-1, 0, 1)。

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
 

1. 新节点插入较高左子树的左侧---左左:右单旋

        上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加
        了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,
        即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

  1. 30节点的右孩子可能存在,也可能不存在
  2. 60可能是根节点,也可能是子树

                 如果是根节点,旋转完成后,要更新根节点

                如果是子树,可能是某个节点的左子树,也可能是右子树

代码:

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if(subLR)
			subLR->_parent = parent;

		subL->_right = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}

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

代码讲解:

  1. 准备工作
    • Node* subL = parent->_left;:获取当前需要旋转的节点parent的左子节点subL
    • Node* subLR = subL->_right;:获取subL的右子节点subLR,因为右单旋会将subL提升到parent的位置,而subLR将成为新的subL的左子节点。
  2. 处理parent的左子节点
    • parent->_left = subLR;:将parent的左子节点指针指向subLR
    • if(subLR) subLR->_parent = parent;:如果subLR不为空,则将其父节点指针指向parent
  3. 处理subL的右子节点
    • subL->_right = parent;:将subL的右子节点指针指向parent,完成右旋操作的核心步骤。
  4. 更新父节点指针
    • Node* ppNode = parent->_parent;:获取parent的父节点ppNode
    • parent->_parent = subL;:将parent的父节点指针指向subL
  5. 处理根节点情况
    • 如果parent是根节点_root,则执行以下操作:
      • _root = subL;:将根节点更新为subL
      • _root->_parent = nullptr;:将新的根节点的父节点指针设置为nullptr
  6. 处理非根节点情况
    • 如果parent不是根节点,则根据parentppNode的左子节点还是右子节点,更新ppNode的相应子节点指针。
      • 如果ppNode->_left == parent,则ppNode->_left = subL;
      • 否则,ppNode->_right = subL;
    • subL->_parent = ppNode;:将subL的父节点指针指向ppNode
  7. 重置平衡因子(这里可能不完全正确):
    • parent->_bf = subL->_bf = 0;:在实际应用中,这里只是简单地重置了parentsubL的平衡因子为0。但在AVL树中,平衡因子应该是根据子树的高度差来计算的。因此,在右单旋后,需要递归地更新这两个节点及其所有祖先节点的平衡因子。
  8. 更新高度和平衡因子
    • 在完成旋转后,需要遍历并更新所有受影响的节点的高度(如果树中存储了高度信息)。
    • 更新所有相关节点的平衡因子。平衡因子通常是左子树高度减去右子树高度。在右旋之后,ZY的平衡因子会发生变化。

2. 新节点插入较高右子树的右侧---右右:左单旋

实现及情况考虑可参考右单旋。

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppNode = parent->_parent;

		parent->_parent = subR;

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

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

3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再
考虑平衡因子的更新。

代码;

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

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

代码讲解:

  1. 准备工作
    • Node* subL = parent->_left;:获取当前需要旋转的节点parent的左子节点subL
    • Node* subLR = subL->_right;:获取subL的右子节点subLR,因为这个节点的高度过高,导致整棵树失去平衡。
    • int bf = subLR->_bf;:保存subLR的平衡因子,因为后面的更新会基于这个值。
  2. 执行左旋
    • RotateL(parent->_left);:对parent的左子树(即subL)执行左旋操作。左旋的目的是将subLR提升到subL的位置或更高层级,使得subL的右子树高度降低。
  3. 执行右旋
    • RotateR(parent);:在左旋之后,对parent执行右旋操作。右旋的目的是将subL(经过左旋后可能已经变化)提升到parent的位置或更高层级,进一步平衡树。
  4. 更新平衡因子
    • 根据subLR的原始平衡因子bf的值,更新subLRsubLparent的平衡因子。由于左旋和右旋后,这些节点的子树结构已经发生变化,因此需要重新计算或设置平衡因子。
    • if (bf == -1):如果subLR的原始平衡因子为-1,说明subLR的左子树比右子树高一个层级。经过双旋后,subLR的左右子树高度平衡,因此subLR->_bf = 0subL的右子树高度没有变化(因为subLRsubL的右子节点),所以subL->_bf = 0;而parent的左子树高度降低了一个层级(因为subL现在是parent的右子节点),所以parent->_bf = 1
    • else if (bf == 1):如果subLR的原始平衡因子为1,说明subLR的右子树比左子树高一个层级。经过双旋后,subLR的左右子树高度平衡,因此subLR->_bf = 0subL的右子树高度增加了一个层级(因为subLR的右子树现在成为了subL的左子树),所以subL->_bf = -1;而parent的左右子树高度没有变化,所以parent->_bf = 0
    • else if (bf == 0):如果subLR的原始平衡因子为0,说明subLR的左右子树高度平衡。经过双旋后,各节点的平衡因子均保持不变,均为0。
    • else:由于bf只可能是-1、0或1,所以这个else分支实际上是不会执行的。这里使用assert(false);是为了在开发阶段确保bf的值是合法的。

4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

实现及情况考虑可参考左右双旋。

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}


5.判断是否符合AVL树

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

		return max(_Height(root->_left), _Height(root->_right)) + 1;
	}

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

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		// 不平衡
		if (abs(leftHeight - rightHeight) >= 2)
		{
			cout << root->_kv.first << endl;
			return false;
		}
		
		// 顺便检查一下平衡因子是否正确
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << endl;
			return false;
		}

		return _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
  1. 基础情况

    • 如果rootnullptr(即树为空),那么它自然是平衡的,所以返回true
  2. 计算左右子树的高度

    • 使用_Height函数(这个函数在提供的代码段中没有给出,但我们可以假设它返回给定节点为根的子树的高度)计算左子树和右子树的高度。
  3. 检查高度差

    • 计算左右子树的高度差(abs(leftHeight - rightHeight))。
    • 如果这个高度差大于或等于2,说明树不是AVL树,所以打印出当前根节点的值(可能是为了调试目的),并返回false
  4. 检查平衡因子

    • AVL树的每个节点通常都有一个平衡因子(BF),它是右子树高度减去左子树高度的结果。
    • 接下来,代码检查右子树高度减去左子树高度得到的差值是否等于当前节点的平衡因子(root->_bf)。
    • 如果不等,说明平衡因子不正确,可能是在之前的旋转或插入/删除操作中出现了错误,所以打印出当前根节点的值(为了调试)并返回false
  5. 递归检查子树

    • 最后,代码递归地检查左子树和右子树是否都是AVL树。只有当两个子树都是AVL树时,当前树才是AVL树。

6.完整代码

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

	int _bf;  // balance factor

	AVLTreeNode(const 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:
	// logN
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		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 = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//...
		// 更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0) // 1 -1 -> 0
			{
				// 更新结束
				break;
			}
			else if(parent->_bf == 1 || parent->_bf == -1)  // 0 -> 1 -1
			{
				// 继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2) // 1 -1 -> 2 -2
			{
				// 当前子树出问题了,需要旋转平衡一下
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				
				break;
			}
			else
			{
				// 理论而言不可能出现这个情况
				assert(false);
			}
		}


		return true;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}


	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if(subLR)
			subLR->_parent = parent;

		subL->_right = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}

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

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppNode = parent->_parent;

		parent->_parent = subR;

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

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

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

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

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}

private:
	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

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

		return max(_Height(root->_left), _Height(root->_right)) + 1;
	}

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

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		// 不平衡
		if (abs(leftHeight - rightHeight) >= 2)
		{
			cout << root->_kv.first << endl;
			return false;
		}
		
		// 顺便检查一下平衡因子是否正确
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << endl;
			return false;
		}

		return _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

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

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};


7.数据测试 

void TestAVLTree1()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert({ e,e });

		cout << "Insert:" << e << "->" << t1.IsBalance() << endl;
	}

	t1.InOrder();

	cout << t1.IsBalance() << endl;
}

符合预期:

  • 31
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值