数据结构与算法(十六):平衡二叉树

数据结构与算法(十六):平衡二叉树一、什么是平衡二叉树1.概述平衡二叉树(AVL树)是一种带有平衡条件的二叉搜索树。它的特性如下:AVL树的左右两个子树的高度差的绝对值不超过1AVL树的左右两个子树都是一棵平衡二叉树举个例子

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、什么是平衡二叉树

1.概述

平衡二叉树(AVL树)是一种带有平衡条件的二叉搜索树。它的特性如下:

  • AVL树的左右两个子树的高度差的绝对值不超过1
  • AVL树的左右两个子树都是一棵平衡二叉树

image-20200722173142958

举个例子,如上图所示:

  • 第一棵树左树高2,右树高1,差值为1,是一颗AVL树;
  • 第二棵树左树高2,右树高2,差值为0,是一颗AVL树;
  • 第三棵树左树高3,右树高1,差值为2,不是一颗AVL树;

红黑树就是一直AVL树。

2.为什么需要平衡二叉树

当我们使用二叉排序树的时候,当连续插入顺序的节点的时候就会出现问题。比如,我们插入{1,2,3,4,5}这样一个数组:

image-20200722173840094

可见该树左树节点全为空,比起树更像单链表,这也导致了该树的插入和查询速度明显的下降,查询速度甚至因为每次多处一个比较左树的操作导致还不如单链表。为了避免这种情况,我们引入的AVL树。

二、AVL树左旋转

1.思路分析

AVL为了避免左右树高度差超过1,在可能导致这种情况的插入或者删除操作时会进行旋转。

我们举个例子,现在有数列{4,3,6,5,7},当插入8后,现在的得到的排序树如下图:

image-20200723175058126

明显不再是一个AVL树,所以需要进行左旋转:

  1. 我们以当前根节点值再创建一个新节点newNode

  2. 让新节点的左子节点指向根节点的左子节点

    newNode.left = root.left

  3. 让新节点的右子节点指向根节点的右子节点的左子节点

    newNode.right = root.right.left

    image-20200723183026891

  4. 把根节点的值换成右子节点的值

    root.val = root.right.val

  5. 把根节点的右子节点指向其右子节点的右子节点

    root.right = root.right.right

  6. 让根节点的左子节点指向新节点(根节点的右子节点成为了新的根节点)

    root.left = newNode

    image-20200723183950837

我们调整一下图片样式,就可以直观的看到左旋转后树的样子:

image-20200723184527438

网上看到一个非常形象直观的动图:

数据结构与算法(十六):平衡二叉树

不难理解:左旋的目的是降低左子树的高度

2.代码实现

由于AVL树是基于BST改进的一种数据结构,所以这里的AVL树类继承了BST的方法和代码,使用同一个节点类,这里具体的代码可以参考之前的文章。

我们先创建一个继承BST的AVL树类:

/**
 * @Author:CreateSequence
 * @Date:2020-07-23 19:01
 * @Description:平衡二叉树
 * 由于是在二叉排序树的基础上改进,这里直接继承了二叉排序树类
 */
public class AVLTree extends BinarySortTree{

    public AVLTree(BinarySortTreeNode root) {
        super(root);
    }
    
}

由于旋转的条件是左右子树高度差大于1,所以我们需要有几个方法来判断树的高度:

/**
 * 获取当前节点的右子树高度
 * @param node
 * @return
 */
public int getRightHeight(BinarySortTreeNode node) {
    if (node.right == null) {
        return 0;
    }
    return getHeight(node.right);
}

/**
 * 获取当前节点的左子树高度
 * @param node
 * @return
 */
public int getLeftHeight(BinarySortTreeNode node){
    if (node.left == null) {
        return 0;
    }
    return getHeight(node.left);
}

/**
 * 获取以当前节点为根节点的树高度
 * @param node
 * @return
 */
public int getHeight(BinarySortTreeNode node) {
    //判断当前节点的左/右节点是否为空,是返回0,否则遍历返回当前节点的左右树最高值
    return Math.max(node.left == null ? 0 : getHeight(node.left), node.right == null ? 0 : getHeight(node.right)) + 1;
}

接着我们需要一个让树左旋的代码,步骤同思路分析:

/**
 * 排序树左旋转
 */
private void leftRotate() {
    // 1.创建新节点,与根节点值相同
    BinarySortTreeNode node = new BinarySortTreeNode(root.val);
    //2.让新节点左子节点指向根节点左子节点
    node.left = root.left;
    //3.让新节点的右子节点指向根节点的右子节点的左子节点
    node.right = root.right.left;
    //4.让根节点的值变为其右子节点的值
    root.val = root.right.val;
    //5.把根节点的右子节点指向其右子节点的右子节点
    root.right = root.right.right;
    //6.让根节点的左子节点指向新节点
    root.left = node;
}

然后我们再原先旧的添加方法上进行改进:

当添加完一个节点后,我们判断左右子树的高度差是否大于1,如果是就进行左旋

/**
 * 重写二叉排序树的节点添加方法,当添加完节点后左子树与右子树高度差大于1时,让树进行左旋转,若情况相反则进行右旋转
 * @param node
 */
@Override
public void add(BinarySortTreeNode node) {
    super.add(node);
    //添加完节点后,判断左子树与右子树高度差是否大于1
    int disparity = getRightHeight(root) - getLeftHeight(root);
    if (disparity > 1) {
        System.out.println("高度差:" + disparity + ",左旋转!");
        //左子树与右子树高度差大于1就左旋
        leftRotate();
    }
}

