Java数据结构与算法分析(九)AVL树(平衡二叉树)

简介: AVL(Adelson-Velskii 和 Landis)树是带有平衡条件的二叉查找树,又叫做平衡二叉树。在AVL树中任何节点的两个子树高度差最多为1,所以它又被称为高度平衡树。

在这里插入图片描述

GitHub源码分享

主页地址:https://gozhuyinglong.github.io
源码分享:https://github.com/gozhuyinglong/blog-demos

1. AVL树

AVL(Adelson-Velskii 和 Landis)树是带有平衡条件的二叉查找树,又叫做平衡二叉树。在AVL树中任何节点的两个子树高度差最多为1,所以它又被称为高度平衡树。

如下图中可以清晰的看出,左边的树其根节点左子树高度为3,右子树高度为2,符合AVL树的特点;而右边的树其根节点左子树高度为3,右子树高度为1,不符合AVL树的特点。因此左边的树为AVL树,右边的树不是AVL树。

AVL树与非AVL树

那么怎样才能保持这种平衡呢?

答案便是在插入或删除节点时,通过对树进行简单的修正来保持平衡,我们称之为旋转

2. 旋转(rotation)

旋转分为单旋转(single rotation)和双旋转(double rotation)。

  • 当左右子树的高度差超过1,并且最高的叶子节点在“外边”时,使用单旋转。
  • 当左右子树的高度差超过1,并且最高的叶子节点在“里面”时,使用双旋转。

而单旋转又分为:

  • 左旋转,即向左旋转。当右子树的高度大于左子树时,进行左旋转。
  • 右旋转,即向右旋转。当左子树的高度大于右子树时,进行右旋转。

双旋转又分为:

  • 左-右双旋转,即先向左旋转(左子节点),再向右旋转。当左子树的高度大小右子树,并且左子树最高的叶子节点为其父节点的右子节点,那么需要左-右双旋转。
  • 右-左双旋转,即先向右旋转(右子节点),再向左旋转。当右子树的高度大小左子树,并且右子树最高的叶子节点为其父节点的左子节点,那么需要右-左双旋转。

单看这些名词概念是不容易理解的,下面我们通过图例来逐个介绍。

3. 左旋转

看下图中左边的树,该树是一棵二叉查找树,但是否满足AVL的特性呢?可以发现其根节点的左子树的高度为1,而右子树的高度为3,所以其不一棵AVL树。

经过观察,其右子树高于左子树,并且最高的叶子节点也在右边,那么我们使用左旋转进行平衡。

左旋转

详细旋转过程:

  • 将根节点4复制出一个新的节点,其左子节点为3保持不变,将其右子节点指向5(即原根节点的右子节点的左子节点)。
  • 将原根节点的右子节点6的左子节点指向新节点4,其右子节点为7保持不变,那么6便成了新的根节点。

哈哈,是不是有点绕,其实也可以简单理解为:既然右子树比左子树高,那么将树根4向左下移,将树根的右子节点6向上移,成为新的树根,这样便使左右子树的高度平衡了。结合上图,反复练习几次吧。

4. 右旋转

右旋转与左旋转正好是对称的,看下图中左边的树,该二叉查找树的左子树高度为3,而右子树高度为1,不满足AVL树的旋转。

因其左子树高于右子树,并且最高的叶子节点在左边,所以我们使用右旋转。

右旋转

详细旋转过程:

  • 将根节点7复制出一个新的节点,其右子节点为9保持不变,左子节点指向5(即原根节点的左子节点的右子节点)。
  • 将原根节点的左节点升级为新的根节点,即其左子树为3保持不变,右子节点指向新的根节点7。

左旋转与右旋转一定要理解,不然下面的双旋转就更容易晕菜了!

5. 双旋转

在介绍双旋转之前,先来看下图,其根节点的左子树高度为3,右子树高度为9,那么我们先使用右旋转的方式,看能不能达平衡的效果。

右旋转后未能达到效果

通过观察右旋转后的效果,是不满足AVL树的特性的。这便需要使用双旋转了。

我们使用左-右旋转来平衡上图中的树,即先进行左旋转,再进行右旋转,但其平衡点不同,如下图所示。

