【高阶数据结构】AVL树(动图详解)_avl tree

文章目录

请添加图片描述

一. AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下

因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

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

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称 平衡因子)的绝对值不超过1(-1/0/1)
  • 平衡因子= 右子树高度 - 左子树高度;非必须,也可以不要,只是方便我们实现的一种方式!

在这里插入图片描述
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在

O

(

l

o

g

2

n

)

O(log_2 n)

O(log2​n),搜索时间复杂度O(

l

o

g

2

n

log_2 n

log2​n)

单支树的效率是

O

(

N

)

O(N)

O(N),AVL树不一样,在10亿中只用找30次(可能多一点)

在这里插入图片描述

二. AVL树结点的定义

此处我们定义成三叉链结构 ,方便后序的操作;也在每个节点引入了平衡因子(右子树高度-左子树高度),还需要实现一下构造函数,左右子树以及父节点都是空,再把平衡因子设置为0即可

template<class K, class V>
struct AVLTreeNode
{
	//定义三叉链
	AVLTreeNode<K, V>\* _left;
	AVLTreeNode<K, V>\* _right;
	AVLTreeNode<K, V>\* _parent;
	 
	//存储的键值对
	pair<K, V> _kv;

	//平衡因子(balance factor)
	int _bf;

	//构造函数
	AVLTreeNode(const pair<K, V>& kv)
		:\_left(nullptr)
		,\_right(nullptr)
		,\_parent(nullptr)
		,\_kv(kv)
		,\_bf(0)
	{}
};

注意:平衡因子不是必须的,只是我们实现高度平衡的一种方式,不用平衡因子也是可以实现的

三. AVL树的插入

插入节点有三个步骤

  1. 按照二叉搜索树的原理,找到待插入的位置
  2. 判断待插入的节点是在parent的左还是右,插入节点
  3. 更新平衡因子,如果发现不平衡,则要旋转

🔥因为AVL树本身就是一颗二叉搜索树,插入规则(比较节点大小即可):

  • 插入的节点key值 > 当前位置的key值,插入到右子树
  • 插入的节点key值 < 当前位置的key值,插入到左子树
  • 插入的节点key值等于当前位置的key值,插入失败

🌈那判断完插入成功与否,是不是就要判断平衡因子的更新了

平衡因子是否更新取决于:该结点的左右子树的高度是否发生了变化,因此插入一个结点后,该结点的 祖先结点的平衡因子可能需要更新

在这里插入图片描述

🌏更新平衡因子的规则:

  • 新增在右,parent -> bf++;新增在左,parent -> bf --

每更新完一个结点的平衡因子后,都需要进行以下判断:

  1. 如果parent的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子
  2. 如果parent的平衡因子等于0;表明无需往上更新平衡因子
  3. 如果parent的平衡因子等于-2或者2;就已经不平衡了,需要旋转处理
  4. 如果parent的平衡因子大于2或者小于-2;就说明之前插入的就不是AVL树了,赶紧去检查💥
更新后的平衡因子分析
-1 or 1说明parent插入前的平衡因子是0;左右子树高度相等,插入后有一边高,parent高度变了,则需要往上继续更新
0说明parent插入前的平衡因子是 -1 or 1;左右子树一边高一边低,插入后两边相等,插入的填上了矮的那一边,parent的高度不变,不需要继续往上更新
-2 or 2说明parent插入前的平衡因子是 -1 or 1;已经是平衡的临界值了;插入后变成-2 or 2 ;打破了平衡,parent所在的子树需要旋转处理

最坏的情况如下:一路更新到root根节点

在这里插入图片描述

那么我们更新平衡因子时第一个更新的就是parent结点的平衡因子,更新完parent结点的平衡因子后,若是需要继续往上进行平衡因子的更新,向上递归,直到parent为空的情况,以下逻辑是必须的

	cur = parent;
	parent = parent->_parent; 

当平衡因子出现了2/-2的情况,要对子树进行旋转处理,但也要遵守原则

  • 旋转成平衡树
  • 保持搜索树的规则

而旋转有四种大情况,对此我们要进行分类:

  1. 当parent的平衡因子为2,cur的平衡因子为1时,进行左单旋
  2. 当parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋
  3. 当parent的平衡因子为-2,cur的平衡因子为1时,进行左右双旋
  4. 当parent的平衡因子为2,cur的平衡因子为-1时,进行右左双旋

