平衡二叉树(AVL)【java实现+图解】

news2024/9/19 8:28:42

目录

一、平衡二叉树(AVL)

二、平衡二叉树的四种旋转

1.右旋转

2.左旋转

3. 左右旋转

4. 右左旋转

 三、基于二叉搜索树之平衡二叉树的代码实现

1.具体方法思路

2.java代码实现 


一、平衡二叉树(AVL)

一种自平衡二叉搜索树,它是在每个节点上增加一个平衡因子,然后通过调整树中节点的高度来保持树的平衡。平衡因子是左子树的高度减去右子树的高度,用它可以表示出当前节点的平衡程度。对于任意一个结点左子树和右子树的高度差不能超过1,当一个节点的平衡因子绝对值大于1时,这个节点就被称为不平衡节点。

二、平衡二叉树的四种旋转

1.右旋转

 

    //左旋转
    public Node leftSpin(Node x){
        Node y = x.right;
        Node t2 = y.left;

        x.right = t2;
        y.left = x;
        //更新节点的高度
        x.height = Math.max(getHight(x.left),getHight(x.right))+1;
        y.height = Math.max(getHight(y.left),getHight(y.right))+1;
        return y;
    }

 

2.左旋转

 

    //右旋转
    public Node rightSpin(Node x){
        Node y = x.left;
        Node t2 = y.right;

        x.left = t2;
        y.right = x;
        //更新节点的高度
        x.height = Math.max(getHight(x.left),getHight(x.right))+1;
        y.height = Math.max(getHight(y.left),getHight(y.right))+1;
        return y;
    }

3. 左右旋转

先旋转成上面只需要右旋转的情况,再经过右旋转成平衡二叉树

4. 右左旋转

先旋转成上面只需要左旋转的情况,再经过左旋转成平衡二叉树

 三、基于二叉搜索树之平衡二叉树的代码实现

1.具体方法思路

主要方法:

添加:每次添加一个节点后更新输的高度,平衡因子可能改变,依据平衡因子对二叉树进行调整;

删除:每删除一个节点后,也需要更新输的高度,平衡因子可能改变,依据平衡因子对二叉树进行调整。

辅助方法:

判断是否是二叉搜索树:通过中序遍历,将节点的前一个值与后一个值比较;

判断是否是平衡二叉树:通过判断每个子树的平衡因子的是否在[-1,1]区间内。

2.java代码实现 

import java.util.*;
import java.util.stream.Collectors;

public class AVL<T extends Comparable<T>> {

    public class Node {
        T val;
        Node left;
        Node right;
        //以该节点为根的树的高度
        int height;

        //统计单词出现的次数
        int count;

        public Node(T val) {
            this.val = val;
            this.height = 1;//默认高度为1
            this.count = 1;
        }

        @Override
        public String toString() {
            return String.format("val:%s,heiget:%d,count:%d",this.val,this.height,this.count);
        }
    }

    private Node root;
    private Integer size;

    public AVL() {
        this.root = null;
        this.size = 0;
    }

    public boolean isEmpt() {
        return this.size == 0;
    }

    public Integer getSize() {
        return this.size;
    }


