数据结构与算法-基础(十一)AVL 树
AVL 树 是最早时期发明的自平衡二叉搜索树之一。是依据它的两位发明者的名称命名。
AVL 树有一个重要的属性,即平衡因子(Balance Factor),平衡因子 == 某个节点的左右子树高度差。
AVL 树特点总结下来有:
- 每个节点的平衡因子有且仅有 1、0、-1,若超过这三个值的范围,就称其为失衡;
- 每个节点左右子树的高度差不会超过 1;
- 搜索、添加、删除的时间复杂度为 O(logn),n 为 n 个节点。

看上图,右侧图中二叉树就可以称为AVL 树。
添加后导致失衡
若再添加一个元素 18,导致它的祖先节点失衡(甚至有可能它的所有祖父节点都失衡)。但是的父节点和非祖先节点不会失衡。

当添加后导致失衡,就要想办法调整节点,使其再达到平衡状态。前提是,必须要满足二叉搜索树的性质,不能随意调整。这里调整方法就是旋转节点。
旋转节点
旋转节点核心调整祖父节点和父节点的位置,使得其调整后的节点达到平衡,在操作的过程中类似旋转,所以称为旋转节点。
先确定几个节点的简称,方便下面的说明:
- g:祖父节点
- p:父节点
- n:自身节点
LL - 右旋转(单旋)
LL: 平衡因子小于 -1 的节点。该失衡的节点为 g,它的左子树为 p,p 的左子树为 n。因为 g、p 、n 都在左侧,所以称为 LL,如下图,当添加元素 1 之后,造成失衡。

当出现 LL 类型的失衡时,可以通过右旋转达到平衡,如下图:

从图中可以看出,相当于将 g、p、n 三个节点向右侧旋转,达到平衡,即:
g.left = p.right
p.right = g
若节点发生变化,需要注意节点 parent 节点的变化情况,并在更改节点之后,更新 g 和 p 的高度。
RR - 左旋转(单旋)
RR 的情况和 LL 情况正好相反,所以旋转也是相反的,旋转之后的处理和 LL 是一样的,这个可以在后面的代码中体现处理,所以不再重复去解释这种情况。
LR - RR 左旋转,LL 右旋转(双旋)
LR : 比如添加元素 4 后失衡,的失衡节点定为 g,它的左子树为 p,p 的右子树为 n。因为 p 在左,n 在右。所以这种情况被称为 LR。

出现 LR 情况需要先对 p 节点左旋转处理,使其变成 LL 的情况之后,再对 g 做右旋转,恢复平衡状态。

