一文详解二叉搜索树

news2024/11/25 5:02:17

数据结构-二叉查找树

前言

**摘自百度百科:**二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。

简而言之,二叉查找树(Binary Search Tree)又称为 二叉搜索树二叉排序树。它是一种 对搜索和排序都有用的特殊二叉树。而 红黑树AVL树 都是特殊的二叉树(自平衡二叉树)

定义

当一个二叉树为特殊二叉树时,势必会满足以下条件:

  • 当其左子树不为空时,其左子树上所有的节点的值均小于根节点的值。
  • 当其右子树不为空时,其右子树上所有节点的值均大于根节点的值。
  • 该树上的所有子树(左右子树)均为一颗二叉查找树。

其结构如下图所示:

未命名文件.png

应用场景

  • 电商系统的商品搜索与排序

  • 秒杀系统中的库存管理

  • 社交系统中的好友关系管理

  • 文件系统中的目录和文件管理

  • 图书管理系统中的书籍检索

以上应用场景只是举例说明,并不代表全部,二叉搜索树的应用场景非常广泛,当系统需要高效的执行:搜索、排序、插入、删除、范围查询等操作时均可考虑使用 二叉查找树

查询

二叉查找树 中,查询方式类似于 二分查找法,每次递归查询时都可以缩小一半的查找范围,其查询的速率也较高。

当使用 二叉查找树 去查找某条数据时(设这条数据为 x ,当前数据为 T ),其会按以下流程循环查询,直至查出结果,如果查到叶子节点仍无匹配数据时则返回 null

  • x == T 时,则 T 就是我们要查询的数据,返回 T
  • x < T 时,则去查询当前节点的左子树进行比对。
  • x > T 时,则去查询当前节点的右子树进行比对。

以上流程如下图所示:

未命名文件.gif

实体

根据上述的二叉查找树的概念,我们可以抽离出来一个模型,代码如下:

public class Node {

    public Integer data;

    public Node parent;

    public Node left;

    public Node right;
    
    public Node(){}

    public Node(Integer data) {
        this.data = data;
    }

    public Integer getData() {
        return data;
    }

    public void setData(Integer data) {
        this.data = data;
    }

    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

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

二叉查找树

建立好我们的实体后,我们可以单独创建一个类来进行二叉查找树的一些操作。

public class BinarySearchTree {

    private final Logger log = Logger.getLogger(BinarySearchTree.class.getName());

    private Node root;

    /**
     * @param data 需要查找的值
     * @return {@link Node}
     * @date 2023-09-16 10:53
     * @author Bummon
     * @description 二叉树查找
     */
    public Node findDataByBTS(Node node, Integer data) {
        while (Objects.nonNull(node)) {
            if (Objects.equals(data, node.data)) {
                return node;
            } else if (data < node.data) {
                //左子树
                node = node.left;
            } else if (data > node.data) {
                //右子树
                node = node.right;
            }
        }
        return null;
    }
}

测试

public class Main {
    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();
        Node root = new Node(50);
        Node node1 = new Node(30);
        root.setLeft(node1);
        Node node2 = new Node(70);
        root.setRight(node2);
        Node node3 = new Node(20);
        node1.setLeft(node3);
        Node node4 = new Node(40);
        node1.setRight(node4);
        Node node5 = new Node(60);
        node2.setLeft(node5);
        Node node6 = new Node(80);
        node2.setRight(node6);

        Node node = bst.findDataByBTS(root, 25);
        System.out.println(node);
    }
}

最终我们可以得到如下结果:

image.png

我们也可以去查找一个不存在的数据测试一下,这里使用25来测试,如下图所示:

image.png

新增

由于二叉查找树的特性,我们首先需要先找到插入元素的插入位置,设需要插入的元素为 x ,当前节点为 T ,则:

  • 当二叉查找树为空时,创建一个新的节点,并将 x 放到根节点的位置,且其 左子树右子树 均为空。
  • 当二叉树不为空且 x < T 时,将 x 插入 T 的左子树中。
  • 当二叉树不为空且 x > T 时,将 x 插入 T 的右子树中。

如下图所示:

未命名文件-(1).gif

代码实现

为了方便测试这里也增加了一个分层遍历的方法

