数据结构 AVL树

AVL树

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下
平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

在这里插入图片描述
在这里插入图片描述

平衡因子

某结点的右子树与左子树的高度(深度)差即为该结点的平衡因子。平衡二叉树上所有结点的平衡因子只可能是 -1,0 或 1
在这里插入图片描述

实现

结点信息

一个AVL树的结点包含了5个成员,一个是当前节点数据_data,一个是左孩子结点_left,一个是右孩子结点_right,一个是父节点_parent,一个是平衡因子_bf

template<class T>
struct AVLNode
{
	AVLNode<T>* _parent;
	AVLNode<T>* _left;
	AVLNode<T>* _right;
	T _val;
	int _bf;//平衡因子

	AVLNode(const T& val = T())
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_val(val)
		,_bf(0)//初始化为0,叶子无左右孩子
	{}
};

//AVL树
template<class T>
class AVLTreeNode
{
public:
	typedef AVLNode<T> Node;

private:
	Node* _root;
};

插入

  1. 查找插入的位置
  2. 创建新结点并连接
  3. 修改平衡因子,调整成AVL树

第三步要分很多种情况,例如我们拿下图来举例说明
在这里插入图片描述
1、假如我们在结点23的左边插入结点20此时此数还是一棵平衡的二叉树,需要修改其父路径上的平衡因子
在这里插入图片描述
2、假如我们在结点4的左边插入一个节点3,此时此树还是一棵平衡的树,只需要修改其父节点的平衡因子,因为此结点的插入后,往上追溯的平衡因子修改后为0,并不会对平衡因子为0的父节点的平衡因子造成影响。
在这里插入图片描述
3、假如我们在上一幅图中再插入一个节点18。则此时已经不是一棵AVL树了,因为结点23的平衡因子不在[-1, 1]的范围之内。此时需要进行旋转调整
调整前:
在这里插入图片描述
调整后
在这里插入图片描述
但这种调整只是其中的一种,我们下面看看调整以及如何调整

右单旋

当我们的子树如下图结构时,并且在a处插入一个结点而导致的不平衡,也就是E结点平衡因子小于-1。此时就需要在结点E进行右单旋
在这里插入图片描述
左边的左边高,需要降低左子树的高度,提高右子树的高度。调整后结点S和结点E的平衡因子都为0,其他因子不受影响。
在这里插入图片描述

动画演示:中间的一条根为S结点的父节点
在这里插入图片描述
需要修改的连接有6条连接,4个结点
在这里插入图片描述

右旋核心代码如下:

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

		parent->_left = subLR;
		if (subLR)//如果不为空需要更新_parent
			subLR->_parent = parent;

		if (parent == _root) //父节点为根,则不考虑pparent的连接
		{
			//更新根节点
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		subL->_right = parent;
		parent->_parent = subL;//必须为最后一个连接

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

	}

测试:
我们先给出insert的部分代码

	bool insert(const T& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}

		//1.查找到要插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;//记录插入节点的父节点
			if (cur->_val == val)//存在相同节点退出
				return false;
			else if (cur->_val > val)//插入节点比当前节点小,则硬插入当前节点的左子树中
				cur = cur->_left;
			else
				cur = cur->_right;
		}

		//2.连接节点关系
		cur = new Node(val);
		if (parent->_val > val)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//3.修改平衡因子,调整成AVL树
		while (parent)
		{
			//更新父节点的平衡因子,判断是否还平衡
			if (parent->_left == cur)
				--parent->_bf;
			else
				++parent->_bf;

			if (parent->_bf == 0) //表示parent的子树比较短的子树高度+1,不影响parent的父节点的平衡因子,插入完毕
				break;
			else if (parent->_bf == 1 || parent->_bf == -1)//表示parent更新前为0,更新后不为0,树的高度有变,会影响parent的父节点
			{
				//向上追溯修改parent父节点的平衡因子
				cur = parent;
				parent = parent->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				if (parent->_bf == -2 && cur->_bf == -1)//左边的左边高
				{
					//右单旋
					RotateR(parent);
				}
				//...
				break;
			}
		}
		return true;
	}

测试代码:

void test()
{
	AVLTreeNode<int> avl;
	avl.insert(5);
	avl.insert(3);
	//    5
	// 3
	avl.insert(1); //右单旋
	//    3
	// 1     5 
	avl.insert(0);
	avl.insert(2);
	avl.insert(-1);//右单旋
	//右旋前
	//       3
	//    1      5
	//  0  2
	//-1
	//右旋后
	//       1
	//    0      3
	// -1      2   5
}

运行结果:
执行完insert(1)后
在这里插入图片描述
执行完insert(-1)后,以1位根的左右子树
在这里插入图片描述

左单旋

