数据结构—平衡二叉搜索树(AVL)

AVL树是一种平衡二叉搜索树,确保任意节点的两个子树高度差不超过1,从而保证查找、插入和删除操作的平均时间复杂度为O(log2n)。文章介绍了AVL树的基本概念、旋转操作(左旋、右旋、左右旋、右左旋)以及插入和删除节点的平衡调整策略,帮助理解如何保持AVL树的平衡性。
摘要由CSDN通过智能技术生成

AVL树的引入

当二叉查找树处于平衡状态时,其操作的时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n),但是当二叉查找树是单支树时,其搜索效率将为 O ( n ) O(n) O(n)

如下图所示:
在这里插入图片描述
因此提出一些对二叉搜索树效率改进的树结构使最坏时间复杂度降为 O ( l o g 2 n ) O(log_2n) O(log2n),AVL树和红黑树就是其中的代表,除此之外,还有一些如AA-tree、B-tree、2-3-tree等。

第一个平衡二叉搜索树,即AVL树,AVL树得名于它的发明者前苏联数学家格奥尔吉·阿杰尔松-韦利斯基 (G.M.Adelson−Velsky) 和 叶夫吉尼·兰迪斯 (E.M.Landis)。

AVL树简介

它具有以下性质:

  • 本身首先是一棵二叉搜索树。
  • 它是一棵空树
  • 或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
  • 查找、插入和删除在平均和最坏情况下都是 O ( l o g 2 n ) O(log _2n) O(log2n)
  • 增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)

AVL树的数据结构

template<typename T>
class AVL
{
public:
	AVL() { _root = nullptr; }
public:
	struct AVLNode
	{
		AVLNode(T data = T())
			:_data(data)
			, _left(nullptr)
			, _right(nullptr)
			, _height(1)
		{}
		T _data;
		AVLNode *_left;
		AVLNode *_right;
		int _height; // 存储节点的高度
	};

我们知道,AVL是平衡二叉查找树,因此AVL树的基本操作与普通的二叉查找树是相似的,但是在我们进行结点的插入和删除时,有可能会破坏AVL树的平衡性,实时的保持这种平衡性非常重要。我们可以通过调整树结构,使之保持平衡,这种用以进行平衡化的操作被称为旋转

左旋操作

在这里插入图片描述
由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转

由上图可知我们是在结点T的右结点的右子树上做了插入元素的操作,我们称这种情况为右右情况,我们应该进行左旋转

具体旋转步骤是:T向右旋转成为R的左结点,同时,Y作为T的左孩子。

旋转过程图如下:
在这里插入图片描述
右右情况的左旋举例:
在这里插入图片描述
左旋代码实现:

// 左旋转操作 以node为根节点进行左旋转,返回旋转后的根节点
	AVLNode* leftRotate(AVLNode *node)
	{
		AVLNode *child = node->_right;
		node-> _right = child->_left;
		child->_left = node;
		node->_height = maxHeight(node->_left, node->_right) + 1;
        child->_height = maxHeight(child->_left,child->_right) + 1;
		return child;
	}

右旋操作

在这里插入图片描述
由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转。

由上图可知我们是在结点T的左结点的左子树上做了插入元素的操作,我们称这种情况为左左情况,我们应该进行右旋转

具体旋转步骤是:T向右旋转成为L的右结点,同时,Y作为T的左孩子。

旋转过程图如下:
在这里插入图片描述
左左情况的右旋举例:

右旋代码实现:

	AVLNode* rightRotate(AVLNode *node)
	{
		AVLNode *child = node->_left;
		node->_left = child->_right;
		child->_right = node;
		node->_height = maxHeight(node->_left, node->_right) + 1;
		child-> _height = maxHeight(child->_left, child->_right) + 1;
		return child;
	}

左右旋操作


由上图可知,我们在T结点的左结点的右子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1如果只是进行简单的右旋,得到的树仍然是不平衡的

我们应该按照如下图所示进行左右旋转
在这里插入图片描述

左右情况的左右旋转实例:

左右旋代码实现:

	// 左平衡  左-右旋转
	AVLNode* leftBalance(AVLNode *node)
	{
		node->_left=leftRotate(node->_left);
		node=rightRotate(node);
		return node;
	}

右左旋操作

由上图可知,我们在T结点的右结点的左子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1,如果只是进行简单的左旋,得到的树仍然是不平衡的。

我们应该按照如下图所示进行右左旋转

右左情况的右左旋转实例:

右左旋代码实现:

// 右平衡  右-左旋转
	AVLNode* rightBalance(AVLNode *node)
	{
		node->_right=rightRotate(node->_right);
		node=leftRotate(node);
		return node;
	}

AVL树的插入操作

AVL树也是一棵二叉搜索树,所以它的插入是和二叉搜索树的插入操作类似的,只是需要加上调整高度的操作

以左子树的插入操作为例(右子树类似):

  • 当我们给某结点的左子树插入新节点后,那么该结点的左子树高度便增加1, 此时有可能失衡。
  • 但是我们需要判断是由于向左子树插入左孩子导致失衡(需要右旋)
  • 还是向左子树插入右孩子导致失衡(需要左-右旋转)。
  • 判断方法就是判断当前结点的值与插入val的大小关系
  • 大于当前结点肯定是插到了当前结点的右边,小于当前结点肯定是插入到了当前结点的左边
  • 然后进行相应的旋转操作即可。

插入操作代码实现:

   AVLNode * insert(AVLNode *node, const T & val)
	{
		if (node == nullptr)
		{
			node = new AVLNode(val);
		}
		else
		{
			if (val < node->_data)//插入到左子树
			{
				node->_left = insert(node->_left, val);
				//需要进行判断
				if (height(node->_left) - height(node->_right) > 1)//失衡
				{
					/*
					 分两种情况:
					 1.左子树的左孩子引起的失衡
					 2.左子树的右孩子引起的失衡
					*/
					if (height(node->_left->_left)>=height(node->_left->_right))//情况1,进行右旋操作
					{
						node = rightRotate(node);
					}
					else //进行左右旋操作
					{
						node = leftBalance(node);
					}
				}
			}
			else if (val > node->_data)//往右子树找
			{
				node->_right = insert(node->_right, val);
				/*
				 分两种情况:
				 1.右子树的右孩子引起的失衡
				 2.右子树的左孩子引起的失衡
				*/
				if (height(node->_right) - height(node->_left) > 1)//失衡
				{
					if (height(node->_right->_right)>=height(node->_right->_left))//情况1,左旋
					{
						node = leftRotate(node);
					}
					else//右左旋
					{
						node = rightBalance(node);
					}
				}
			}
			else//要插入放入结点已经存在,不做操作			{
				;
			}
			//更新结点的高度
			node->_height = maxHeight(node->_left, node->_right) + 1;
		}
		return node;
	}

AVL树的删除操作

AVL树的删除操作二叉查找树的删除操作也是类似的。

同样分三种情况,但是对于第三种情况,也就是待删除结点同时拥有左右孩子的情况,对于之前的讲解是可以使用前驱或后继节点来替代当前节点并将其前驱或后继删除。
在AVL树的删除中,有可能导致失衡从而需要使用旋转操作来维护平衡,在这里我们可以进行优化,免去旋转操作
直接判断当前待删除结点的左右子树高度,若左子树高我们删除前驱,若右子树高或者高度相同我们删除后继。

但是对于普通的情况,即待删除结点只有一个孩子或者没有孩子的情况,我们就必须要进行相应判断进而看是否需要进行旋转操作来维护AVL树的平衡特性。