/**
     * @param data 需要插入的值
     * @date 2023-09-16 10:54
     * @author Bummon
     * @description 插入节点
     */
    public void insert(int data) {
        if (Objects.isNull(root)) {
            root = initNode(root);
            root.setData(data);
        } else {
            recursionInsert(root, data);
        }
    }

    /**
     * @param data 需要插入的值
     * @date 2023-09-16 10:54
     * @author Bummon
     * @description 插入节点
     */
    public void insert(int data) {
        if (Objects.isNull(root)) {
            root = initNode(root);
            root.setData(data);
        } else {
            recursionInsert(root, data);
        }
    }

    /**
     * @param node 插入节点
     * @param data 需要插入的值
     * @return {@link Node}
     * @date 2023-09-16 10:53
     * @author Bummon
     * @description 私有化插入节点
     */
    private Node recursionInsert(Node node, int data) {
        if (Objects.isNull(node)) {
            node = new Node(data);
        }
        if (data < node.data) {
            node.left = recursionInsert(node.left, data);
            node.left.parent = node;
        } else if (data > node.data) {
            node.right = recursionInsert(node.right, data);
            node.right.parent = node;
        }
        return node;
    }

    /**
     * @param node 需要初始化的节点
     * @return {@link Node}
     * @date 2023-09-16 10:52
     * @author Bummon
     * @description 初始化节点
     */
    private Node initNode(Node node) {
        if (Objects.isNull(node)) {
            return new Node();
        }
        return node;
    }

    /**
     * @date 2023-09-16 14:18
     * @author Bummon
     * @description 分层遍历树
     */
    public void levelOrderTraversal() {
        Queue<Node> nodes = new ConcurrentLinkedDeque<>();
        nodes.add(root);
        while (nodes.size() > 0) {
            Node currNode = nodes.poll();
            if (Objects.nonNull(currNode.left)) {
                nodes.add(currNode.left);
            }
            if (Objects.nonNull(currNode.right)) {
                nodes.add(currNode.right);
            }
            String msg = String.format("{%s}(%s)", currNode.data, currNode.parent != null ? currNode.parent.data : "null");
            log.info(msg);
        }
    }

测试

public class Main {
    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();
        bst.insert(50);
        bst.insert(30);
        bst.insert(20);
        bst.insert(40);
        bst.insert(70);
        bst.insert(60);
        bst.insert(80);

        bst.levelOrderTraversal();
    }
}

我们执行的结果如下:

image.png

如果以树的形式来展示的话长以下这样:

未命名文件 (4).png

删除

当我们想要从二叉查找中删除某个节点时,会经历以下历程:首先先要查找到我们要删除的节点位置,如果查找成功则继续执行删除操作,否则直接返回。而删除操作会有以下四种情况:

  1. 当被删除的节点左右子树均为空时,则直接删除即可。
  2. 当被删除的节点只有左子树时,即右子树为空,则直接删除,并将其左子树的节点代替被删除的节点,代替后删除其左子树原先的节点。
  3. 当被删除的节点只有右子树时,即左子树为空,则直接删除,并将其右子树的节点代替被删除的节点,代替后删除其右子树原先的节点。
  4. 当被删除的节点左右子树均不为空时,则将其直接前驱或直接后续作为替代节点,将被删除的节点删除后,将替代节点放至被删除的节点位置,代替后删除替代节点原先的节点。

如下所示:

未命名文件-(1).gif

代码实现

public void deleteNode(int data) {
        if (root == null) {
            System.out.println("删除失败,为空树");
        }
        Node delNode = root;
        Node parent = null;
        //首先要先找到待删除的节点
        while (delNode != null) {
            if (data < delNode.data) {
                parent = delNode;
                delNode = delNode.left;
            } else if (data > delNode.data) {
                parent = delNode;
                delNode = delNode.right;
            } else {
                break;
            }
        }
        if (delNode == null) {
            log.info("Not Found " + data);
            return;
        }
        //删除节点左子树为空
        if (delNode.left == null) {
            if (delNode == root) {
                root = delNode.right;
            } else if (delNode == parent.left) {
                parent.left = delNode.left;
            } else {
                parent.right = delNode.right;
            }
        } else if (delNode.right == null) {
            //删除节点右子树为空
            if (delNode == root) {
                root = delNode.left;
            } else if (delNode == parent.left) {
                parent.left = delNode.left;
            } else {
                parent.right = delNode.left;
            }
        } else {
            //删除有节点左右子树均不为空
            Node nextParent = delNode;
            Node next = delNode.right;
            while (next.left != null) {
                nextParent = next;
                next = next.left;
            }
            delNode.data = next.data;
            if (nextParent == delNode) {
                nextParent.right = next.right;
            } else {
                nextParent.left = next.right;
            }
        }
    }

测试