    //验证是否是二叉搜索树
    public  boolean isBinaryTree(){
        List<T> list = new ArrayList<>();
        midTraversal(list,this.root);
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i-1).compareTo(list.get(i))>0){
                return false;
            }
        }
        return true;
    }

    //获取当前节点的高度
    public int getHight(Node node){
        if (node==null){
            return 0;
        }
        return node.height;
    }
    //获取当前节点的平衡因子
    public int getBalanceFactor(Node node){
        if (node==null){
            return 0;
        }
        return getHight(node.left)-getHight(node.right);
    }
    

    public boolean isBalanceTree(){
        return isBalanceTree(this.root);
    }

    //判断以node为节点的树是否是平衡二叉树
    private boolean  isBalanceTree(Node node){
        if (node==null){
            return true;
        }
        int balanceFactor = Math.abs(getBalanceFactor(node));

        if (balanceFactor>1){
            return false;
        }else {
            return isBalanceTree(node.left)&&isBalanceTree(node.right);
        }
    }
    
    //左旋转
    public Node leftSpin(Node x){
        Node y = x.right;
        Node t2 = y.left;

        x.right = t2;
        y.left = x;
        //更新节点的高度
        x.height = Math.max(getHight(x.left),getHight(x.right))+1;
        y.height = Math.max(getHight(y.left),getHight(y.right))+1;
        return y;
    }
    //右旋转
    public Node rightSpin(Node x){
        Node y = x.left;
        Node t2 = y.right;

        x.left = t2;
        y.right = x;
        //更新节点的高度
        x.height = Math.max(getHight(x.left),getHight(x.right))+1;
        y.height = Math.max(getHight(y.left),getHight(y.right))+1;
        return y;
    }

    //添加
    public void add(T val) {
        this.root = add(this.root, val);
    }

    private Node add(Node node, T val) {
        if (node == null) {
            this.size++;
            Node leafNode = new Node(val);
            return leafNode;
        }
        //当前节点的值小于添加的val,因此val做右孩子
        if (node.val.compareTo(val) < 0) {
            node.right = add(node.right, val);
            //更新高度
            node.height = Math.max(getHight(node.left),getHight(node.right))+1;
        } else if (node.val.compareTo(val) > 0){
            node.left = add(node.left, val);
            //更新高度
            node.height = Math.max(getHight(node.left),getHight(node.right))+1;
        }else {
            node.count++;
        }

        //添加后还要判断是否是平衡二叉树
        Node resNode = node;
        int balanceFactor = getBalanceFactor(node);
        if (balanceFactor>1&&getBalanceFactor(node.left)>=0){
            //右
            resNode = rightSpin(node);
        }else if (balanceFactor>1&&getBalanceFactor(node.left)<0){
            //左右
            node.left = leftSpin(node.left);
            resNode = rightSpin(node);
        }else if (balanceFactor<-1&&getBalanceFactor(node.right)<=0){
            //左
            resNode = leftSpin(node);
        }else if (balanceFactor<-1&&getBalanceFactor(node.right)>0){
            //右左
            node.right = rightSpin(node.right);
            resNode = leftSpin(node);
        }
        return resNode;
    }


    //删除操作
    //删除树中的val
    public void remove(T val){
        if (!contain(val)){
            return;
        }
        this.root = remove(this.root,val);
    }

    /**
     * 删除val
     * @param node
     * @param val
     * @return
     */
    public Node remove(Node node, T val){
        //    递归终止条件
        if (node == null) {
            return null;
        }
        Node resNode = null;
        if (node.val.compareTo(val) == 0) {
            this.size--;
            if (node.right==null){
                //右子树为空
                resNode = node.left;
            }else if (node.left==null){
                //左子树为空
                resNode = node.right;
            }else {
                //    左右子树都不为空
                //    1.找到删除节点的后继
                 Node suffixNode = getMinDG(node.right);
                // 2.删除后继
                suffixNode.right = remove(node.right,getMinDG(node.right).val);
                // 3.连接
                suffixNode.left = node.left;
                this.size++;
                //    返回删除后的根
                resNode = suffixNode;
            }
        }else if (node.val.compareTo(val)<0){
            node.right = remove(node.right,val);
            resNode = node;
        }else {
            node.left = remove(node.left,val);
            resNode =  node;
        }

        //删除节点可能为叶子结点
        if (resNode==null){
            return null;
        }
        //更新高度
        resNode.height = Math.max(getHight(resNode.left),getHight(resNode.right))+1;
        int balanceFactor = getBalanceFactor(resNode);
        if (balanceFactor>1&&getBalanceFactor(resNode.left)>=0){
            //右
            resNode = rightSpin(resNode);
        }else if (balanceFactor>1&&getBalanceFactor(resNode.left)<0){
            //左右
            resNode.left = leftSpin(resNode.left);
            resNode = rightSpin(resNode);
        }else if (balanceFactor<-1&&getBalanceFactor(resNode.right)<=0){
            //左
            resNode = leftSpin(resNode);
        }else if (balanceFactor<-1&&getBalanceFactor(resNode.right)>0){
            //右左
            resNode.right = rightSpin(resNode.right);
            resNode = leftSpin(resNode);
        }
        return resNode;
    }

    private Node getMinDG(Node node) {
        // 递归终止条件
        if (node.left == null) {
            return node;
        }
        //   递归操作
        return getMinDG(node.left);
    }

    public String midTraversal() {
        List<T> res = new ArrayList<>();
        midTraversal(res, this.root);
        return res.stream().map(item -> item.toString()).collect(Collectors.joining(","));
    }

    /**
     * 中序遍历
     *
     * @param result
     * @param node   当前节点
     * @return
     */
    private void midTraversal(List<T> result, Node node) {
        if (node == null) {
            return;
        }
        midTraversal(result, node.left);
        result.add(node.val);
        midTraversal(result, node.right);
    }

    public void showTree(){
        showTree(this.root);
    }
    private void showTree(Node node){
        if (node == null) {
            return;
        }
        showTree(node.left);
        System.out.print(node);
        System.out.println("BalanceFactor:"+getBalanceFactor(node));
        showTree(node.right);
    }

    //查询是否存在val
    public boolean contain(T val){
        return contain(this.root,val);
    }
    private boolean contain(Node node,T val){
    //    递归的终止条件
    //    查询到低也没有找到
        if (node==null){
            return false;
        }
    //    递归操作
        if (node.val.compareTo(val)==0){
            return true;
        }else if (node.val.compareTo(val)<0){
            return contain(node.right,val);
        }else {
            return  contain(node.left,val);
        }
    }
    //测试
    public static void main(String[] args) {
        AVL<String> bst = new AVL<>();

        List<String> list = ReadBookUtil.readBook("pride-and-prejudice.txt");
        list.stream().forEach(item->{
            bst.add(item);
        });
        System.out.println("是否是二分搜索树"+bst.isBinaryTree());
        System.out.println("是否是平衡二叉树"+bst.isBalanceTree());
        bst.showTree();
  
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1126144.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day 1 Vue 页面框架

现在前端框架越来越像后端了&#xff0c;特别是TypeScript这样的语言出现后&#xff0c;开发前端的体验跟后端渐渐接近了。当然&#xff0c;作为一个后端&#xff0c;直接上手前端&#xff0c;还是有很多坑要填的。 本次开发&#xff0c;前端页面框架直接选择Vue。原因很简单&…

HTML中文本框\单选框\按钮\多选框

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <h1>登录注册</h1> <form action"第一个网页.html" method"post&quo…

字节码进阶之java反射的底层实现原理

文章目录 1. 前言1. 类加载机制2. 字节码操作 2. 反射方法源码分析反射的inflation机制 3. 为什么反射性能差4. 反射的限制与安全性考虑1. **性能开销**2. **安全限制**3. **破坏抽象**4. **版本兼容性问题** 参考文档 java 反射的底层实现原理 1. 前言 Java反射的底层实现原理…

Windows下Eclipse C/C++开发环境配置教程

1.下载安装Eclipse 官网下载eclipse-installer&#xff08;eclipse下载器&#xff09;&#xff0c;或者官方下载对应版本zip。 本文示例&#xff1a; Eclipse IDE for C/C Developers Eclipse Packages | The Eclipse Foundation - home to a global community, the Eclipse ID…

模式识别——高斯分类器

模式识别——高斯分类器 需知定义特殊情况&#xff08;方差一致&#xff09;Sigmoid 需知 所有问题定义在分类问题下&#xff0c;基于贝叶斯决策 定义 条件概率为多元高斯分布&#xff0c;此时观测为向量 X X 1 , X 2 , . . . , X n X{X_1,X_2,...,X_n} XX1​,X2​,...,Xn​…

IP地址在网络安全中的关键作用

IP地址&#xff08;Internet Protocol Address&#xff09;是互联网世界中的重要标识符&#xff0c;它在网络安全领域发挥着至关重要的作用。这些地址不仅帮助设备在网络上找到彼此&#xff0c;还在多个方面有助于维护网络的完整性、机密性和可用性。本文将探讨IP地址在网络安全…

nodejs使用es-batis

使用方法 创建连接 因为它只支持非连接池所以每次都要创建连接 let dao new MySqlDaoContext({charset: "utf8",host: "localhost",user: "root",password: "root",database: "test",});await dao.initialize();dao in…

H3C AC通过Web平台进行AC软件的升级?

软件升级的流程 1、获取软件版本 登录新华三官网&#xff08;首页>产品支持与服务>文档与软件>软件下载&#xff09;&#xff0c;将指定的软件版本下载至本地。 无线路由器-无线接入点-无线控制器-新华三集团-H3C 官网软件下载公共账号密码&#xff1a;账号&#x…

Selenium中WebDriver最新Chrome驱动安装教程

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

网络规划设计

文章目录 一、网络架构设计1.1 单核心双核心1.1.1 单核心1.1.2 双核心端口聚合 链路聚合网关冗余VRRP堆叠技术生成树STPPOE以太网供电服务器冗余&负载均衡服务器双机热备 1.2 环网架构设计1.3 层次化架构设计1.3.2 三层网络架构设计1.3.3 园区大二层网络架构1.3.4 数据中心…

【前段基础入门之】=>CSS3新增渐变颜色属性

导语&#xff1a; CSS3 新增了&#xff0c;渐变色 的解决方案&#xff0c;这使得我们可以绘制出更加生动的炫酷的的配色效果 线性渐变 多个颜色之间的渐变&#xff0c; 默认从上到下渐变 background-image: linear-gradient(red,yellow,green); /*默认从上到下渐变*/默认从上…

CVE-2022-41082:Microsoft Exchange 反序列化类型混淆 RCE 漏洞简单分析

简介 漏洞编号&#xff1a;CVE-2022-41082漏洞类型&#xff1a;类型混淆软件名称&#xff1a;Microsoft Exchange模块名称&#xff1a;Exchange 服务 powershell 接口模块历史漏洞&#xff1a;易受攻击的流行软件影响的版本&#xff1a;Microsoft Exchange Server 2019 2016 2…

webpack proxy如何解决跨域?

一、是什么 webpack proxy&#xff0c;即webpack提供的代理服务 基本行为就是接收客户端发送的请求后转发给其他服务器 其目的是为了便于开发者在开发模式下解决跨域问题&#xff08;浏览器安全策略限制&#xff09; 想要实现代理首先需要一个中间服务器&#xff0c;webpac…

.obj模型文件(带材质和纹理)合并的基本思路

1、将v开头的顶点信息依次拷贝到合并新.obj中 2、将vt纹理坐标依次拷贝到合并新.obj中 3、f&#xff08;面&#xff09;的合并 步骤&#xff1a; &#xff08;1&#xff09;第一个obj文件的f&#xff08;面&#xff09;原封不动拷进新.obj中 &#xff08;2&#xff09;第二个…

SpringMVC(三)获取请求参数

1.1通过ServletAPI获取 SpringMVC封装的就是原生的servlet 我们进行测试如下所示&#xff1a; package com.rgf.controller.service;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.…

git rebase 和 git merge的区别?

一、是什么 在使用 git 进行版本管理的项目中&#xff0c;当完成一个特性的开发并将其合并到 master 分支时&#xff0c;会有两种方式&#xff1a; git mergegit rebase git rebase 与 git merge都有相同的作用&#xff0c;都是将一个分支的提交合并到另一分支上&#xff0c…

nodejs+vue大学生社团管理系统

通过软件的需求分析已经获得了系统的基本功能需求&#xff0c;根据需求&#xff0c;将大学生社团管理系统平台功能模块主要分为管理员模块。管理员添加社团成员管理、社团信息管理&#xff0c;社长管理、用户注册管理等操作。 目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1…

【Java笔试强训】Day4(WY33 计算糖果、DD5 进制转换)

WY33 计算糖果 链接&#xff1a;WY33 计算糖果 题目&#xff1a; A,B,C三个人是好朋友,每个人手里都有一些糖果,我们不知道他们每个人手上具体有多少个糖果,但是我们知道以下的信息&#xff1a; A - B, B - C, A B, B C. 这四个数值.每个字母代表每个人所拥有的糖果数. 现…

SpringBoot 统一响应返回格式格式 数组

RequestMapping("/update")public Result login() {List<String> arr new ArrayList<>();arr.add("123");arr.add("456");return Result.success("获取成功",arr);}public class Result {public Result(int code,String …

js关于深度克隆问题

js的克隆是一个老生常谈的内容了,今天没啥好写的,就写这个了 要搞清楚js的克隆,就需要先搞清楚js中的数据类型,js中数据类型分为两大类 类型说明原始类型-string字符串类型&#xff0c;用于表示文本数据。number数字类型&#xff0c;包括整数和浮点数&#xff0c;用于表示数值…