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

news2024/11/24 19:54:33

文章目录

  • 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());

    }
}

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

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

相关文章

不再担心代码丢失!掌握同步代码到两个git仓库的绝妙方法!

最近&#xff0c;我手里的项目由于某些原因&#xff0c;需要从一个代码仓库把所有的代码复制到另一个代码仓库中&#xff0c;并且以后再同步代码的时候&#xff0c;需要同时把本地的代码同步到两个代码仓库。为了满足这一需求&#xff0c;我将为大家介绍两种方案。方案一可以实…

飞行动力学 - 第4节-part1-螺旋桨式飞机的最大最小速度 之 基础点摘要

飞行动力学 - 第4节-part1-螺旋桨式飞机的最大最小速度 之 基础点摘要 1. 最小功率和最大/最小速度概念2. 最小功率2.1 手工推导2.2 PPT推导 3. 最大速度和最小速度函数关系4. 参考资料 1. 最小功率和最大/最小速度概念 最小功率&#xff1a;类似抛物线底部斜率为零的位置最大…

Qt-事件(下)(事件过滤、自定义事件)

文章目录 事件过滤自定义事件 事件过滤 event()函数是一个protected的函数&#xff0c;这意味着我们要想重写event()&#xff0c;必须继承一个已有的组件类&#xff0c;——重写其event()函数。event()函数的确有一定的控制&#xff0c;不过有时候我的需求更严格一些&#xff…

使用Python+Autogluon对“员工自评”进行机器学习建模分析

建模核心代码 #员工自评AutoML from autogluon.tabular import TabularDataset, TabularPredictor import warnings warnings.filterwarnings(ignore) train_data TabularDataset(train_df2)# 预测标签 label 员工自评# 模型保存文件名 save_path ../data/AUO-train/model/…

一种快速拓扑聚类算法

使用场景&#xff0c;节点编号不重叠&#xff0c;可以缺损&#xff0c;确定每个节点的相互关系和最大的节点编号&#xff0c;对节点进行聚类。如下图所示&#xff0c;分三个簇&#xff0c;计算每个簇包含的元素。 插入代码&#xff0c;暂时没有继续优化的空间 // TopologicalC…

嵌入式软件开发面试题(一)

目录 1.用预处理指令表示一年有多少秒 2.写出float x 与“零值”比较的if语句 3.为什么说if(0x)比if(x0)好? 4.将地0x8000中存放的整形变量&#xff0c;清除bit1。 5.linux下用shell命令在当前目录下创建myfolder目录&#xff0c;并将此目录的权限设为拥有者可读写群组和…

携手共赢!润建股份与科士达达成战略合作

7月4日&#xff08;今日&#xff09;&#xff0c;润建股份有限公司&#xff08;以下简称”润建股份“&#xff09;与深圳科士达科技股份有限公司&#xff0c;在科士达光明工业园正式签署战略合作框架协议&#xff0c;双方将发挥各自领域的技术优势&#xff0c;在新能源与数字科…

Android开发之屏幕尺寸的兼容

屏幕尺寸和密度 本部分提供了具有特定屏幕配置&#xff08;由屏幕尺寸和密度定义&#xff09;的设备的相对数量数据。为了简化针对不同屏幕配置设计界面的过程&#xff0c;Android 将实际屏幕尺寸和密度的范围划分为多个区间&#xff08;如下表所示&#xff09;。 ldpi mdpitv…

MySQL之数据库引擎详解(内附面试题:InnoDB和MyISAM的联系与区别)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于MySQL数据库引擎的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一. 数据库引擎是什么&#xff…

【kubernetes系列】Kubernetes之生命周期和重启策略

概述 Pod 遵循一个预定义的生命周期&#xff0c;起始于 Pending 阶段&#xff0c;如果至少 其中有一个主要容器正常启动&#xff0c;则进入 Running&#xff0c;之后取决于 Pod 中是否有容器以 失败状态结束而进入 Succeeded 或者 Failed 阶段。 在 Pod 运行期间&#xff0c;k…

