本文将主要讲解平衡二叉树中的 AVL 树,其中将重点讲解二叉树的重平衡方法,即左旋和右旋,以及 3+4 重构;这些方法都是后面要讲的 B 树,红黑树等 BBST 的重要基础;此外在看本文之前最好先看一下 二叉搜索树

一、结构概述

前一篇博客里面讲了,二叉树同时具有向量的静态查找列表的动态插入、删除等优点;当然这是在理想的状态下,但是当出现一些极端情况的时候,比如二叉树的右子树或者左子树全部为空,此时二叉树将退化为一个列表;所以为了避免这种情况就要使二叉树的左右子树尽量平衡,当然最好的情况是左右子树完全平衡,那么此时二叉树的查找效率就相当于二分查找;但实际上要维护二叉树的完全平衡非常困难,所以一般情况下我们都是维护二叉树的适度平衡,于是就产生了变种众多的 BBST(balance binary search tree);

其中 AVL 树 就是其中一种,即 左右子树的高度差(平衡因子)< 2; 如图所示:

图中每个节点上方的数字就是平衡因子;

主体结构如下:

public class AVLTree2<T extends Comparable<? super T>> extends BST<T> {
private AVLNode root;
private class AVLNode extends BST.Node {
private final T key;
private int height;
private AVLNode left;
private AVLNode right;
private AVLNode parent;
}
}

二、重平衡

在我们使用 AVL 树 的时候,必然会有动态的插入和删除,同时平衡也会被打破,此时我们就需要一些旋转操作使得 AVL 树能够再次平衡;

1. 单旋

左旋(zag)

情景重现,顺序插入,70,80,85,60,82,90,87,100;最后删除60

实现:

private AVLNode rotateLeft(AVLNode x) {
AVLNode y = x.right;
y.parent = x.parent; x.right = y.left;
if (x.right != null) {
x.right.parent = x;
} y.left = x;
x.parent = y; if (y.parent != null) {
if (x == x.parent.left) {
y.parent.left = y;
} else {
y.parent.right = y;
}
} else {
root = y;
} updateHeight(x);
updateHeight(y);
return y;
}

右旋(zig)

情景重现,顺序插入,65,75,80,90,70,55,60,50;最后删除90

实现:

private AVLNode rotateRight(AVLNode x) {
AVLNode y = x.left;
y.parent = x.parent;
x.left = y.right;
if (x.left != null) {
x.left.parent = x;
} y.right = x;
x.parent = y;
if (y.parent != null) {
if (x == x.parent.left) {
y.parent.left = y;
} else {
y.parent.right = y;
}
} else {
root = y;
} updateHeight(x);
updateHeight(y);
return y;
}

2. 双旋

对于以上左倾或者右倾的,自需要经过一次 zig 或者 zag 就可以恢复平衡;但是对于左倾右倾交叉的,则需要两次旋转;如图所示

对于以上情景的复现:

  • 左图:顺序插入,70,80,90,60,100,85,82,87;最后删除60
  • 中图:同上面左旋

3. (3+4)重构

当 AVL 树不在平衡的时候,任然还是一个 BST;也就是说不平衡的时候仍然有序,所以我们可以根据他的有序性直接构造出最终结果,而不是通过一次或者两次旋转;如图所示;

对于以上的各种重平衡方法,其 不变的都是树的顺序性,抓住这一点即使记不住旋转时候,节点之间关系的切换,画一个草图也能很快知道,应该如何调整;接下来我们就将使用上述的重平衡方法,继续研究 AVL 树使用过程中插入和删除的重平衡;

实现:

private AVLNode connect34(AVLNode a, AVLNode b, AVLNode c, AVLNode t0, AVLNode t1, AVLNode t2, AVLNode t3) {
a.left = t0;
if (t0 != null) t0.parent = a;
a.right = t1;
if (t1 != null) t1.parent = a;
updateHeight(a); c.left = t2;
if (t2 != null) t2.parent = c;
c.right = t3;
if (t3 != null) t3.parent = c;
updateHeight(c); b.left = a;
a.parent = b;
b.right = c;
c.parent = b;
updateHeight(b); return b;
}

