计算机基础--->数据结构(6)【AVL树(平衡二叉树)】

文章目录

  • AVL(平衡二叉树)树
    • 性质
    • AVL树的操作(Java)
    • 节点的创建
    • AVL树的插入
      • 1.判断平衡
      • 2.保持树的平衡
      • 3.判断是否AVL树
      • 4.删除节点
    • 全部代码

AVL(平衡二叉树)树

平衡二叉树是一种特殊的二叉搜索树,他的左子树与右子树的高度差不超过1。并且左右两个子树都是一颗平衡二叉树。保持平衡的特性使得平衡二叉树的查询、插入和删除操作在平均和最坏情况下的时间复杂度都是O(logn),其中n是树中节点的数量。 相比于普通的二叉搜索树,平衡二叉树更加适合处理动态变化的数据集。 常见的平衡二叉树有AVL树、红黑树以及B树等。这些树结构通过在插入或删除节点时进行特定的平衡操作来保持树的平衡。

性质

  • 它的左右子树都是AVL树
  • 左右子树高度差不超过1
  • 完全二叉树是AVL树,如果一棵树是AVL树,那么其高度可保持在O(log2n),搜索时间复杂度为O(log2n)

AVL树的操作(Java)

首先创建节点用来保存树中的节点的数据,其次和二叉搜索树不同的是,AVL树需要平衡因子来控制二叉树的平衡。

节点的创建

    private class Node {
        int val;
        Node left;
        Node right;
        int height;

        public Node(int val) {
            this.val = val;
            this.left = this.right = null;
            this.height = 1;
        }
    }

AVL树的插入

插入操作和二叉搜索树是相同的,但是在插入结束时会判断AVL树是否平衡。

    // 向树中添加节点
    public void add(int val) {
        if (contains(val)) {
            return;
        }
        this.root = add(this.root, val);
        this.size++;
    }

    // 向AVL树中添加节点
    private Node add(Node node, int val) {
        // 递归到底的情况
        if (node == null) {
            return new Node(val);
        }
        if (node.val > val) {
            node.left = add(node.left, val);
        } else {
            node.right = add(node.right, val);
        }
        // 更新节点高度
        node.height = Math.max(getNodeHeight(node.left), getNodeHeight(node.right)) + 1;

        // 维护平衡
        int balance = getBalance(node);
        if (balance > 1 && getBalance(node.left) >= 0) {
            // 右旋
            return rightRotate(node);
        } else if (balance > 1 && getBalance(node.left) <= 0) {
            // 左旋右旋
            node.left = leftRotate(node.left);
            return rightRotate(node);
        } else if (balance < -1 && getBalance(node.right) >= 0) {
            // 右旋左旋
            node.right = rightRotate(node.right);
            return leftRotate(node);
        } else if (balance < -1 && getBalance(node.right) <= 0) {
            // 左旋
            return leftRotate(node);
        }
        return node;
    }

1.判断平衡

一个二叉树是否是平衡的,根绝二叉树的性质可以知道,当其左右子树节点的高度差的绝对值不超过1时,这个二叉树就是平衡二叉树,当其高度差绝对值超过1时,那么就是不平衡的二叉树,就需要对二叉树进行平衡调整。

 // 获取当前节点的平衡因子(左右子树高度差)
    private int getBalance(Node node) {
        if (node == null) {
            return 0;
        }
        return getNodeHeight(node.left) - getNodeHeight(node.right);
    }

2.保持树的平衡

当判断出一棵树不平衡时,需要进行平衡调整。

二叉树一般出现不平衡的时候有四种状态:

1. 左旋
在这里插入图片描述

根据上图,我们可以直到,当二叉树的右树的高度超过平衡时需要进行左旋,左旋的方式是先将x节点的左子树单独定义出来为t2,然后将y节点连接到x的左子树,将t2连接到y的右子树上,这时就完成了左旋。

    // 左旋转
    private Node leftRotate(Node y) {
        Node x = y.right;
        Node leftX = x.left;
        x.left = y;
        y.right = leftX;
        y.height = 1 + Math.max(getNodeHeight(y.left), getNodeHeight(y.right));
        x.height = 1 + Math.max(getNodeHeight(x.left), getNodeHeight(x.right));
        return x;
    }

