045.平衡二叉树的原理和实现

本文介绍了二叉排序树的缺陷,特别是在查询效率上的不足,并引出了平衡二叉树的概念,如AVL树。详细阐述了AVL树的平衡性质,以及左旋、右旋和双旋的调整过程,通过具体案例演示了如何在不平衡时进行旋转操作。此外,还提供了AVL树的Java代码实现,包括节点类、二叉树类和测试类,展示了如何在插入节点后保持AVL树的平衡。

博主的 Github 地址


1. 二叉树排序树的缺陷

1.1. 存在的问题

  • 给定数列 arr = {1,2,3,4,5,6} 创建二叉排序树
  • 创建出来的二叉排序树如下图所示
    BST
1.1.1. 问题分析
  • 左子树全部为空, 从形式上看更像是单链表
  • 这种树对插入新结点的速度并不受影响
  • 查询速度却会明显下降, 因为要依次比较, 不能发挥 BST 的优势
    因为每次还要比较左子树, 其查询速度比单链表还要慢

2. 平衡二叉树的原理

2.1. 基本介绍

  • 平衡二叉树也叫平衡二叉搜索树 (Self-balancing binary search tree)
    又被称为 AVL 树, 可以保证有较高的查询效率

  • 具有如下特点:

    • 首先它本身是二叉排序树, 是二叉排序树的改进
    • 它是一棵空树或它的左右两个子树的高度差绝对值好不超过 1
    • 并且它的左右两个子树都是一棵平衡二叉树
  • 平衡二叉树常用的实现方法有: 红黑树, AVL, 替罪羊树, Treap, 伸展树等.


3. 左旋转 AVL 树案例

  • 要求: 给定数列, 创建出对应的平衡二叉树, arr = {4,3,6,5,7,8}

3.1. 思路分析

  • 首先用数列 arr = {4,3,6,5,7,8} 进行构造二叉排序树, 如下图进行排列.
    pic1

  • 然后问题就出现于此, 此时正常构建的二叉排序树的右子树的高度为 3,
    左子树的高度则为 1, 并不满足平衡二叉树的定义, 因此要进行左旋转.

3.1.1. 左旋转进行的步骤如下:
  • 创建一个新结点, 权值等于当前二叉树的根结点, 即权值等于 4.
    s1

  • 把新结点的左子结点设置为原根结点的左子结点
    s2

  • 把新结点的右子树设置为原根结点的右子结点的左子结点
    s3

  • 把原根结点的权值替换成原根结点的右子结点的权值
    s4-1

    位置进行调整和优化

    s4-2

  • 把原根结点的右子树设置成其右子结点的右子结点
    s5

  • 把原根结点的左子树设置为新结点
    s6

  • 调整后得到新的平衡二叉树
    s7


3.1.2. 注意事项:
  • 在分析过程中左右子树的高度是关键判断是否旋转的标准,
    当发现左右子树的高度差绝对值大于 1 的时候就进行旋转.

  • 即当新结点进行插入的时候, 若发现二叉树不再是 AVL 树的时候,
    就根据条件情况进行判断进行左旋转或右旋转.

  • 因此在旋转前, 需要统计出二叉树的高度及其左右子树各自的高度


4. 右旋转 AVL 树案例

  • 要求给定数列, 创建出对应的平衡二叉树, arr = {10,12,8,9,7,6}

4.1. 思路分析

  • 首先用数列创建出对应的排序二叉树, 如下图所示.
    s1
4.1.1. 右旋转步骤如下
  • 创建一个新的结点, 权值等于当前二叉树根结点的权值
  • 把新的结点的右子结点设置为原根结点的右子结点
  • 把新的结点的左子结点设置为原根结点的左子结点的右子结点
  • 把原根结点的权值换成原根结点的左子结点的权值
  • 把原根结点的左子结点设置为其左子结点的左子结点
  • 把原根结点的右子结点设置为新结点
  • 最后得出 AVL 树
    s2

5. 双旋转 AVL 树

  • 在某些情况下单旋转并不能完成平衡二叉树的转换
  • 比如数列 arr = {10,11,7,6,8,9} 以及数列 arr = {2,1,6,5,7,3}
  • 如下图分析所示:
    • 单旋转过后 arr = {10,11,7,6,8,9} 并没有成功转换成平衡二叉树
      p1

5.1. 双旋转情况分析

5.1.1. 情况一

sample1

  • 当符合右旋转的条件时
  • 若二叉树的左子树的右子树高度大于其左子树的左子树高度
  • 则先对这棵二叉树的左子树进行左旋转
  • 再对这棵二叉树本身进行右旋转即可
5.1.2. 情况二

sample2

  • 当符合左旋转的条件时
  • 若二叉树的右子树的左子树高度大于其右子树的右子树高度
  • 则先对这棵二叉树的右子树进行右旋转
  • 再对这棵二叉树本身进行左旋转即可

6. AVL 树代码实现

  • 大部分代码与二叉排序树重合, 但结点类有很大程度更改和新增

6.1. 结点类

package com.leo9.dc29.avl_tree;

public class TreeNode {
   
   
    public int value;
    public TreeNode left_node;
    public TreeNode right_node;

    public TreeNode(int value) {
   
   
        this.value = value;
    }

    //region 添加结点的方法
    //递归形式添加结点, 注意需要满足二叉排序树的要求
    public void addNode(TreeNode new_node) {
   
   
        //若传入空结点则直接返回即可
        if (new_node == null) {
   
   
            return;
        }

        //region 判断传入的结点的值, 和当前调用方法的结点的值关系
        //如果新结点的值小于等于当前调用结点的值
        if (new_node.value <= this.value) {
   
   
            //如果当前调用结点的左子结点为空, 则直接将新结点接在其左子结点即可
            if (this.left_node == null) {
   
   
                this.left_node = new_node;
            }
            //如果不为空, 递归向左添加
            else {
   
   
                this.left_node.addNode(new_node);
            }
        }
        //如果新结点的值大于当前调用结点的值
        else {
   
   
            //如果当前调用结点的右子结点为空, 则直接将新结点接在其右子结点即可
            if (this.right_node == null) {
   
   
                this.right_node = new_node;
            }
            //如果不为空, 递归向右添加
            else {
   
   
                this.right_node.addNode(new_node);
            }
        }
        //endregion

        /**----------新增部分-----------*/
        //region 判断是否需要左旋转
        //如果 (右子树高度-左子树高度) > 1, 进行左旋转
        if (getRightHeight() -<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值