public class Main {
    public static void main(String[] args) {
        BinarySearchTree bst = new BinarySearchTree();
        bst.insert(50);
        bst.insert(30);
        bst.insert(20);
        bst.insert(40);
        bst.insert(70);
        bst.insert(60);
        bst.insert(80);

        bst.levelOrderTraversal();
        bst.deleteNode(70);
        bst.levelOrderTraversal();
    }
}

我们会得到如下结果:

image.png

总结

二叉查找树 是一种特殊的二叉树,其搜索速率较快,类似于 二分查找法 ,其在查询时先确定查找值的查询范围,再逐步缩小范围,直至查出结果或查完所有节点仍未找到返回 null 。其最坏情况为遍历所有节点,即时间复杂度为 O(n),空间复杂度为 O(1)


推荐

关注博客和公众号获取最新文章

Bummon’s Blog | Bummon’s Home | 公众号

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

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

相关文章

SpringBoot运维实用篇

SpringBoot运维实用篇 ​ 基础篇发布以后&#xff0c;看到了很多小伙伴在网上的留言&#xff0c;也帮助超过100位小伙伴解决了一些遇到的问题&#xff0c;并且已经发现了部分问题具有典型性&#xff0c;预计将有些问题在后面篇章的合适位置添加到本套课程中&#xff0c;作为解…

怎么将自己的Maven项目上传到Maven中央仓库/Maven阿里云云效仓库

前言 对于工作了多年的老程序员来说&#xff0c;往往会总结出一些比较好用的开发工具包。那么如果把这些好的工具插件共享出来供大家快速的使用呢&#xff0c;最好的方式就是将这些工具插件上传到Maven中央仓库/Maven阿里云云效仓库&#xff0c;这样&#xff0c;有需要用到这些…

泰语同声翻译一天多少钱呢

我们知道&#xff0c;泰语同声翻译是指在涉外会议、展览会、商务洽谈等场合中广泛使用的翻译服务。活动中&#xff0c;专业的翻译人员在场内提供及时的泰语口译服务&#xff0c;使听众能够及时地理解讲话者的内容。那么&#xff0c;泰语同声翻译一天多少钱呢&#xff1f;如何做…

BeanFactory与ApplicationContext

BeanFactory与ApplicationContext的区别 使用Alt Ctrl U查看java类图 什么是BeanFactory接口 他是ApplicationContext的父接口他才是Spring 的核心容器&#xff0c;主要的ApplicationContext功能的实现都间接通过BeanFactory接口来实现 在ApplicationContext类中方法的实现是…

数据结构与算法(C语言版)P4---顺序表、链表总结

顺序表和链表&#xff08;双向带头链表&#xff09;的区别 顺序表&#xff1a; 优点&#xff1a; 支持随机访问。需要随机访问结构支持算法可以很好的使用。cpu高速缓存利用率&#xff08;命中率&#xff09;更高。存储密度高 缺点&#xff1a; 头部中部插入删除时间效率低。…

数据结构与算法面试

1、链表反转 需要三个指针&#xff0c;一个pre指针指向反转的前一个节点&#xff0c;cur指向要反转的节点&#xff0c;然后设置有一个temp指针指向需要反转的下一个节点&#xff0c;用来使得cur指针移动&#xff0c;因为我们反转之后&#xff0c;无法使用next指针访问到后一个节…

【三维重建】3D Gaussian Splatting:实时的神经场渲染

文章目录 摘要一、前言二、相关工作1.传统的场景重建与渲染2.神经渲染和辐射场3.基于点的渲染和辐射场4.*什么是 Tile-based rasterizer (快速光栅化) 三、OVERVIEW四、可微的三维高斯 Splatting五、三维高斯 自适应密度控制的优化1.优化2.高斯的自适应控制 六、高斯分布的快速…

计算机是如何工作的(上篇)