2. 右旋
在这里插入图片描述

右旋转和左旋转类似,右旋的方式是先将x节点的右子树单独定义出来为t2,然后将y节点连接到x的右子树,将t2连接到y的左子树上,这时就完成了右旋。

    // 右旋转
    private Node rightRotate(Node y) {
        Node x = y.left;
        Node rightX = x.right;
        x.right = y;
        y.left = rightX;
        y.height = 1 + Math.max(getNodeHeight(y.left), getNodeHeight(y.right));
        x.height = 1 + Math.max(getNodeHeight(x.left), getNodeHeight(x.right));
        return x;
    }

3. 右旋+左旋

在这里插入图片描述

所谓右旋+左旋就是将右旋和左旋进行连接,将这类不平衡的树进行平衡

4. 左旋+右旋

在这里插入图片描述

3.判断是否AVL树

判断是否AVL树就是判断二叉树的各个节点的平衡因子是否都是小于等于1或者大于等于-1。

    // 判断是否为平衡二叉树
    public boolean isBalanceTree() {
        return isBalanceTree(this.root);
    }

    private boolean isBalanceTree(Node node) {
        if (node == null) {
            return true;
        }
        if (Math.abs(getBalance(node)) > 1) {
            return false;
        }
        return isBalanceTree(node.left) && isBalanceTree(node.right);
    }

 	// 获取当前节点的平衡因子(左右子树高度差)
    private int getBalance(Node node) {
        if (node == null) {
            return 0;
        }
        return getNodeHeight(node.left) - getNodeHeight(node.right);
    }

4.删除节点

    // 删除任意节点
    public void remove(int val) {
        boolean isExist = contains(val);
        if (isExist) {
            this.root = remove(this.root, val);
        }
    }

    // 从以node为根的二分搜索树中删除值为val的结点
    private Node remove(Node node, int val) {
        Node resultNode = null;
        if (node.val == val) {
            // 叶子节点
            if (node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                this.size--;
                resultNode = rightNode;
            } else if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                this.size--;
                resultNode = leftNode;
            } else {
                // 找出删除节点的后继
                Node nextNode = getMinNode(node.right);
                // 从右树中删除最小节点
                nextNode.right = removeMinNode(node.right);
                // 开始连接
                nextNode.left = node.left;
                // 让node失去关联关系
                node.left = node.right = null;
                resultNode = nextNode;
            }
        } else if (node.val > val) {
            node.left = remove(node.left, val);
            resultNode = node;
        } else {
            node.right = remove(node.right, val);
            resultNode = node;
        }

        // 删除的是叶子节点
        if (resultNode == null) {
            return null;
        }

        // 删除之后,可能改变了树的平衡,因此需要进行调整
        resultNode.height = Math.max(getNodeHeight(resultNode.left), getNodeHeight(resultNode.right)) + 1;

        Node result = resultNode;
        if (getBalance(resultNode) > 1 && getBalance(resultNode.left) >= 0) {
            result = rightRotate(resultNode);
        } else if (getBalance(resultNode) < -1 && getBalance(resultNode.right) <= 0) {
            result = leftRotate(resultNode);
        } else if (getBalance(resultNode) > 1 && getBalance(resultNode.left) < 0) {
            resultNode.left = leftRotate(resultNode.left);
            result = rightRotate(resultNode);
        } else if (getBalance(resultNode) < -1 && getBalance(resultNode.right) > 0) {
            resultNode.right = rightRotate(resultNode.right);
            result = leftRotate(resultNode);
        }
        return result;
    }

全部代码

import java.util.*;

public class AVLTree {

    private Node root;
    private int size;

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

    // 判断是否为空
    public boolean isEmpty() {
        return this.size == 0;
    }