注意:截止目前,仅仅只对左子树高度较高的情况作了处理!

三、AVL树的双旋转

左旋转是为了降低左子树的高度,但是如果是右子树高度过高,我们就需要右旋,事实上,一个完整的AVL树,应当是能够双旋的。

右旋的步骤与左旋基本一致,但是方向不同:

  1. 我们以当前根节点值再创建一个新节点newNode

  2. 让新节点的右子节点指向根节点的右子节点

    newNode.right = root.right

  3. 让新节点的左子节点指向根节点的左子节点的右子节点

    newNode.left = root.left.right

  4. 把根节点的值换成左子节点的值

    root.val = root.left.val

  5. 把根节点的左子节点指向其左子节点的左子节点

    root.left = root.left.left

  6. 让根节点的右子节点指向新节点(根节点的左子节点成为了新的根节点)

    root.right = newNode

数据结构与算法(十六):平衡二叉树

实现代码:

/**
 * 排序树右旋转
 */
private void rightRotate() {
    // 1.创建新节点,与根节点值相同
    BinarySortTreeNode node = new BinarySortTreeNode(root.val);
    //2.让新节点右子节点指向根节点右子节点
    node.right = root.right;
    //3.让新节点的左子节点指向根节点的左子节点的右子节点
    node.left = root.left.right;
    //4.让根节点的值变为其左子节点的值
    root.val = root.left.val;
    //5.把根节点的左子节点指向其左子节点的左子节点
    root.left = root.left.left;
    //6.让根节点的右子节点指向新节点
    root.right = node;
}

现在为排序树的add方法添加右旋的情况:

/**
 * 重写二叉排序树的节点添加方法,当添加完节点后左子树与右子树高度差大于1时,让树进行左旋转,若情况相反则进行右旋转
 * @param node
 */
@Override
public void add(BinarySortTreeNode node) {
    super.add(node);
    //添加完节点后,判断左右树高度差是否大于1
    int disparity = getRightHeight(root) - getLeftHeight(root);
    if (disparity > 1) {
        System.out.println("高度差:" + disparity + ",左旋转!");
        //左子树与右子树高度差大于1就左旋
        leftRotate();
    }else if (- disparity > 1){
        //右子树与左子树高度差小于1就左旋
        rightRotate();
    }
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170801.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Python 正则表达式大全[通俗易懂]

    Python 正则表达式大全[通俗易懂]1前言正则表达式是对字符串(包括普通字符(例如,a到z之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。上面都是官方的说明,博主自己的理解是(仅供参考):通过事先规定好一些特殊字符的匹配规则,然后利用这些字符进行组合来匹配各种复杂的字符串场景。比如现在的爬虫和数据分析,字符串校验等等都

    2022年10月3日
  • 树莓派是什么 树莓派能做什么 树莓派的功能用途

    树莓派是什么 树莓派能做什么 树莓派的功能用途阿里云官方镜像源:https://developer.aliyun.com/mirror/?utm_content=g_1000304579树莓派是指RaspberryPi,是为学习计算机编程教育而设计,只有信用卡大小的微型电脑,其系统基于Linux。随着Windows10IoT的发布,我们也将可以用上运行Windows的树莓派。下面小编给大家介绍一下“树莓派是什么树莓派能做什么树莓派的功能用途”1.树莓派是什么树莓派是指RaspberryPi,是为学习计算机编程

    2022年6月10日
  • debian开启ipv6_docker 端口 ipv4 ipv6

    debian开启ipv6_docker 端口 ipv4 ipv6Debian6配置本地IPV6地址访问1、检查自己本地是否支持IPV6,cmd执行命令ipconfig查找有没有ipv6地址,没用的话网上找找怎么配2、vi编辑/etc/sysctl.conf最下面有则修改,无则添加#DisableIPv6net.ipv6.conf.all.disable_ipv6=0net.ipv6.conf.default.disable_ipv…

    2022年8月31日
  • 前端开发代码编辑器_前端自动生成代码

    前端开发代码编辑器_前端自动生成代码目录前言CodeSandbox介绍多种模板代码选择VSCode一致体验运行Node容器CodeSandbox示例前言有时候需要经常写一些测试代码或示例,然后将这些代码分享给他人,少量的代码通过GitHub分享有点大材小用,而且他人要从GitHub上fork代码后,在本地用IDE打开,然后安装依赖、运行,这个步骤过于繁琐。因此使用在线代码编辑器就能解决上面说到的问题,CodeSandbox介绍我用过几个在线代码编辑器,如知名的CodePen,Jsfilddle和Jsbin也有使用过,对比起来,还是C

    2022年8月14日
  • 一个简单需求:HashMap实现相同key存入数据后不被覆盖

    做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!看似是一个简单的问题,其实里面包含很多的东西!需求:实现一个在HashMap中存入(任意类型)相同的key值后,key中的value不会被覆盖,而是能够进行叠加!拿到一个需求的时候,我们要先进行分析,看此需求能否实现,基于已有的知识(经验),然后在通过目前的一些技术看此需求如何实现。要实现在HashMap中插…

    2022年2月28日
  • android 环境搭建

    android 环境搭建

    2021年9月4日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号