(30)精准降落和悬停(IRLock)

文章目录 30.1 概述 30.2 哪里可以买到 30.3 连接到自动驾驶仪 30.4 安装到框架上 30.5 通过任务规划器进行设置 30.6 飞行和测试 30.1 概述 Copter 支持使用 IR-LOCK 传感器(IR-LOCK sensor)和声纳或激光雷达(sonar or lidar)进行精确着陆。使用该系统&#xff0c;当飞行…

畅谈RocketMQ重复消费7个根源问题

在众多关于MQ的面试八股文中有这么一道题&#xff0c;“如何保证MQ消息消费的幂等性”。 为什么需要保证幂等性呢&#xff1f;是因为消息会重复消费。 为什么消息会重复消费&#xff1f; 明明已经消费了&#xff0c;为什么消息会被再次被消费呢&#xff1f; 不同的MQ产生的…

0基础学习VR全景平台篇 第56篇:专业版功能-故事线

功能位置示意 一、本功能将用在哪里&#xff1f; 故事线功能&#xff0c;支持将多个VR视频片段&#xff0c;自由设置剧情&#xff0c;在故事中设置多个路线结局&#xff0c;实现VR视频创作新玩法。 区别传统VR视频单项输出内容&#xff0c;促使用户主动思考&#xff0c;参与剧…

windows、linux部署seata1.3.0

前提:https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明Spring Cloud AlibabaSpring CloudSpring BootNacosSeata2.2.7.RELEASESpring Cloud Hoxton.SR122.3.12.RELEASE2.0.31.3.0背景:seata配合nacos使用,并开启nacos,nacos使用默认的namespace,mysql5.7 s…

在Jetpack Compose中使用SurfaceView

在 Android 开发中&#xff0c;SurfaceView 是一种特殊的视图&#xff0c;它拥有自己的专用绘图表面&#xff0c;可以在后台线程中更新&#xff0c;非常适合需要频繁和快速绘制的地方&#xff0c;如游戏和视频播放。然而&#xff0c;在Jetpack Compose&#xff08;Google的新的…

dxf文件怎么转成dwg格式?分享几种简单的转换方法

将DXF文件转成DWG格式可以提高CAD文件的兼容性和功能性&#xff0c;使其更易于在不同的CAD软件之间传输和共享。如果我们需要与其他人共享CAD图纸&#xff0c;或者想要更好地利用CAD编辑软件的各种功能和工具&#xff0c;将文件转换为DWG格式会更好一些&#xff0c;那么怎么进行…

佳明手表APP开发系列02——汉字和图标的标准显示

前言 使用点阵字库的方式来汉化或者增强佳明App的显示是一种传统的方法&#xff0c;操作繁琐&#xff0c;效果也一般。笔者通过进一步的学习&#xff0c;发现佳明的MonkeyC支持一种新的方式&#xff0c;即 BmpFont文件的显示&#xff0c;可以像使用普通字符一样对包括汉字在内…

东芝光电耦合器TLP152(TPL,E的工作原理以及应用

东芝深力科TLP152(TPL,E是SO6封装中的光电耦合器&#xff0c;由GaA组成ℓ作为红外发光二极管&#xff08;LED&#xff09;光学耦合到集成的高增益、高速光电探测器IC芯片。光电探测器IC芯片具有内部屏蔽&#xff0c;提供20kV的高共模瞬态抗扰度/s&#xff0c;从而在输入和输出引…

MySQL-分库分表详解(二)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

港联证券|利好刺激创新药板块迎久违拉升 估值处历史低位

昨日&#xff0c;国家医保局就《谈判药品续约规则》及《非独家药品竞价规则》公开征求意见。在此利好消息影响下&#xff0c;创新药板块集体拉升。截至收盘&#xff0c;创新药指数涨幅近1%&#xff0c;实现四连阳。港股创新药指数收盘涨超3%。 机构认为政策利好创新药发展 《非…