计算机发展史 世界上很多的高科技发明,来自于军事领域 计算机最初是用来计算弹道导弹轨迹的 弹道导弹 ~~国之重器,非常重要 两弹一星 原子弹,氢弹,卫星(背后的火箭发射技术) 计算弹道导弹轨迹的计算过程非常复杂,计算量也很大 ~~ 但是可以手动计算出来的(当年我国研究两弹一…

【深度学习】Pytorch 系列教程(九):PyTorch数据结构:2、张量操作(Tensor Operations):(3)张量变形

目录 一、前言 二、实验环境 三、PyTorch数据结构 0、分类 1、张量&#xff08;Tensor&#xff09; 2、张量操作&#xff08;Tensor Operations&#xff09; 1. 数学运算 2. 统计计算 3. 张量变形 展开张量&#xff1a;flatten 改变张量的形状&#xff1a;view 改变…

TDE和数据脱敏功能介绍

TDE(Transparent Data Encryption)和数据脱敏(Data Masking)是两种常见的数据安全技术&#xff0c;它们在保护敏感数据和增强数据隐私方面起着至关重要的作用。接下来&#xff0c;将对这两种技术进行详细的介绍。 TDE&#xff0c;全称透明数据加密(Transparent Data Encryption…

Golang编写自定义IP限流中间件

目录 基于令牌桶的限流算法实现高并发限流&#xff08;使用golang官方限流器&#xff09;Go代码测试记录ab -t 1 -c 1 http://127.0.0.1:8080/api/resource结果预测&#xff1a;1秒内最多生成10个令牌&#xff0c;而总共有20个串行的请求&#xff0c;结果应该是1个成功&#xf…

Hadoop源码阅读(二):DataNode启动

说明&#xff1a; 1.Hadoop版本&#xff1a;3.1.3 2.阅读工具&#xff1a;IDEA 2023.1.2 3.源码获取&#xff1a;Index of /dist/hadoop/core/hadoop-3.1.3 (apache.org) 4.工程导入&#xff1a;下载源码之后得到 hadoop-3.1.3-src.tar.gz 压缩包&#xff0c;在当前目录打开Pow…

二叉树(2——二叉树链式结构的实现)

二叉树的遍历 前序、中序以及后序遍历 学习二叉树结构&#xff0c;最简单的方式就是遍历。所谓 二叉树遍历 (Traversal) 是按照某种特定的规则&#xff0c;依次对二叉 树中的节点进行相应的操作&#xff0c;并且每个节点只操作一次 。访问结点所做的操作依赖于具体的应用问题。…

第一次课,通过进程信息和服务信息识别当前计算机运行程序(预习版)

题目&#xff1a; 检测的目标进程&#xff1a; ydebugg ; “ImmunityDebugger.exe” _500], rax Exe ; “ollydbg.exe” _4F8], rax hackerE ; “ProcessHacker.exe” _4F0], rax Exe ; “tcpview.exe” _4E8], rax sExe ; “autoruns.exe” _4E0], rax scExe ; “autorunsc.ex…

什么是GPT磁盘?介绍GPT(GUID 分区表)磁盘及其优势!

GPT概述 GPT磁盘是什么意思&#xff1f;GPT是全局唯一标识符分区表&#xff08;GUID Partition Table&#xff09;的简称&#xff0c;它是硬盘分区表结构的一个标准模式。在我们深入了解GPT磁盘的特性之前须知&#xff0c;MBR磁盘的分区信息直接保存在主引导记录&#xff0…

【论文阅读】Cornus: Atomic Commit for a Cloud DBMS with Storage Disaggregation

Cornus Paper Preknowledge Share-Nothing Related Work Cornus: Atomic Commit for a Cloud DBMS with Storage Disaggregation ABSTRACT 传统2PC存在两个限制&#xff08;缺点&#xff09; Long Latency&#xff1a;long latency due to two eager log writes on the …

SQL优化--分页优化(limit)

在数据量比较大时&#xff0c;如果进行limit分页查询&#xff0c;在查询时&#xff0c;越往后&#xff0c;分页查询效率越低。 通过测试我们会看到&#xff0c;越往后&#xff0c;分页查询效率越低&#xff0c;这就是分页查询的问题所在。 因为&#xff0c;当在进行分页查询时&…

想要精通算法和SQL的成长之路 - 最长等差数列

想要精通算法和SQL的成长之路 - 最长等差数列 前言一. 最长等差数列 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 最长等差数列 原题链接 思路&#xff1a; 我们假设dp[i][j] 为&#xff1a;以num[i]为结尾&#xff0c;以j为公差的最长等差子序列的长度。由此可知&a…

wifi密码破解

文章目录 前言一、破解原理二、配置环境三、运行测试四、资源自取 前言 本文基于 python 实现了破解 wifi 密码的功能&#xff0c;采用的破解方式是穷举法&#xff0c;效率相对来说很低&#xff0c;对于设置密码简单的路由器来说比较适用。 一、破解原理 程序主要采用 python…

2023-09-16 CSP-J 第一轮初赛真题解析

分数&#xff1a;100 考试时间&#xff1a;120分钟 一、 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 1.在 C中&#xff0c;下面哪个关键字用于声明一个变量&#xff0c;其值不能被修改&#xf…