注意:图中标识错误,是 p 向左旋转,g 向右旋转
每当旋转后都要更新其对应节点的高度。
RL - LL 右旋转,RR 左旋转(双旋)
RL 的情况与 LR 的情况也是正好相反的。处理上也是需要反着来就好。旋转后也是一定要更新其对应的节点。
实现代码
这里以 RR 为例,RR 的情况就需要进行左旋转。
void rotateLeft(Node<E> grand) {
// 创建临时变量 parent 和 child
Node<E> parent = grand.right;
Node<E> child = parent.left;
// 祖父节点的右子节点为父节点的左子节点
grand.right = child;
// 父节点的左节点为祖父节点
parent.left = grand;
/**
旋转之后,更改节点的指向,方法代码看下面
*/
}
做完旋转之后,要更改 g 和 child 节点的 parent 指向。
void afterRotate(Node<E> grand, Node<E> parent, Node<E> child) {
// 调整 grand 的 parent 节点
if (grand.isLeftChild()) {
grand.parent.left = parent;
} else if (grand,isRightChild()) {
grand.parent.right = parent;
} else {
root = parent;
}
if (child != null) {
child.parent = grand;
}
parent.parent = grand.parent;
grand.parent = parent;
/**
调整之后,更新 g 和 p 的高度,方法代码看下面
*/
}
这里是在每个 AVL 节点中定义一个 height 属性,来记录当前节点的高度,所以可以在 AVL 节点中实现更新高度的方法。
public void updateHeight() {
int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
height = 1 + Math.max(leftHeight, rightHeight);
}
当前节点的高度就是它的左右子节点的高度中的最大值。代码逻辑如上
这时候,就有可能有个问题,怎么保证 AVL 节点中的 height 都是一一对应的呢?这里是在添加方法之后做的处理,核心逻辑就是在每次添加一个节点之后就更新下全部的节点高度就可以,详细的在下面去深究。
有了上面左旋转的实现,那么右旋转的实现也就能顺利得到,就是和左旋转相反就好。
private void rotateRight(Node<E> grand) {
Node<E> parent = grand.left;
Node<E> child = parent.right;
grand.left = child;
parent.right = grand;
afterRotate(grand, parent, child);
}
知道了左右旋转和旋转后更新节点的方法之后,接下来就要来判断失衡的节点,以及失衡的情况是 LL 还是 RR,或者其他。
首先判断失衡的节点,判断一个节点是否平衡,就要看这个节点的平衡因子是否是在 -1 到 1 之间的,即
boolean isBalanced(Node<E> node) {
return Math.abs(((AVLNode<E>)node).balanceFactor()) <= 1;
}
public int balanceFactor() {
int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
return leftHeight - rightHeight;
}
添加节点
当添加一个节点时,因为当前节点是没有左右子节点的,所以要判断的不是当前节点,而是这个节点的父节点祖父节点们是否失衡,这就用到循环遍历
void afterAdd(Node<E> node) {
while ((node = node.parent) != null) {
if (isBalanced(node)) {
// 更新高度
updateHeight(node);
}
else {
// 恢复平衡
rebalance(node);
break;
}
}
}
看上面代码中,有一个恢复平衡的方法没有说过,在调用这个方法时,就已经确定这个节点是失衡状态,这时要做的就是确定失衡的情况,然后根据不同的情况做相应的旋转处理。
这里要先知道节点的最大高度,就是左右子节点高度中的最大值,即
public Node<E> tallerChild() {
int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
if (leftHeight > rightHeight) return left;
if (leftHeight < rightHeight) return right;
return isLeaf() ? left : right;
}
高度大的节点对应到要处理的节点,可看下面代码:
void rebalance(Node<E> grand) {
Node<E> parent = ((AVLNode<E>)grand).tallerChild();
Node<E> node = ((AVLNode<E>)parent).tallerChild();
if (parent.isLeftChild()) { // L
if (node.isLeftChild()) { // LL
rotateRight(grand);
}
else { // LR
rotateLeft(parent);
rotateRight(grand);
}
}
else { // R
if (node.isLeftChild()) { // RL
rotateRight(parent);
rotateLeft(grand);
}
else { // RR
rotateLeft(grand);
}
}
}
删除节点
当删除节点后,也可能导致 AVL 树失衡,有可能是父节点或者祖先节点失衡,通过添加节点的处理失衡逻辑后,可以总结删除节点的失衡处理逻辑,也是先要遍历查找失衡的节点,然后把这个节点的失衡情况搞出来,最后对应情况做旋转处理。
所以删除后的处理就是:
void afterRemove(Node<E> node) {
while ((node = node.parent) != null) {
if (isBalanced(node)) {
// 更新高度
updateHeight(node);
}
else {
// 恢复平衡
rebalance(node);
}
}
}
总结
看代码,添加或者删除节点之后都要一一的遍历节点的父节点和祖先节点,没有失衡的更新高度,失衡的就恢复平衡,知道 root 节点,为什么这样?
- 添加节点后,可能会导致所有的祖先节点都失衡
- 删除节点后,可能会导致父节点或祖先接单失衡,恢复平衡后,可能会导致更高层次的祖先节点失衡
所以就需要整体的遍历到 root 节点。单处理一个节点是不够的。
时间复杂度上,搜索是 O(logn),添加是 O(logn),只需要 O(1) 次的旋转,删除也是 O(logn),最多需要 O(logn) 次的旋转。
数据结构与算法-基础(十一)AVL 树的更多相关文章
- 数据结构与算法(九):AVL树详细讲解
数据结构与算法(一):基础简介 数据结构与算法(二):基于数组的实现ArrayList源码彻底分析 数据结构与算法(三):基于链表的实现LinkedList源码彻底分析 数据结构与算法(四):基于哈希 ...
- 数据结构与算法——平衡二叉树(AVL树)
目录 二叉排序树存在的问题 基本介绍 单旋转(左旋转) 树高度计算 旋转 右旋转 双旋转 完整代码 二叉排序树存在的问题 一个数列 {1,2,3,4,5,6},创建一颗二叉排序树(BST) 创建完成的 ...
- 数据结构图文解析之:AVL树详解及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- 数据结构与算法--从平衡二叉树(AVL)到红黑树
数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树.算法的性能取决于树的形状,而树的形状取决于插入键的顺序.在最好的情况下,n个结点的树是完全平衡的,如下图"最好情况&q ...
- Java数据结构和算法(一)树
Java数据结构和算法(一)树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 前面讲到的链表.栈和队列都是一对一的线性结构, ...
- 数据结构与算法系列研究五——树、二叉树、三叉树、平衡排序二叉树AVL
树.二叉树.三叉树.平衡排序二叉树AVL 一.树的定义 树是计算机算法最重要的非线性结构.树中每个数据元素至多有一个直接前驱,但可以有多个直接后继.树是一种以分支关系定义的层次结构. a.树是n ...
- Java数据结构和算法(二)树的基本操作
Java数据结构和算法(二)树的基本操作 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 一.树的遍历 二叉树遍历分为:前序遍 ...
- 数据结构54:平衡二叉树(AVL树)
上一节介绍如何使用二叉排序树实现动态查找表,本节介绍另外一种实现方式——平衡二叉树. 平衡二叉树,又称为 AVL 树.实际上就是遵循以下两个特点的二叉树: 每棵子树中的左子树和右子树的深度差不能超过 ...
- java数据结构和算法07(2-3-4树)
上一篇我们大概了解了红黑树到底是个什么鬼,这篇我们可以看看另外一种树-----2-3-4树,看这个树的名字就觉得很奇怪.... 我们首先要知道这里的2.3.4指的是任意一个节点拥有的子节点个数,所以我 ...
随机推荐
- hibernate关联关系(一对多)
什么是关联(association)关联指的是类之间的引用关系.如果类A与类B关联,那么被引用的类B将被定义为类A的属性.例如: class B{ private String name; } pub ...
- 被面试官问懵:TCP 四次挥手收到乱序的 FIN 包会如何处理?
摘要:收到个读者的问题,他在面试的时候,被搞懵了,因为面试官问了他这么一个网络问题. 本文分享自华为云社区<TCP 四次挥手收到乱序的 FIN 包会如何处理?>,作者:小林coding . ...
- 20210804 noip30
考场 第一眼感觉 T1 是状压 DP,弃了.T2 好像也是 DP???看上去 T3 比较可做. 倒序开题.T3 暴力是 \(O(pn\log p)\)(枚举 \(x\),二分答案,看能否分成合法的不超 ...
- 第06课:GDB 常用命令详解(中)
本课的核心内容: info 和 thread 命令 next.step.util.finish.return 和 jump 命令 info 和 thread 命令 在前面使用 info break 命 ...
- 剑指offer计划16( 排序简单)---java
1.1.题目1 剑指 Offer 45. 把数组排成最小的数 1.2.解法 这题看的题解,发现自己思路错了. 这里直接拿大佬的题解来讲吧. 一开始这里就把创一个string的数组来存int数组 Str ...
- (3)java Spring Cloud+Spring boot+mybatis企业快速开发架构之SpringCloud-Spring Cloud和Dubbo的区别及各自的优缺点
我们先从 Nginx 说起,了解为什么需要微服务.最初的服务化解决方案是给相同服务提供一个统一的域名,然后服务调用者向这个域发送 HTTP 请求,由 Nginx 负责请求的分发和跳转. 这种架构存 ...
- vue-router路由钩子
路由跳转前后,需要做某些操作,这时就可以使用路由钩子来监听路由的变化. 接收三个参数: to: Route: 即将要进入的目标路由对象 from: Route: 当前导航正要离开的路由 next: F ...
- TP5路由的位置导致错误
// 测试 '[js]' => [ ':id' => ['test/test/js', ['method' => 'get'], ['id' => '\d+']], ':id/ ...
- npm WARN ajv-keywords@2.1.1 requires a peer of ajv@^5.0.0 but none is installed. You must install peer dependencies yourself.
解决: npm install -g npm-install-peers npm install -g npm npm i ajv 但是好像没啥用
- 关于连接服务器redis的教程
第一步:下载RedisDesktopManager 这个百度一搜就有了,但是现在的版本ssh用不了 建议找可以用的版本,这个百度,懂得都懂. 第二步:服务器宝塔redis设置 在配置文件将bind 1 ...