当在右子树的右节点中插入一个节点而导致树的平衡因子大于2。此时就需要进行左单旋。应用场景如下
在这里插入图片描述
右边的右边高,需要降低右边的高度,从而提高左边的高度。调整后结点S和结点E的平衡因子都为0,其他因子不受影响。
在这里插入图片描述
动画演示:中间的一条根为S结点的父节点
在这里插入图片描述
连接方式和右单旋非常类似,只是连接的结点不同
在这里插入图片描述
左旋代码

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

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = bullptr;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
		subR->_left = parent;
		parent->_parent = subR;

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

测试:

void test()
{
	AVLTreeNode<int> avl;
	avl.insert(5);
	avl.insert(3);
	avl.insert(1); //右单旋
	avl.insert(0);
	avl.insert(2);
	avl.insert(-1);//右单旋
	//       1
	//    0      3
	// -1      2   5
	avl.insert(10);
	avl.insert(15); //左单旋
	//左单旋前
	//       1
	//    0      3
	// -1      2   5
	//               10
	//                  15
	//左单旋后
	//       1
	//    0      3
	// -1      2    10
	//             5  15
	avl.insert(20);//左单旋
	//左单旋前
	//       1
	//    0      3
	// -1      2    10
	//            5    15
	//                   20
	//左单旋后
	//			3
	//		1		 10
	//	0     2		5   15
	//-1		     	   20

}

走到第一个左单旋后进行旋转后左右子树情况
在这里插入图片描述
走到第二个左单旋后进行旋转后左右子树情况
在这里插入图片描述

左右双旋 和 右左双旋

左边的右边高,此时需要进行两次旋转,先在C结点进行左旋,再在P结点进行右旋
在这里插入图片描述
我们在左旋和右旋旋转完后他们他们的结点的平衡因子都设置为0,可是我们发现p结点旋转完后的平衡因子不是0,所以我们在旋转后完应该要分情况处理。我们再来看看在c子树下插入结点后他们的平衡因子如何
在这里插入图片描述
我们发现P结点的平衡因子为0,而C结点的平衡因子为-1。总结下来就是谁拿到高的那边,谁的平衡因子就为0。而没拿到的另一边的高度应该减1

代码

else if (parent->_bf == -2 && cur->_bf == 1) //左边的右边高
{
	//左右双旋
	Node* subLR = cur->_right;
	int bf = subLR->_bf;
	RotateL(cur);
	RotateR(parent);
	if (bf == -1)
	{
		parent->_bf = 1;
		cur->_bf = 0;
	}
	if (bf == 1)
	{
		parent->_bf = 0;
		cur->_bf = -1;
	}
}

右边的左边高,此时需要进行两次旋转,先在C结点进行右旋,再在P结点进行左旋
在这里插入图片描述

和左右双旋类似,CL结点的平衡因子不同,导致最后P结点和C结点的平衡因子也不同
在这里插入图片描述
当在CL结点的右边插入时,C结点的平衡因子为0,P结点的平衡因子为-1;当在CL结点的左边插入结点时,C的结点的平衡因子为1,P结点的平衡因子为0

代码

else if(parent->_bf == 2 && cur->_bf == -1) //右边的左边高
{
	//右左双旋
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(cur);
	RotateL(parent);
	if (bf == 1)
	{
		parent->_bf = -1;
		cur->_bf = 0;
	}
	if (bf == -1)
	{
		parent->_bf = 0;
		cur->_bf = 1;
	}
}

代码我们都写好了,现在我们就通过验证AVL树来判断我们写的是否正确

总结

当以parent为根的子树不平衡,也就是parent的平衡因子为2或者-2时,会根据不同的平衡原因来进行不同的旋转操作

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR,右子树的左子树的根为subRL
    1.1 当subR的平衡因子为1时,表示的是右子树的右边高,在parent结点进行左单旋操作
    1.2 当subR的平衡因子为-1时,表示右子树的左边高,此时需要进行右左双旋,也就是以subR结点先进行右单旋,再以parent结点进行左单旋。当subRL的平衡因子为1时,表示在subRL的右边插入新的结点,此时需要更新parent的平衡因子为-1,subR的平衡因子为0;当subRL的平衡因子为-1时,表示在subRL的左边插入新的结点,此时需要更新parent的平衡因子为0,subR的平衡因子为1
  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL,左子树的右子树的根为subLR
    2.1 当subL的平衡因子为-1时,表示左子树的左边高,此时需要进行右单旋
    2.2 当subL的平衡因子为1时,表示左子树的右边高,此时需要进行左右双旋,也就是以subL结点先进行左单旋,再以parent结点进行右单旋。当subLR的平衡因子为1时,表示在subLR的右边插入新的结点,此时需要更新parent的平衡因子为0,subL的平衡因子为-1;当subLR的平衡因子为-1时,表示在subLR的左边插入新的结点,此时需要更新parent的平衡因子为1,subR的平衡因子为0