左-右双旋转

详细旋转过程:

  • 先将根节点的左子树(5节点)进行左旋转,降低其(5节点)右子树的高度。
  • 再将根节点进行右旋转,便达到了平衡效果。

那么反过来,右-左双旋转的详细过程:

  • 先将根节点的右子树进行右旋转,降低其右子树的高度。
  • 再将根节点进行左旋转。

6. 代码实现

AVL树的实现是在二叉查找树的基础上添加了平衡操作。

6.1 求节点高度

在Node类中添加节点高度方法heightleftHeightrightHeight,若节点为空则高度为0。

// 当前节点高度
public int height() {
   
   
    return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
}

// 左子节点高度
public int leftHeight() {
   
   
    if (left == null) {
   
   
        return 0;
    }
    return left.height();
}

// 右子节点高度
public int rightHeight() {
   
   
    if (right == null) {
   
   
        return 0;
    }
    return right.height();
}

6.2 左旋转

在Node类中增加左旋转方法leftRotate

public void leftRotate() {
   
   
    // 将当前节点向左下移,成为新的左节点
    Node newLeftNode = new Node(element);
    newLeftNode.left = left;
    // 将右子节点设为原根节点右子树的左子树
    newLeftNode.right = right.left;

    // 将右节点上移,成为新的树根(当前节点)
    element = right.element;
    // 将左子节点设为新的左子节点(原树根)
    left = newLeftNode;
    right = right.right;
}

6.3 右旋转

在Node类中增加右旋转方法rightRotate

public void rightRotate() {
   
   
    // 将当前节点向右下移,成为新的右子节点
    Node newRightNode = new Node(element);
    // 将左子节点指向原根节点的左子树的右子树
    newRightNode.left = left.right;
    newRightNode.right = right;

    // 将左子节点上移,成为新的树根(当前节点)
    element = left.element;
    left = left.left;
    // 将右子节点设为新的右子节点(原树根)
    right = newRightNode;
}

6.4 平衡方法

在AVLTree类中添加平衡方法balance,该方法用于判断是需要单旋转还是双旋转。

public void balance(Node node) {
   
   

    if (node == null) {
   
   
        return;
    }

    if (node.leftHeight() - node.rightHeight() > 1) {
   
   
        if (node.left.rightHeight() > node.left.leftHeight()) {
   
   
            node.left.leftRotate();
        }
        node.rightRotate();

    } else if (node.rightHeight() - node.leftHeight() > 1) {
   
   
        if (node.right.leftHeight() > node.right.rightHeight()) {
   
   
            node.right.rightHeight();
        }
        node.leftRotate();
    }
}

6.5 添加节点

在AVLTree类中增加添加节点方法,当添加完一个节点后,进行调用balance方法,来维持平衡。

private void add(Node node, int element) {
   
   
    if (node.compareTo(element) < 0) {
   
   
        if (node.left == null) {
   
   
            node.left = new Node(element);
        } else {
   
   
            add(node.left, element);
        }
    } else if (node.compareTo(element) > 0) {
   
   
        if (node.right == null) {
   
   
            node.right = new Node(element);
        } else {
   
   
            add(node.right, element);
        }
    }
    balance(node);
}

6.6 删除节点

在AVLTree类中增加删除节点方法,当删除完一个节点后,也进行调用balance方法,来维护平衡。