三、插入

@Override
public AVLNode insert(T key) {
AVLNode node = (AVLNode) super.insert(key);
updateHeight(node); // 插入节点,只需要调整一次,但是有可能是父亲节点或者祖父节点;
for (AVLNode x = node; x != null; x = x.parent) {
if (!isBalanced(x)) {
balance(x);
break;
}
}
return node;
} private AVLNode balance(AVLNode x) {
if (balanceFactor(x) < -1) {
if (balanceFactor(x.right) > 0) {
x.right = rotateRight(x.right);
}
x = rotateLeft(x);
} else if (balanceFactor(x) > 1) {
if (balanceFactor(x.left) < 0) {
x.left = rotateLeft(x.left);
}
x = rotateRight(x);
}
return x;
} private int balanceFactor(AVLNode x) {
return height(x.left) - height(x.right);
} private boolean isBalanced(AVLNode x) {
int i = balanceFactor(x);
return i > -2 && i < 2;
}

具体插入算法就是找到为空的叶子节点,然后插入,详情可见 二叉搜索树

四、删除

@Override
public AVLNode delete(T key) {
AVLNode node = (AVLNode) super.delete(key); // 删除原本就矮的一边,使得重平衡后高度减一,不平衡向上传播
// 使得祖父也不平衡,所以需要一次检查所有父亲节点
for (AVLNode x = node; x != null; x = x.parent) {
if (!isBalanced(x)) {
balance(x);
}
updateHeight(x);
}
return node;
}

同样具体的删除算法也请参考 二叉搜索树 ;其思路就是:

  • 先查找,如果该节点的其中一个或两个节点为空,直接令其后代代替;
  • 如果不为空,则找到直接后继,然后交换位置,在删除;

五、重构

使用 3+4重构 的方式,重平衡;

private AVLNode balance34(AVLNode g) {
AVLNode p = tallerChild(g);
AVLNode v = tallerChild(p); if (balanceFactor(g) < -1) {
if (balanceFactor(g.right) > 0) {
/*
* O
* \
* O
* /
* O
*/
return connect34(v, g, p, v.left, v.right, g.left, p.right);
} else {
/*
* O
* \
* O
* \
* O
*/
return connect34(g, p, v, g.left, p.left, v.left, v.right);
}
} else if (balanceFactor(g) > 1) {
if (balanceFactor(g.left) < 0) {
/*
* O
* /
* O
* \
* O
*/
return connect34(p, v, g, p.left, v.left, v.right, g.right);
} else {
/*
* O
* /
* O
* /
* O
*/
return connect34(v, p, g, v.left, v.right, p.right, g.right);
}
}
return g;
}

总结

  • AVL 树同二叉搜索树相比无论查找,插入还是删除,其时间复杂度都只有 LOG(n) ;
  • 但是从上述的讲解也可以看到插入和删除都需要旋转操作,成本仍然比较高;

