数据结构 | AVL(平衡二叉查找树)

AVL树的概念

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

  • 它的左右子树都是AVL
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是 AVL 树。如果它有 n个结点,其高度可保持在 O(log2​n) ,搜索时 间复杂度O(log2​n)

AVL树节点的定义

template <class T>
struct AVLNode
{
	AVLNode(T data = T())
		: _data(data), _left(nullptr), _right(nullptr), _height(1) {}
	AVLNode *_left;
	AVLNode *_right;
	T _data;        //数据域
	int _height;    //用来记录以当前节点为根节点的树的高度
};

AVL树的定义

template <class T>
class AVLTree
{
public:
	AVLTree() { _root = nullptr; }

private:
	struct AVLNode
    {
	    AVLNode(T data = T())
		    : _data(data), _left(nullptr), _right(nullptr), _height(1) {}
	    AVLNode *_left;
	    AVLNode *_right;
	    T _data;        //数据域
	    int _height;    //用来记录以当前节点为根节点的树的高度
    };
	AVLNode *_root;
    
    //当前节点的高度
	int height(AVLNode *node) const
	{
		return node == nullptr ? 0 : node->_height;
	}

    //返回左右子树最高的高度
	int maxHeight(AVLNode *node1, AVLNode *node2)
	{
		int height1 = height(node1);
		int height2 = height(node2);
		return height1 > height2 ? height1 : height2;
	}
};

AVL树的旋转

如果在一棵原本是平衡的 AVL 树中插入一个新节点,可能造成不平衡,此时就要进行检查。在插入新节点位置向上回溯的根节点位置进行检查各个节点的左右子树的高度差。进行平衡调整
二叉树的失衡主要的基本情况如下:
AVL树为了维护节点平衡,总共包含四种旋转操作,分别是:
1.节点失衡,是由于左孩子的左子树太高造成的,进行右旋转操作
右旋转操作:左孩子的右子树放到失衡节点左子树上,失衡节点放到左孩子的右子树上,左孩子成为根节点
代码:
	// /右旋转
	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;
	}

2.节点失衡,是由于右孩子的右子树太高造成的,进行左旋转操作
左旋转操作:右孩子的左子树放到失衡节点右子树上,失衡节点放到右孩子的左子树上,右孩子成为根节点
代码:
	//\左旋转
	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;
	}

3.节点失衡,是由于左孩子的右子树太高造成的,进行左-右旋转(也叫左平衡操作)操作
左平衡操作:先对失衡节点的左子树进行左旋操作,在对失衡节点进行右旋操作
代码:
	// 左右旋转  <
	AVLNode *_leftBlance(AVLNode *node)
	{
		node->_left = _leftRotate(node->_left);
		return _rightRotate(node);
	}

4.节点失衡,是由于右孩子的左子树太高造成的,进行右-左旋转(也叫右平衡操作)操作
右平衡操作:先对失衡节点的左子树进行右旋操作,在对失衡节点进行左旋操作
代码:
	//右左旋转  >
	AVLNode *_rightBlance(AVLNode *node)
	{
		node->_right = _rightRotate(node->_right);
		return _leftRotate(node);
	}

AVL树的插入

AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么 AVL 树的插入 过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的高度
思路:
  • 先按照BST树的插入操作进行节点插入
  • 插入后判断左右子树高度是否失衡
  • 若失衡判断需要单旋转还是双旋转
  • 更新节点高度 返回
代码:
	void remove(const T &val)
	{
		_root = _remove(_root, val);
	}	
    AVLNode *_insert(AVLNode *node, const T &val)
	{
		if (node == nullptr)
		{
			return new AVLNode(val);
		}

		//向左子树插入
		if (node->_data > val)
		{
			node->_left = _insert(node->_left, val);
			//进行旋转调整 插入左子树 必然左边高度大于右边高度
			if (height(node->_left) - height(node->_right) > 1)
			{
				//判断是 /还是 <
				if (node->_left->_data > val)
				{
					// /
					node = _rightRotate(node);
				}
				else
				{
					// <
					node = _leftBlance(node);
				}
			}
		}
		//向右子树插入
		else if (node->_data < val)
		{
			node->_right = _insert(node->_right, val);
			if (height(node->_right) - height(node->_left) > 1)
			{
				if (node->_right->_data > val)
				{
					// >进行右平衡
					node = _rightBlance(node);
				}
				else
				{
					// \ 左旋转
					node = _leftRotate(node);
				}
			}
		}

		//更新高度
		node->_height = maxHeight(node->_left, node->_right) + 1;
		return node;
	}

AVL树的删除

因为 AVL 树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新节点高度,只不过与插入不同的是,删除节点后的高度更新,最差情况下一直要调整到根节点的位置。
思路:与删除BST树一样需要讨论几个情况;
  • 当待删除节点左右子树都不为空时

将该节点的前驱或者后继赋值给该节点 转而去删除前驱或者后继。

这里注意判断该节点的左右子树高度,若左子树高删除前驱,若右子树高删除后继。原因,删除比较高的节点时不会影响整个AVL树的平衡,因此也就不需要调整

  • 当待删除节点只有一个子树节点

待删除节点在左子树中:判断右子树高度是否过高导致不平衡,若是,判断是右子树的左孩子高还是右子树的右孩子高,从而去进行相应的调整

待删除节点在右子树中:与上述相反

代码:

	void remove(const T &val)
	{
		_root = _remove(_root, val);
	}
	AVLNode *_remove(AVLNode *node, const T &val)
	{
		if (node == nullptr)
			return nullptr;

		if (node->_data > val)
		{
			//在左子树中找删除
			node->_left = _remove(node->_left, val);
			if (height(node->_right) - height(node->_left) > 1)
			{
				//  \ 左旋转
				if (height(node->_right->_right) > height(node->_right->_left))
				{
					node = _leftRotate(node);
				}
				else
				{
					//  >
					node = _rightBlance(node);
				}
			}
		}
		else if (node->_data < val)
		{
			//在右子树中找删除
			node->_right = _remove(node->_right, val);
			if (height(node->_left) - height(node->_right) > 1)
			{
				//  / 右旋转
				if (height(node->_left->_left) > height(node->_left->_right))
				{
					node = _rightRotate(node);
				}
				else
				{
					//  <
					node = _leftBlance(node);
				}
			}
		}
		else
		{
			//找到了 对于左右子树都存在 删除他的前去或者后继
			if (node->_left != nullptr && node->_right != nullptr)
			{
				// 比较左子树和右子树的高度,谁高删除谁,不用旋转
				if (height(node->_left) > height(node->_right))
				{
					//删除前驱
					AVLNode *p = node->_left;
					while (p->_right != nullptr)
						p = p->_right;
					node->_data = p->_data;
					node->_left = _remove(node->_left, p->_data);
				}
				else
				{
					//删除后继
					AVLNode *p = node->_right;
					while (p->_left != nullptr)
						p = p->_left;
					node->_data = p->_data;
					node->_right = _remove(node->_right, p->_data);
				}
			}
			else if (node->_left != nullptr)
			{
				AVLNode *child = node->_left;

				delete node;
				return child;
			}
			else if (node->_right != nullptr)
			{
				AVLNode *child = node->_right;
				delete node;
				return child;
			}
			else
			{
				return nullptr;
			}
		}
		//更新高度
		node->_height = maxHeight(node->_left, node->_right) + 1;
		return node;
	}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值