以删除左子树中的结点为例(右子树类似):
如果我们待删除的结点在左子树中找到,那么删除后,左子树高度降低,那么失衡肯定是由于右子树的高度过高导致的。
因此我们直接判断右子树的右孩子与右子树的左孩子的高度大小,若右子树的右孩子高度更高,那么我们进行左旋操作维护平衡,否则我们进行右-左双旋转维护平衡。


删除操作代码实现:

//递归实现AVL树的删除操作
   AVLNode * remove(AVLNode *node, const T & val)
   {
	   if (node == nullptr)
	   {
		   return  nullptr;
	   }
	   if (val < node->_data)//要删除的元素在左子树
	   {
		   node->_left = remove(node->_left, val);
	   }
	   else if (val > node->_data)//要删除的元素在右子树
	   {
		   node->_right = remove(node->_right, val);
	   }
	   else  //找到了
	   {
	       //情况3:有两个孩子
		   if (node->_left != nullptr&&node->_right != nullptr)
		   {
			   //用前驱节点的值替代要删除的结点的值
			   AVLNode *pre = nullptr;//用来记录前驱结点的父亲结点
			   AVLNode *cur = node->_left;//cur用来找前驱结点
			   while (cur->_right != nullptr)
			   {
				   pre = cur;
				   cur = cur->_right;
			   }
			   //此时cur代表前驱node的前驱结点
			   //用cur的值代表node的值
			   node->_data = cur->_data;
			   //删除前驱结点,注意前驱结点最多只可能有一个左孩子
			   node->_left = remove(node->_left, cur->_data);
		   }
		   else//只有一个孩子或没有孩子
		   {
			   AVLNode *tmp = node;
			   AVLNode *child = node->_left;
			   if (child == nullptr)
			   {
				   child = node->_right;
			   }
			   node = child;//用其孩子代替该node
			   delete(tmp);
		   }
	   }

	   //进行调整
	   if (node != nullptr)
	   {
		   node->_height = maxHeight(node->_left, node->_right);
		   if (height(node->_left) - height(node->_right) > 1)//左子树失衡
		   {
			   /*
			   * 如果左子树的左孩子的高度>左子树的右孩子的高度
			   * 则说明是左子树左孩子引起的失衡,需要进行右旋
			   */
			   if (height(node->_left->_left) > height(node->_left->_right))
			   {
				   node = rightRotate(node);
			   }
			   else if(height(node->_left->_left) < height(node->_left->_right))
			   {
					node = leftBalance(node);
			   }
		   }
		   else if (height(node->_right) - height(node->_left) > 1)//右子树失衡
		   {
			   /*
			   还是分两种情况:
			   1.右子树的右孩子引起的失衡
			   2.右子树的左孩子引起的失衡
			   */

			   //情况1,进行左旋
			   if (height(node->_right->_right) > height(node->_right->_left))
			   {
				   node = leftRotate(node);
			   }
			   else if (height(node->_right->_left) > height(node->_right->_left))
			   {
				   //进行右左旋
				   node = rightBalance(node);
			   }
		   }
	   }
	   return node;
   }

判断一棵二叉搜索树是否是平衡树

bool isAVL(AVLNode *node)
   { 
	   if (node == nullptr)
	   {
		   return true;
	   }
     
	   int left = height(node->_left);//左子树的高度
	   int right = height(node->_right);//右子树的高度
	   if (abs(left- right) > 1)
	   {
		   return false;
	   }
	   return isAVL(node->_left) && isAVL(node->_right);
   }

判断一颗二叉树是不是AVL树


bool isAVLTree()
{
	return isAVLTree(_root);
}
bool isAVLTree(AVLNode* node)
{
   static AVLNode* prev = nullptr;
	if (node == nullptr)
	{
		return true;
	}

	if (!isAVLTree(node->_left))
	{
		return false;
	}

	if (prev != nullptr && node->_data <= prev->_data)
	{
		return false;
	}

	if (abs(level(node->_left) - level(node->_right)) > 1)
	{
		return false;
	}

	prev = node;
	return isAVLTree(node->_right);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值