数据结构系列(2)之 AVL 树的更多相关文章

  1. 自己动手实现java数据结构(七) AVL树

    1.AVL树介绍 前面我们已经介绍了二叉搜索树.普通的二叉搜索树在插入.删除数据时可能使得全树的数据分布不平衡,退化,导致二叉搜索树最关键的查询效率急剧降低.这也引出了平衡二叉搜索树的概念,平衡二叉搜 ...

  2. Java数据结构和算法(七)--AVL树

    在上篇博客中,学习了二分搜索树:Java数据结构和算法(六)--二叉树,但是二分搜索树本身存在一个问题: 如果现在插入的数据为1,2,3,4,5,6,这样有序的数据,或者是逆序 这种情况下的二分搜索树 ...

  3. 算法与数据结构(十一) 平衡二叉树(AVL树)

    今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...

  4. 数据结构之平衡二叉树(AVL树)

    平衡二叉树(AVL树)定义如下:平衡二叉树或者是一棵空树,或者是具有以下性质的二叉排序树: (1)它的左子树和右子树的高度之差绝对值不超过1: (2)它的左子树和右子树都是平衡二叉树. AVL树避免了 ...

  5. 算法与数据结构(十一) 平衡二叉树(AVL树)(Swift版)

    今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...

  6. 【数据结构】什么是AVL树

    目录 什么是AVL树 1. 什么是AVL树 2. 节点的实现 3. AVL树的调整 3.1 LL旋转 3.2 RR旋转 3.3 RL旋转 3.4 LR旋转 什么是AVL树 二叉查找树的一个局限性就是有 ...

  7. 数据结构与算法:AVL树

    AVL树 在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树.增加和删除可能需要通过一次或多次树旋转来重新平衡这个树.AV ...

  8. AVL树(三)之 Java的实现

    概要 前面分别介绍了AVL树"C语言版本"和"C++版本",本章介绍AVL树的Java实现版本,它的算法与C语言和C++版本一样.内容包括:1. AVL树的介绍 ...

  9. AVL树(一)之 图文解析 和 C语言的实现

    概要 本章介绍AVL树.和前面介绍"二叉查找树"的流程一样,本章先对AVL树的理论知识进行简单介绍,然后给出C语言的实现.本篇实现的二叉查找树是C语言版的,后面章节再分别给出C++ ...

  10. AVL树(二)之 C++的实现

    概要 上一章通过C语言实现了AVL树,本章将介绍AVL树的C++版本,算法与C语言版本的一样. 目录 1. AVL树的介绍2. AVL树的C++实现3. AVL树的C++测试程序 转载请注明出处:ht ...

随机推荐

  1. BZOJ_4590_[Shoi2015]自动刷题机_二分答案

    BZOJ_4590_[Shoi2015]自动刷题机_二分答案 Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机--一种可以自动AC题目的神秘装置.自动 刷题 ...

  2. BZOJ_4819_[Sdoi2017]新生舞会_01分数规划+费用流

    BZOJ_4819_[Sdoi2017]新生舞会_01分数规划+费用流 Description 学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴.有n个男生和n个女生参加舞 ...

  3. QTTabBar

    出处:https://www.mokeyjay.com/archives/1811

  4. MyBatis新手教程(一)

    MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache 迁移到了 google,并改名为MyBatis,2013年迁移到Github. MyBatis是一个优秀的持 ...

  5. CentOS7搭建本地YUM仓库,并定期同步阿里云源

    CentOS7同步阿里云镜像rpm包并自建本地yum仓库 系统环境 # cat /etc/centos-release CentOS Linux release 7.6.1810 (Core) # u ...

  6. Python爬虫入门教程 60-100 python识别验证码,阿里、腾讯、百度、聚合数据等大公司都这么干

    常见验证码 之前的博客中已经解决了一些常见验证码的问题,但是验证码是层出不穷的,目前解决验证码除了通过常规手段解决以外,还可以通过人工智能领域的深度学习去解决 深度学习?! 无疑对爬虫coder提高了 ...

  7. 《HelloGitHub》第 35 期

    <HelloGitHub>第 35 期 兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程. ...

  8. DSAPI DS密法

    DS密法是DYLIKE本人研发的一种针对文本字符串的高强度加密方法,本加密方法的优点是同源不同密,同一个源文本每次加密的结果都不同,长度也不同.密钥最大可达String类型的字符最大长度.缺点是解密时 ...

  9. 什么是CSS

    CSS是Cascading Style Sheet的缩写.译作”层叠样式表单“.是用于(增强)控制网页样式并允许将样式信息与网页内容分离的一种标记性语言.使用CSS样式可以控制许多仅使用HTML无法控 ...

  10. SQL Server作业报错特殊案例

    一个作业报错,报错信息如下,从错误信息根本看不出为什么出错,手工运行作业又成功了.一时不清楚什么原因导致作业出错. Message Executed as user: NT SERVICE\SQLSE ...