    // 获取树中节点个数
    public int getSize() {
        return this.size;
    }

    // 获取节点高度
    public int getNodeHeight(Node node) {
        if (node == null) {
            return 0;
        }
        return node.height;
    }

    // 获取当前节点的平衡因子(左右子树高度差)
    private int getBalance(Node node) {
        if (node == null) {
            return 0;
        }
        return getNodeHeight(node.left) - getNodeHeight(node.right);
    }

    // 查找最小节点
    public Node getMinNode() {
        if (this.root == null) {
            return null;
        }
        return getMinNode(this.root);
    }

    private Node getMinNode(Node node) {
        if (node.left == null) {
            return node;
        }
        return getMinNode(node.left);
    }

    // 判断是否重复
    public boolean contains(int val) {
        return contains(this.root, val);
    }

    private boolean contains(Node node, int val) {
        if (node == null) {
            return false;
        }
        if (node.val == val) {
            return true;
        } else if (node.val > val) {
            return contains(node.left, val);
        } else {
            return contains(node.right, val);
        }
    }

    // 向树中添加节点
    public void add(int val) {
        if (contains(val)) {
            return;
        }
        this.root = add(this.root, val);
        this.size++;
    }

    // 向AVL树中添加节点
    private Node add(Node node, int val) {
        // 递归到底的情况
        if (node == null) {
            return new Node(val);
        }
        if (node.val > val) {
            node.left = add(node.left, val);
        } else {
            node.right = add(node.right, val);
        }
        // 更新节点高度
        node.height = Math.max(getNodeHeight(node.left), getNodeHeight(node.right)) + 1;

        // 维护平衡
        int balance = getBalance(node);
        if (balance > 1 && getBalance(node.left) >= 0) {
            // 右旋
            return rightRotate(node);
        } else if (balance > 1 && getBalance(node.left) <= 0) {
            // 左旋右旋
            node.left = leftRotate(node.left);
            return rightRotate(node);
        } else if (balance < -1 && getBalance(node.right) >= 0) {
            // 右旋左旋
            node.right = rightRotate(node.right);
            return leftRotate(node);
        } else if (balance < -1 && getBalance(node.right) <= 0) {
            // 左旋
            return leftRotate(node);
        }
        return node;
    }

    // 从二分搜索树中删除最小节点
    public Node removeMinNode() {
        if (this.root == null) {
            return null;
        }
        Node result = getMinNode();
        if (result != null) {
            this.root = removeMinNode(this.root);
            this.size--;
        }
        return result;
    }

    private Node removeMinNode(Node node) {
        if (node.left == null) {
            return node.right;
        }
        node.left = removeMinNode(node.left);
        return node;
    }

    // 删除任意节点
    public void remove(int val) {
        boolean isExist = contains(val);
        if (isExist) {
            this.root = remove(this.root, val);
        }
    }

    // 从以node为根的二分搜索树中删除值为val的结点
    private Node remove(Node node, int val) {
        Node resultNode = null;
        if (node.val == val) {
            // 叶子节点
            if (node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                this.size--;
                resultNode = rightNode;
            } else if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                this.size--;
                resultNode = leftNode;
            } else {
                // 找出删除节点的后继
                Node nextNode = getMinNode(node.right);
                // 从右树中删除最小节点
                nextNode.right = removeMinNode(node.right);
                // 开始连接
                nextNode.left = node.left;
                // 让node失去关联关系
                node.left = node.right = null;
                resultNode = nextNode;
            }
        } else if (node.val > val) {
            node.left = remove(node.left, val);
            resultNode = node;
        } else {
            node.right = remove(node.right, val);
            resultNode = node;
        }

        // 删除的是叶子节点
        if (resultNode == null) {
            return null;
        }

        // 删除之后,可能改变了树的平衡,因此需要进行调整
        resultNode.height = Math.max(getNodeHeight(resultNode.left), getNodeHeight(resultNode.right)) + 1;