注意:旋转过后无需再往上更新平衡因子了,因为高度已经没有发生变化了,也就不会影响父节点的平衡因子了

	//插入
	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)//插入节点值大于当前节点的key
			{
				parent = cur;
				cur = cur->_right;//往右走
			}
			else if (cur->_kv.first > kv.first)//插入节点值小于当前节点的key
			{
				parent = cur;
				cur = cur->_left;//往左走
			}
			else
			{
				//插入的节点key值等于当前位置的key值,插入失败
				return false;
			}
		}
		//开始插入
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else if (parent->_kv.first < kv.first)
		{
			parent->_left = cur;
		}

		//连接parent
		cur->_parent = parent;

		//控制平衡
		//1、更新平衡因子

		while (parent)
		{
			if (cur == parent->right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == -1 || parent->_bf == 1)//也可以用abs
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//说明parent所在的子树已经不平衡了,需要旋转
				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)
				{
					RotateLR(parent);//左右双旋
				}

				break;
			}
			else
			{
				assert(false);//在插入前树的平衡因子就有问题
			}
		}
		return true;
	}


四. AVL树的旋转

🥑左单旋

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

⚡动图展示:

请添加图片描述

抽象图过程解析:

在这里插入图片描述
其中h可以等于0、1、2等等,不过都可以归纳到这种大情况,处理情况都一样,都是引发parent 等于2,cur等于1

左单旋旋转步骤:

  1. subRL变成parent的右子树(subL和parent的关系,要注意🔥subL可能为空
  2. parent成为subR的左子树(parent和subLR的关系)
  3. subR成为根节点(ppNode 和 subL关系,也要注意🔥parent是子树的根还是整棵树的根
  4. 最后更新平衡因子

在这里插入图片描述

为什么要这样旋转?要符合二叉搜索树规则

  • subR的左子树的值本身就比parent的值要大,所以可以作为parent的右子树
  • parent及其左子树当中结点的值本身就比subR的值小,所以可以作为subR的左子树

平衡因子更新:

在这里插入图片描述

可以看见,左单旋后树的高度就平衡了,也就无需继续向上更新平衡因子了

代码实现如下:(详细注释)

	void RotateL(Node\* parent)
	{
	    //三叉链
		Node\* subR = parent->_right;
		Node\* subLR = subR->_left;
		Node\* ppNode = parent->_parent;

		//subR 和 parent之间的关系
		subR->_left = parent;
		parent->_parent = subR;

		//subRL 和 parent之间的关系
		subRL = parent->_right;
		if (subRL)
			subRL->parent = parent;

		//ppNode 和 subR的关系
		if (ppNode == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;//没有父节点,所以为空
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
		
		//更新平衡因子
		subR->_bf = parent->_bf = 0;
	}

🥑右单旋(和左单旋高度相似)

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

动图演示:

请添加图片描述抽象图过程解析:

在这里插入图片描述

右单旋旋转步骤:

与左单旋雷同,看上面就行

同样也要满足二叉搜索树的性质:也是和左单旋雷同,看上面就行

平衡因子更新如下:

在这里插入图片描述同样右单旋后,parent的平衡因子为0,左右子树高度相等,也就无需继续往上更新平衡因子了

话不多说上代码:

//右单旋
void RotateR(Node\* parent)
{
	Node\* subL = parent->_left;
	Node\* subLR = subL->_right;
	Node\* ppNode = parent->_parent;

	//subL 和 parent的关系
	subL->_right = parent;
	parent->_parent = subL;

	//subLR 和 parent之间的关系
	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;

	//ppNode 和 subL的关系
	if (ppNode == nullptr)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left == subL;
		}
		else
		{
			ppNode->_right == subL;
		}
		subL->_parent = ppNode;
	}
		
	//更新平衡因子
	subL->_bf = parent->_bf = 0;


### 文末

我一直觉得技术面试不是考试,考前背背题,发给你一张考卷,答完交卷等通知。

首先,技术面试是一个 认识自己 的过程,知道自己和外面世界的差距。



更重要的是,技术面试是一个双向了解的过程,要让对方发现你的闪光点,同时也要 试图去找到对方的闪光点,因为他以后可能就是你的同事或者领导,所以,面试官问你有什么问题的时候,不要说没有了,要去试图了解他的工作内容、了解这个团队的氛围。

**前端面试题汇总**

![](https://img-blog.csdnimg.cn/img_convert/1471537bfd3f5a483690ca477fd3f2b9.png)

**JavaScript**

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

![](https://img-blog.csdnimg.cn/img_convert/7796de226b373d068d8f5bef31e668ce.png)

**性能**

![](https://img-blog.csdnimg.cn/img_convert/d7f6750332c78eb27cc606540cdce3b4.png)

**linux**

![](https://img-blog.csdnimg.cn/img_convert/ed368cc25284edda453a4c6cb49916ef.png)

**前端资料汇总**

![](https://img-blog.csdnimg.cn/img_convert/6e0ba223f65e063db5b1b4b6aa26129a.png)

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值