Java集合系列源码分析(六)-- 二叉查找树

本文详细介绍了二叉查找树的基本概念、插入、查找、删除操作及遍历方法,并分析了其查找效率,同时讨论了极端情况下可能出现的问题及平衡二叉树的概念。

之前讲了部分集合源码,有涉及到红黑树,一下就讲红黑树有点不好讲,先从简单点的树开始讲,以下就从二叉查找树开始讲

一、 二叉查找树

二叉查找树(Binar Search Tree),又称二叉搜索树,二叉排序树

二叉查找树特性:

  1. 若左子树非空,则左子树上所有的节点关键字值均小于根节点的关键字值
  2. 若右子树非空,则右子树上所有节点关键字值均大于根节点的关键字值
  3. 左右子树本身也分别是一颗二叉查找树

简单来说就是左子树节点值<根节点值<右子树节点值。

如下图所示就是一个二叉查找树:
在这里插入图片描述

二、二叉查找树的增删改查

二叉搜索树的节点定义

public class Node {

    public int data;
    public Node left;
    public Node right;

    public Node(int data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                '}';
    }
}

2.1 二叉查找树的插入

  • 若二叉查找树为空,则直接插入节点,即根节点
  • 若插入的节点值小于根节点值,则插入左子树
  • 若插入的节点值大于根节点值,则插入右子树
  • 新插入的节点一定是一个叶节点
public class BinarySearchTree {

    private Node root;

    public BinarySearchTree() {
        root = null;
    }

    public boolean insert(int val){
        Node newNode = new Node(val);
        if(root == null){
            root = newNode;
        }else {
            Node temp = root;
            Node parent;
            while(true){
                parent = temp;
                if(val < temp.data){
                    temp = temp.left;
                    if(temp == null){
                        parent.left = newNode;
                        return true;
                    }
                }else if (val > temp.data){
                    temp = temp.right;
                    if(temp == null){
                        parent.right = newNode;
                        return true;
                    }
                }else{
                    return false;
                }
            }
        }
        return false;
    }
}

2.2 二叉查找树的查找

二叉查找树的查找先从根节点开始,进行比较向下查找。根据二叉查找树的特性,最小值必然在左子树的最左侧节点,最大值必然在右子树的最右侧节点。

而其他节点的查找则根据查找数与根节点大小的比较,比根节点小的则查找根节点的左子树,比根节点大的则查找根节点的右子树

   public Node search(int val) {
        Node temp = root;
        while (val != temp.data) {
            //判断查询方向
            if (val < temp.data) {
                temp = temp.left;
                if(temp == null){
                    return null;
                }
            }
            if (val > temp.data) {
                temp = temp.right;
                if(temp == null){
                    return null;
                }
            }
        }
        return temp;
    }

比较简单,看下就懂了,不多说了

2.3 二叉查找树的删除

删除过程比较复杂

在删除时,只能删除需要删除的节点,而该节点下的子树不能删除,删除后有可能又破坏了二叉查找树的构成条件,需要重新构造一下

因此删除分为以下几种情况:

  1. 删除叶节点:直接删除即可,不会破坏二叉查找树的构成
  2. 删除节点只有一颗左子树或右子树:直接让删除节点的子树替代删除节点的位置
  3. 删除节点有左右两颗子树:使用删除节点后,中序第一个子女填补

情况1:删除叶节点

  public void delete(int val) {
        Node temp = root;
        Node parent = null;
        String position = null;
        while (val != temp.data) {
            if (val < temp.data) {
                parent = temp;
                position = "left";
                temp = temp.left;
                if (temp == null) {
                    return;
                }
            }
            if (val > temp.data) {
                parent = temp;
                position = "right";
                temp = temp.right;
                if (temp == null) {
                    return;
                }
            }
        }

        //情况1:检查删除的节点是否是叶子节点
        if (isLoof(temp)) {
            if (position.equals("left")) {
                parent.left = null;
            } else {
                parent.right = null;
            }

            return ;
        }
        }

删除需要先查找再删除,暂时想到这个比较笨的code,有好的可以分享出来。逻辑比较简单,不多解释了。

情况2:删除节点只有一颗子树

删除节点只有1颗子树只有以下图示得四种情况
在这里插入图片描述

根据上面的四种类型,在删除方法里增加以下代码

        //判断是否是情况2
        Node next = isLoofParent(temp);
        if (next != null) {
            if (position.equals("left")) {
                parent.left = next;
            } else {
                parent.right = next;
            }
            temp.left = null;
            temp.right = null;

            return ;
        }