        Node result = resultNode;
        if (getBalance(resultNode) > 1 && getBalance(resultNode.left) >= 0) {
            result = rightRotate(resultNode);
        } else if (getBalance(resultNode) < -1 && getBalance(resultNode.right) <= 0) {
            result = leftRotate(resultNode);
        } else if (getBalance(resultNode) > 1 && getBalance(resultNode.left) < 0) {
            resultNode.left = leftRotate(resultNode.left);
            result = rightRotate(resultNode);
        } else if (getBalance(resultNode) < -1 && getBalance(resultNode.right) > 0) {
            resultNode.right = rightRotate(resultNode.right);
            result = leftRotate(resultNode);
        }
        return result;
    }

    // 中序遍历
    public List<Integer> middleTravel() {
        List<Integer> list = new ArrayList<>();
        middleTravel(this.root, list);
        return list;
    }

    private void middleTravel(Node node, List<Integer> list) {
        if (node == null) {
            return;
        }
        middleTravel(node.left, list);
        list.add(node.val);
        middleTravel(node.right, list);
    }

    // 层序遍历
    private List<Node> levelTravel() {
        List<Node> list = new ArrayList<>();
        Queue<Node> queue = new LinkedList<>();
        if (this.root == null) {
            return list;
        }
        queue.offer(this.root);
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            list.add(node);
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
        return list;
    }

    // 判断是否是二分搜索树
    public boolean isBinearySearchTree() {
        List<Integer> list = middleTravel();
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i - 1) > list.get(i)) {
                return false;
            }
        }
        return true;
    }

    // 判断是否为平衡二叉树
    public boolean isBalanceTree() {
        return isBalanceTree(this.root);
    }

    private boolean isBalanceTree(Node node) {
        if (node == null) {
            return true;
        }
        if (Math.abs(getBalance(node)) > 1) {
            return false;
        }
        return isBalanceTree(node.left) && isBalanceTree(node.right);
    }

    // 右旋转
    private Node rightRotate(Node y) {
        Node x = y.left;
        Node rightX = x.right;
        x.right = y;
        y.left = rightX;
        y.height = 1 + Math.max(getNodeHeight(y.left), getNodeHeight(y.right));
        x.height = 1 + Math.max(getNodeHeight(x.left), getNodeHeight(x.right));
        return x;
    }

    // 左旋转
    private Node leftRotate(Node y) {
        Node x = y.right;
        Node leftX = x.left;
        x.left = y;
        y.right = leftX;
        y.height = 1 + Math.max(getNodeHeight(y.left), getNodeHeight(y.right));
        x.height = 1 + Math.max(getNodeHeight(x.left), getNodeHeight(x.right));
        return x;
    }

    public String show() {
        // 按层遍历树
        List<Node> list = levelTravel();
        String s = "";
        for (int i = 0; i < list.size(); i++) {
            s += "size = " + list.get(i).val + ", height = " + list.get(i).height + ", balance = " + getBalance(list.get(i)) + ";\n";
        }
        return s;
    }

    @Override
    public String toString() {
        // 按层遍历树
        List<Node> list = levelTravel();
        String s = "["+list.get(0).val;
        for (int i = 1; i < list.size(); i++) {
            s += "," + list.get(i).val;
        }
        s += "]";
        return s;
    }

    private class Node {
        int val;
        Node left;
        Node right;
        int height;

        public Node(int val) {
            this.val = val;
            this.left = this.right = null;
            this.height = 1;
        }
    }

    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            avlTree.add(i);
            avlTree.add(random.nextInt(100));
        }
        System.out.println(avlTree.toString());
        // 判断是否为平衡二叉树
        System.out.println(avlTree.isBalanceTree());
        avlTree.removeMinNode();
        System.out.println(avlTree.toString());
        avlTree.remove(9);
        System.out.println(avlTree.toString());

    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【nav_msgs/Path.h发布路径】