AVL树的验证

判断AVL树的两个条件

  1. 该树是一个二叉搜索树
  2. 该树的左右孩子的高度差不能大于1

验证第一个条件可以使用中序遍历来判断,当输出的结果是有序时表示该树为二叉搜索树。我们通过随机值的插入与遍历,来验证该树是否是有序输出

	void inorder()
	{
		_inorder(_root);
		cout << endl;
	}

	void _inorder(Node* root)
	{
		if (root)
		{
			_inorder(root->_left);
			cout << root->_val << " ";
			_inorder(root->_right);
		}
	}

运行结果:升序输出,该树为二叉搜索树
在这里插入图片描述
我们知道一棵树的平衡因子为右子树的高度减去左子树的高度,我们去递归获取子树的高度再利用右左子树高度差比较,相等就继续向下判断,一旦不相等或者平衡因子有误则表示不是AVL树

	bool isBalance()
	{
		cout << endl;
		return _isBalance(_root);
	}

	int height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int left = height(root->_left);
		int right = height(root->_right);

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

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

		//查看平衡因子是否和左右子树的高度差一致
		int left = height(root->_left);
		int right = height(root->_right);

		if (right - left != root->_bf || abs(root->_bf) > 1)//不相等或者平衡因子有误则表示不是AVL树
			return false;
		return _isBalance(root->_left) && _isBalance(root->_right);
	}

运行结果:该树是一棵AVL树
在这里插入图片描述

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
AVL树是一种自平衡二叉搜索树,它的平衡因子(左子树高度减去右子树高度)在任何时候都是-1、0或1。当插入或删除节点后,如果AVL树失去平衡,则需要通过旋转来重新平衡它。 在Python中实现AVL树,可以使用节点类和AVL树类。节点类包括节点值、左子树、右子树、高度和平衡因子等属性。AVL树类包括根节点、插入节点、删除节点、旋转方法和平衡方法等方法。 下面是一个简单的Python实现AVL树的代码示例: ```python class Node: def __init__(self, val): self.val = val self.left = None self.right = None self.height = 1 self.balance = 0 class AVLTree: def __init__(self): self.root = None def insert(self, val): self.root = self._insert(self.root, val) def _insert(self, node, val): if not node: return Node(val) if val < node.val: node.left = self._insert(node.left, val) else: node.right = self._insert(node.right, val) node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) node.balance = self.get_balance(node) if node.balance > 1 and val < node.left.val: return self.right_rotate(node) if node.balance < -1 and val > node.right.val: return self.left_rotate(node) if node.balance > 1 and val > node.left.val: node.left = self.left_rotate(node.left) return self.right_rotate(node) if node.balance < -1 and val < node.right.val: node.right = self.right_rotate(node.right) return self.left_rotate(node) return node def delete(self, val): self.root = self._delete(self.root, val) def _delete(self, node, val): if not node: return node if val < node.val: node.left = self._delete(node.left, val) elif val > node.val: node.right = self._delete(node.right, val) else: if not node.left or not node.right: temp = node.left if node.left else node.right if not temp: node = None else: node = temp else: temp = self.get_min(node.right) node.val = temp.val node.right = self._delete(node.right, temp.val) if not node: return node node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) node.balance = self.get_balance(node) if node.balance > 1 and self.get_balance(node.left) >= 0: return self.right_rotate(node) if node.balance < -1 and self.get_balance(node.right) <= 0: return self.left_rotate(node) if node.balance > 1 and self.get_balance(node.left) < 0: node.left = self.left_rotate(node.left) return self.right_rotate(node) if node.balance < -1 and self.get_balance(node.right) > 0: node.right = self.right_rotate(node.right) return self.left_rotate(node) return node def right_rotate(self, node): left_child = node.left node.left = left_child.right left_child.right = node node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) left_child.height = 1 + max(self.get_height(left_child.left), self.get_height(left_child.right)) node.balance = self.get_balance(node) left_child.balance = self.get_balance(left_child) return left_child def left_rotate(self, node): right_child = node.right node.right = right_child.left right_child.left = node node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) right_child.height = 1 + max(self.get_height(right_child.left), self.get_height(right_child.right)) node.balance = self.get_balance(node) right_child.balance = self.get_balance(right_child) return right_child def get_height(self, node): if not node: return 0 return node.height def get_balance(self, node): if not node: return 0 return self.get_height(node.left) - self.get_height(node.right) def get_min(self, node): while node.left: node = node.left return node ``` 这个实现包括插入、删除、旋转和平衡等基本操作。你可以按需调用这些方法,来实现你的具体需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhiteShirtI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值