isLoofParent(Node temp)的代码

    private Node isLoofParent(Node temp) {
        if (temp.left == null && temp.right != null) {
            temp = temp.right;
            return temp;
        }
        if (temp.left != null && temp.right == null) {
            temp = temp.left;
            return temp;
        }
        return null;
    }

代码还是比较low的,逻辑也简单,不解释了。

情况3:左右都有子树

按中序找删除节点后面节点的第一个子女,填补删除节点,然后删除删除节点后面节点的第一个子女。说的有点绕,看下面的图:
在这里插入图片描述
以上方法,填补后转换为情况1或情况2

附上完整的删除方法,都比较简单,因为情况有限,每种情况做了if就找到了,就是感觉有点low

    public void delete(int val) {
        Node temp = root;
        Node parent = null;
        String position = null;
        while (val != temp.data) {
            if (val < temp.data) {
                parent = temp;
                position = "left";
                temp = temp.left;
                if (temp == null) {
                    return;
                }
            }
            if (val > temp.data) {
                parent = temp;
                position = "right";
                temp = temp.right;
                if (temp == null) {
                    return;
                }
            }
        }

        //情况1:检查删除的节点是否是叶子节点
        if (isLoof(temp)) {
            if (position.equals("left")) {
                parent.left = null;
            } else {
                parent.right = null;
            }

            return ;
        }

        //判断是否是情况2
        Node next = isLoofParent(temp);
        if (next != null) {
            if (position.equals("left")) {
                parent.left = next;
            } else {
                parent.right = next;
            }
            temp.left = null;
            temp.right = null;

            return ;
        }


        //检查是否是情况3
        if (temp.left != null && temp.right != null) {
            parent = temp;
            position = "right";
            Node rNode = temp.right;
            while (rNode.left != null) {
                parent = rNode;
                position = "left";
                rNode = rNode.left;
            }
            int tempData = rNode.data;
            delete(rNode.data);
            temp.data = tempData;
        }
    }

2.4 二叉查找树的遍历

遍历树可以使用递归,很简单方便,但是可能执行效率稍低。

中序遍历的代码如下,前序和后序只需要换下输出语句的位置即可

public void inOrder() {
    this.inOrder(root);
}

private void inOrder(Node root) {
    if (root == null) {
        return;
    }
    inOrder(root.left);
    System.out.println(root.data + " ");
    inOrder(root.right);
}

三、二叉查找树的查找效率分析

对一个高度为h的二叉查找树来说,查找、删除和插入的时间都是O(h)。效率主要取决于树的高度,相当于2分查找,平均查找长度为O(log2n)(不知道怎么把n上标,理解就好)

因此二叉查找树的查找效率也是比较高的,与2分法还有点不同。二分法的查找树是唯一的,但是二叉查找树会根据插入的不同顺序生成不同的树。

有的时候二叉查找树会生成一种极端情况,如下所示:

在这里插入图片描述
在这种情况下,相当于是一个链表,查找效率就会受到严重影响。

为了解决这个问题,又出现了平衡二叉树,下一节说一下平衡二叉树

【飞机能量-机动性(E-M)特性】飞机评估的最大转弯速度(即机动速度)、最大可持续转弯速度和最大可持续载荷系数对应的真空速度(Matlab代码实现)内容概要:本文档主要围绕飞机能量-机动性(E-M)特性展开,重点研究了飞机评估中的最大转弯速度(即机动速度)、最大可持续转弯速度以及最大可持续载荷系数对应的真空速度,并提供了基于Matlab的代码实现方法。内容涵盖飞行力学中的关键性能指标计算,结合理论分析与编程仿真,帮助理解飞机在不同飞行状态下的机动能力边界。此外,文档还涉及大量与航空航天、无人机控制、路径规划、电力系统优化、信号处理等相关课题的Matlab/Simulink仿真实例,展示了其在多领域科研中的广泛应用。; 适合人群:具备一定航空工程或自动化背景,熟悉Matlab编程,从事飞行器设计、控制算法开发或相关科研工作的研究生、工程师及科研人员。; 使用场景及目标:①用于飞机机动性能分析与仿真,掌握E-M图的核心构建方法;②通过Matlab代码实现关键飞行参数的计算,支撑飞行器性能评估与控制系统设计;③作为教学与科研参考资料,辅助开展航空航天领域的建模与仿真工作。; 阅读建议:建议读者结合飞行力学基础知识,逐步运行并调试所提供的Matlab代码,深入理解各项性能参数的物理意义与计算逻辑,同时可参考文档中其他相关领域的案例进行拓展学习与交叉应用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值