#include <nav_msgs/Path.h> 是一个 ROS (Robot Operating System) 中的包含文件。它是用于包含 nav_msgs/Path 消息类型的头文件,这是一个标准的 ROS 消息类型。 nav_msgs/Path 消息类型常用于机器人导航系统中,以表示路径。这种路径通常由一系列的位置点组成,这些点…

解决:yarn 无法加载文件 “C:\Users\admin\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁止运行脚本“ 的问题

1、问题描述&#xff1a; 其一、报错的整体代码为&#xff1a; yarn : 无法加载文件 C:\Users\admin\AppData\Roaming\npm\yarn.ps1&#xff0c;因为在此系统上禁止运行脚本 // 整体的报错代码为 &#xff1a; yarn : 无法加载文件 C:\Users\admin\AppData\Roaming\npm\yar…

Linux环境搭建(三)— 搭建数据库服务器

linux &#xff08;ubuntu&#xff09;安装mysql 和环境配置 一、安装MySql二、配置环境三、外网访问四、重置密码五、卸载 写在前面&#xff1a; 本文默认你的Linux系统已经安装vim&#xff0c;yum等&#xff0c;如你使用的是一个全新的操作系统&#xff0c;移步上一篇开始配置…

迪赛智慧数——柱状图(象形动态图):高考填报专业考虑的因素

效果图 填报志愿是高考后的一大重要环节&#xff0c;你的职业生涯就在这里起航了。那么&#xff0c;应该怎么填报志愿呢&#xff1f;高考填报专业考虑的因素很多&#xff0c;过半的人会考虑专业就业前景及薪资&#xff0c;其次是个人兴趣和是否为双一流建设学科。 数据源&…

基于深度学习的高精度动物园动物检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度动物园动物&#xff08;水牛、斑马、大象、水豚、海龟、猫、奶牛、鹿、狗、火烈鸟、长颈鹿、捷豹、袋鼠、狮子、鹦鹉、企鹅、犀牛、羊、老虎&#xff09;检测识别系统可用于日常生活中或野外来检测与定位动物园动物&#xff0c;利用深度学…

【算法与数据结构】541、LeetCode反转字符串 II

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题自己写了一个swap函数&#xff0c;用来反转字符串&#xff0c;也可以用库函数reverse。然后是用in…

mac上使用brew安装mysql5.7

使用Homebrew进行MySQL数据库的安装需要MacOS系统中已经安装了相关环境 1.查询软件信息 首先使用search命令搜索MySQL数据库完整名称&#xff1a; brew search mysql可以看到5.7版本的MySQL数据库完整名称是mysql5.7 2. 执行安装命令 使用install命令进行软件安装&#xf…

基于单片机电子密码锁射频卡识别指纹门禁密码锁系统的设计与实现

功能介绍 通过指纹进行开锁或者是按键输入当前的密码&#xff0c;修改密码&#xff0c;对IC卡可以进行注册&#xff0c;删除。当有RFID卡进入到读卡器的读卡范围内时&#xff0c;则会自动读取卡序列号&#xff0c;单片机根据卡的序列号对卡进行判断。若该卡是有效卡&#xff0c…

Markdown 进阶语法:Mermaid 绘图 (一) - 流程图 (Flowchart)

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

瑞吉外卖-Day02

