平衡二叉树(AVL)

上一篇我们聊过,二叉查找树不是严格的O(logN),如果找到一种方法,使得二叉查找树不受输入序列和插入结点等的影响,始终保持平衡状态,从而达到很好的检索效率.平衡二叉树就是基于此目的而产生的.

定义:平衡二叉树或为空树,或为如下性质的二叉排序树(平衡二叉树是一种特殊的二叉排序树):
(1)左右子树深度之差的绝对值不超过1;
(2)左右子树仍然为平衡二叉树.
平衡因子BF=左子树深度-右子树深度.
平衡二叉树每个结点的平衡因子只能是1,0,-1。若其绝对值超过1,则该二叉排序树就是不平衡的。

如图所示为平衡树和非平衡树示意图:
这里写图片描述

显然,如果有n个结点,则他的高度为O(logN),因而在其上进行的各种操作,都只需要O(logN)的时间代价.但问题在于如何保持一颗avl树的结构,使得对他的任何操作,都不改变他的平衡特性.实际上,主要是通过旋转的局部操作来调整使其保持平衡特性.

旋转
左旋:
这里写图片描述
对x进行左旋,意味着”将x变成一个左节点”。

右旋:
这里写图片描述
对y进行右旋,意味着”将y变成一个右节点”。

结点再怎么失衡都逃不过4种情况,下面我们来看一下怎么对这4中情况进行对应的旋转。
<1>.LL型平衡旋转法 —- 右旋

这里写图片描述
我们看到,在向树中追加“节点1”的时候,根据定义我们知道这样会导致了“节点3”失衡,可以这样想,把这棵树比作齿轮,我们在“节点5”处把齿轮往下拉一个位置,也就变成了后面这样“平衡”的形式.

<2>.RR型平衡旋转法 —- 左旋

这里写图片描述
同样,”节点5“满足”RR型“,其实我们也看到,这两种情况是一种镜像,当然操作方式也大同小异,我们在”节点1“的地方将树往下拉一位,最后也就形成了我们希望的平衡效果。

<3>.LR型平衡旋转法 —- 左右旋

这里写图片描述
从图中我们可以看到,当我们插入”节点3“时,“节点5”处失衡,注意,找到”失衡点“是非常重要的,当面对”LR型“时,我们将失衡点的左子树进行”RR型左旋转”,然后进行”LL型右旋转“,经过这样两次的旋转就OK了,很有意思,对吧。

<4>.RL型平衡旋转法 —- 右左旋

这里写图片描述
这种情况和“情景3”也是一种镜像关系,很简单,我们找到了”节点15“是失衡点,然后我们将”节点15“的右子树进行”LL型右旋转“,然后进行”RR型左旋转“,最终得到了我们满意的平衡。

插入
如果我们理解了上面的这几种旋转,那么添加方法简直是轻而易举,出现了哪一种情况调用哪一种旋转方法而已。

删除
同理,删除也是.

### AVL树的定义 AVL树是一种自平衡二叉搜索树,以其发明者G.M.Adelson-Velsky和E.M.Landis的名字命名[^3]。它通过维护树的高度平衡来确保操作的时间复杂度保持在O(log n)级别。具体来说,在AVL树中,任意节点的两个子树的高度差(称为平衡因子)最多为1。 #### 平衡条件 - 对于任何一个节点,其左子树和右子树都必须是AVL树。 - 节点的平衡因子范围限定为{-1, 0, 1}。 --- ### AVL树的实现 以下是基于C++语言的一个典AVL树实现框架: #### 结构体定义 ```cpp struct AVLNode { int key; int height; AVLNode* left; AVLNode* right; AVLNode(int k) : key(k), height(1), left(nullptr), right(nullptr) {} }; ``` #### 获取高度函数 用于计算某个节点的高度。 ```cpp int getHeight(AVLNode* node) { if (node == nullptr) return 0; return node->height; } ``` #### 更新高度函数 每次修改树结构后都需要更新节点的高度。 ```cpp void updateHeight(AVLNode* node) { if (node != nullptr) { node->height = std::max(getHeight(node->left), getHeight(node->right)) + 1; } } ``` #### 左旋操作 当右子树过重时执行左旋。 ```cpp AVLNode* rotateLeft(AVLNode* y) { AVLNode* x = y->right; AVLNode* T2 = x->left; // 执行旋转 x->left = y; y->right = T2; // 更新高度 updateHeight(y); updateHeight(x); return x; // 新的根节点 } ``` #### 右旋操作 当左子树过重时执行右旋。 ```cpp AVLNode* rotateRight(AVLNode* x) { AVLNode* y = x->left; AVLNode* T2 = y->right; // 执行旋转 y->right = x; x->left = T2; // 更新高度 updateHeight(x); updateHeight(y); return y; // 新的根节点 } ``` #### 插入操作 插入过程中可能会破坏平衡,因此需要进行相应的旋转调整。 ```cpp AVLNode* insert(AVLNode* root, int key) { if (root == nullptr) { return new AVLNode(key); } if (key < root->key) { root->left = insert(root->left, key); } else if (key > root->key) { root->right = insert(root->right, key); } else { return root; // 不允许重复键值 } // 更新当前节点的高度 updateHeight(root); // 计算平衡因子并判断是否失衡 int balanceFactor = getHeight(root->left) - getHeight(root->right); // 进行必要的旋转调整 if (balanceFactor > 1 && key < root->left->key) { return rotateRight(root); // 单右旋 } if (balanceFactor < -1 && key > root->right->key) { return rotateLeft(root); // 单左旋 } if (balanceFactor > 1 && key > root->left->key) { root->left = rotateLeft(root->left); // 先左旋再右旋 return rotateRight(root); } if (balanceFactor < -1 && key < root->right->key) { root->right = rotateRight(root->right); // 先右旋再左旋 return rotateLeft(root); } return root; } ``` --- ### 应用场景 AVL树适用于那些频繁查询而较少插入或删除的应用场合。由于其严格的平衡约束,使得查找效率非常高,但在大量动态数据集中的表现可能不如红黑树灵活。常见的应用领域包括但不限于: - 数据库索引设计:快速定位记录的位置。 - 编译器优化:存储符号表以便高效访问变量信息。 - 文件系统的元数据管理:加速文件检索速度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值