private void remove(Node parentNode, Node node, int element) {
   
   
    if (node == null) {
   
   
        return;
    }
    // 先找到目标元素
    int compareResult = node.compareTo(element);
    if (compareResult < 0) {
   
   
        remove(node, node.left, element);
    } else if (compareResult > 0) {
   
   
        remove(node, node.right, element);
    } else {
   
   
        // 找到目标元素,判断该节点是父节点的左子树还是右子树
        boolean isLeftOfParent = false;
        if (parentNode.left != null && parentNode.left.compareTo(element) == 0) {
   
   
            isLeftOfParent = true;
        }

        // 删除目标元素
        if (node.left == null && node.right == null) {
   
    // (1)目标元素为叶子节点,直接删除
            if (isLeftOfParent) {
   
   
                parentNode.left = null;
            } else {
   
   
                parentNode.right = null;
            }
        } else if (node.left != null && node.right != null) {
   
    // (2)目标元素即有左子树,也有右子树
            // 找到右子树最小值(叶子节点),并将其删除
            Node minNode = findMin(node.right);
            remove(minNode.element);
            // 将该最小值替换要删除的目标节点
            minNode.left = node.left;
            minNode.right = node.right;
            if (isLeftOfParent) {
   
   
                parentNode.left = minNode;
            } else {
   
   
                parentNode.right = minNode;
            }

        } else {
   
    // (3)目标元素只有左子树,或只有右子树
            if (isLeftOfParent) {
   
   
                parentNode.left = node.left != null ? node.left : node.right;
            } else {
   
   
                parentNode.right = node.left != null ? node.left : node.right;
            }
        }
    }
    balance(node);
}

6.7 完整代码

由于完整代码篇幅过长,这里就不展示了,可以通过GitHub进行访问,地址如下:
https://github.com/gozhuyinglong/blog-demos/blob/main/java-data-structures/src/main/java/io/github/gozhuyinglong/datastructures/tree/AVLTreeDemo.java

7. 总结

总结一句话来表示AVL树:AVL树是一棵其平衡因子(左右子树的高度差)的绝对值小于1的二叉查找树,其可以通过单旋转或双旋转来保持平衡。

相关文章
|
1月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
59 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
1月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
48 12
|
1月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
46 10
|
1月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
49 2
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
99 5
|
3月前
|
算法
数据结构之文件系统模拟(树数据结构)
本文介绍了文件系统模拟及其核心概念,包括树状数据结构、节点结构、文件系统类和相关操作。通过构建虚拟环境,模拟文件的创建、删除、移动、搜索等操作,展示了文件系统的基本功能和性能。代码示例演示了这些操作的具体实现,包括文件和目录的创建、移动和删除。文章还讨论了该算法的优势和局限性,如灵活性高但节点移除效率低等问题。
90 0
|
1天前
|
算法 数据安全/隐私保护 计算机视觉
基于FPGA的图像双线性插值算法verilog实现,包括tb测试文件和MATLAB辅助验证
本项目展示了256×256图像通过双线性插值放大至512×512的效果,无水印展示。使用Matlab 2022a和Vivado 2019.2开发,提供完整代码及详细中文注释、操作视频。核心程序实现图像缩放,并在Matlab中验证效果。双线性插值算法通过FPGA高效实现图像缩放,确保质量。
|
1月前
|
算法 数据安全/隐私保护 计算机视觉
基于Retinex算法的图像去雾matlab仿真
本项目展示了基于Retinex算法的图像去雾技术。完整程序运行效果无水印,使用Matlab2022a开发。核心代码包含详细中文注释和操作步骤视频。Retinex理论由Edwin Land提出,旨在分离图像的光照和反射分量,增强图像对比度、颜色和细节,尤其在雾天条件下表现优异,有效解决图像去雾问题。
|
1月前
|
算法 数据可视化 安全
基于DWA优化算法的机器人路径规划matlab仿真
本项目基于DWA优化算法实现机器人路径规划的MATLAB仿真,适用于动态环境下的自主导航。使用MATLAB2022A版本运行,展示路径规划和预测结果。核心代码通过散点图和轨迹图可视化路径点及预测路径。DWA算法通过定义速度空间、采样候选动作并评估其优劣(目标方向性、障碍物距离、速度一致性),实时调整机器人运动参数,确保安全避障并接近目标。
146 68
|
1月前
|
算法 数据安全/隐私保护
室内障碍物射线追踪算法matlab模拟仿真
### 简介 本项目展示了室内障碍物射线追踪算法在无线通信中的应用。通过Matlab 2022a实现,包含完整程序运行效果(无水印),支持增加发射点和室内墙壁设置。核心代码配有详细中文注释及操作视频。该算法基于几何光学原理,模拟信号在复杂室内环境中的传播路径与强度,涵盖场景建模、射线发射、传播及接收点场强计算等步骤,为无线网络规划提供重要依据。

热门文章

最新文章