title: 瑞吉外卖-Day02 abbrlink: ‘1’ date: 2023-04-1 19:30:00 瑞吉外卖-Day02 课程内容 完善登录功能新增员工员工信息分页查询启用/禁用员工账号编辑员工信息 分析前端页面效果是如何实现的 为什么点击左边 右边会根着变化 [外链图片转存失败,源站可能有防盗链机制…

Neo4J 特性CQL语句,函数,Springboot集成

Neo4J Neo4J Neo4J一、Neo4J相关介绍1.为什么需要图数据库方案1&#xff1a;Google方案2&#xff1a;Facebook 2.特性和优势3.什么是Neo4j4.Neo4j数据模型图论基础属性图模型Neo4j的构建元素 5.软件安装 二、CQL语句1.CQL简介2.CREATE 命令3.MATCH 命令4.RETURN 子句5.MATCH和R…

FastDFS【FastDFS环境搭建_Linux、FastDFS指令、复习】(二)-全面详解(学习总结---从入门到深化)

目录 FastDFS环境搭建_Linux FastDFS指令 复习&#xff1a; FastDFS环境搭建_Linux 下载安装gcc 安装方式为yum安装&#xff08;需网络&#xff09;&#xff1a; yum install gcc-c perl-devel pcre-devel openssl-devel zlib-devel wget 下载安装FastDFS wget https:/…

vue3 异步组件

vue3中使用异步组件 vue3中使用异步组件可以解决两个问题&#xff1a; 1.提升性能&#xff08;类似于懒加载&#xff09; 2.分包 下载插件 npm i vueuse/core -S 1.提升性能&#xff08;懒加载&#xff09; 父组件 <template><div><h1>异步组件</h1&g…

【计算机视觉】对比学习综述(自己的一些理解)

对比loss 对比学习的 loss&#xff08;InfoNCE&#xff09;即以最 大化互信息为目标推导而来。其核心是通过计算样本表示间的距离&#xff0c;拉近正样本&#xff0c; 拉远负样本&#xff0c;因而训练得到的模型能够区分正负例。 具体做法为&#xff1a;对一个 batch 输入的图…

Matlab绘图系列教程-Matlab 34 种绘图函数示例(上)

Matlab绘图系列教程&#xff1a;揭秘高质量科学图表的绘制与优化 文章目录 Matlab绘图系列教程&#xff1a;揭秘高质量科学图表的绘制与优化第一部分&#xff1a;入门指南1.1 简介关于本教程的目的与范围Matlab绘图在科学研究中的重要性 1.2 准备工作安装Matlab及其工具箱 1.3 …

探索Python条件语句的奇妙世界:解密逻辑与控制流

文章目录 前言if 语句if ... else ...多重判断&#xff08;if ... elif ... else...&#xff09;if 嵌套猜数字游戏三目运算符 前言 Python的条件语句用来根据特定的条件决定程序的执行流程。它允许程序根据条件的真假执行不同的代码块&#xff0c;从而实现不同情况下的不同操…

ES6: 模版字符串

前言: ES5 中我们表示字符串的时候使用 或者 "" 作用: 在 ES6 中&#xff0c;我们还有一个东西可以表示字符串&#xff0c;就是 &#xff08;反引号&#xff09; let str hello worldconsole.log(typeof str) // string和单引号还有双引号的区别: 反引号可以换行…

《面向分布式云的直播及点播云技术创新方案》获中国信通院“分布式云技术创新先锋案例”

由中国信息通信研究院、中国通信标准化协会主办的第三届“云边协同大会”于 6 月 30 日在京举办。阿里云视频云团队凭借 《面向分布式云的直播及点播云技术创新方案》 在一众产品服务中脱颖而出&#xff0c;荣获「分布式云技术创新先锋案例」。 面向分布式云技术的直播及点播云…

83、基于STM32单片机录音机录音笔语音存储回放TF卡TFT屏系统设计(程序+原理图+PCB源文件+参考论文+硬件设计资料+元器件清单等)

单片机主芯片选择方案 方案一&#xff1a;AT89C51是美国ATMEL公司生产的低电压&#xff0c;高性能CMOS型8位单片机&#xff0c;器件采用ATMEL公司的高密度、非易失性存储技术生产&#xff0c;兼容标准MCS-51指令系统&#xff0c;片内置通用8位中央处理器(CPU)和Flash存储单元&a…

git介绍和使用

目录 一、git概述 1、简介 2、下载安装 二、git代码托管服务 1、常用的 Git 代码托管服务 2、使用码云代码托管服务 三、git常用命令 1、git全局设置 2、获取git仓库 3、工作区、暂存区、版本库 概念 4、Git工作区中文件的状态 5、本地仓库操作 6、远程仓库操作 …