【数据结构与算法】二叉排序树(BST)

news2024/11/24 3:44:02

二叉排序树(BST)

需求

给你一个数列{7,3,10,12,5,1,9},要求能够高效的完成对数据的查询和添加。

解决方案分析

  • 使用数组
    • 数组未排序,优点:直接在数组尾添加,速度快。缺点:查找速度慢。
    • 数组排序,优点:可以使用二分查找,查找速度快,缺点:为保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。
  • 使用链式存储 - 链表
    • 不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。
  • 使用二叉排序树

基本介绍

二叉排序树:BST(Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。

特别说明:如果有相同的值,可以将该节点放在左子结点或右子节点。

比如针对前面的数据{7,3,10,12,5,1,9},对应的二叉排序树为:
在这里插入图片描述

二叉排序树的创建和遍历

一个大户组常见成对应的二叉排序树,并使用中序遍历二叉排序树

代码实现

public class BinarySortTreeDemo {
    public static void main(String[] args) {
        int[] arr = {7, 3, 10, 12, 5, 1, 9};
        BinarySortTree binarySortTree = new BinarySortTree();
        // 循环添加节点到二叉排序树
        for (int i = 0; i < arr.length; i++) {
            binarySortTree.add(new Node(arr[i]));
        }
        // 中序遍历
        System.out.println("中序遍历二叉排序树");
        binarySortTree.infixOrder();
    }
}

// 创建二叉排序树
class BinarySortTree {
    private Node root;

    // 添加节点的方法
    public void add(Node node) {
        // 如果 root 为空,则直接让 root 指向 node
        if (root == null) {
            root = node;
        } else {
            root.add(node);
        }
    }

    // 中序遍历
    public void infixOrder() {
        if (root != null) {
            root.infixOrder();
        } else {
            System.out.println("二叉排序树为空,不能遍历");
        }
    }

}

// 创建 Node 节点
class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    // 添加节点的方法
    // 通过递归的方式添加节点,注意需要满足二叉排序树的要求
    public void add(Node node) {
        if (node == null) {
            return;
        }
        // 判断出入节点的值和当前子树的根节点的关系
        if (node.value < this.value) {
            // 如果当前节点的左子节点为 null
            if (this.left == null) {
                this.left = node;
            } else {
                // 递归向左子树添加
                this.left.add(node);
            }
        } else {
            // 如果当前节点的右子节点为 null
            if (this.right == null) {
                this.right = node;
            } else {
                // 递归向右子树添加
                this.right.add(node);
            }
        }
    }

    // 中序遍历
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

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

二叉排序树的删除

二叉排序树的删除情况比较复杂,有下面三种情况:

  1. 删除叶子结点
  2. 删除只有一棵子树的的节点
  3. 删除有两棵子树的节点

第一种情况

删除叶子结点

思路:

  1. 先去找到要删除的节点 targeNode
  2. 找到 targeNode 的父节点 parent
  3. 确定 targeNode 是 parent 的左子节点还是右子节点
  4. 根据前面的情况来对应删除
    1. 左子节点:parent.left = null;
    2. 右子节点:parent.right = null;

第二种情况

删除只有一棵子树的的节点

思路:

  1. 先去找到要删除的节点 targeNode

  2. 找到 targeNode 的父节点 parent

  3. 确定 targeNode 的子节点是左子节点还是右子节点

  4. 确定 targeNode 是parent 的左子节点还是右子节点

  5. 如果 targeNode 有左子节点

    1. 如果 targeNode 是 parent 的左子节点:parent.left = targeNode.left;
    2. 如果 targeNode 是 parent 的右子节点:parent.right = targeNode.left;
  6. 如果 targeNode 有右子节点

    1. 如果 targeNode 是 parent 的左子节点:parent.left = targeNode.right;
    2. 如果 targeNode 是 parent 的右子节点:parent.left = targeNode.right;

第三种情况

删除有两棵子树的节点

思路:

  1. 先去找到要删除的节点 targeNode
  2. 找到 targeNode 的父节点 parent
  3. 从 targeNode 的右子树找到最小的节点
  4. 用一个临时变量,将最小的节点的值保存 temp = min
  5. 删除该最小节点
  6. targeNode.value = temp

代码实现:

/**
 * 得到以 node 为节点的最小节点的值,并删除该值
 *
 * @param node 传入的节点
 * @return 返回的是以 node 为根节点的二叉排序树的最小节点的值
 */
public int delRightTreeMin(Node node) {
    Node target = node;
    // 循环查找左节点,就会找到最小值
    while (target.left != null) {
        target = target.left;
    }
    // 这时 target 就指向了最小节点
    // 删除最小节点
    delNode(target.value);
    return target.value;
}

/**
 * 得到以 node 为节点的最大节点的值,并删除该值
 *
 * @param node 传入的节点
 * @return 返回的是以 node 为根节点的二叉排序树的最大节点的值
 */
public int delLiftTreeMax(Node node) {
    Node target = node;
    // 循环查找右节点,就会找到最小值
    while (target.right != null) {
        target = target.right;
    }
    // 这时 target 就指向了最大节点
    // 删除最大节点
    delNode(target.value);
    return target.value;
}

/**
 * 删除节点
 *
 * @param value 要删除节点的值
 */
public void delNode(int value) {
    if (root == null) {
        return;
    } else {
        // 1. 需要先去找到要删除的节点
        Node targetNode = search(value);
        // 如果没有找到要删除的节点
        if (targetNode == null) {
            return;
        }
        // 如果我们发现当前这棵二叉排序树只有一个节点
        if (root.left == null && root.right == null) {
            root = null;
            return;
        }
        // 去找到 targetNode 的父节点
        Node parent = searchParent(value);
        // 第一种情况
        // 如果要删除节点是叶子结点
        if (targetNode.left == null && targetNode.right == null) {
            // 判断 targetNode 是父节点的左子节点还是右子节点
            if (parent.left != null && parent.left.value == value) { // 是左子节点
                parent.left = null;
            } else if (parent.right != null && parent.right.value == value) { // 是右子节点
                parent.right = null;
            }
        } else if (targetNode.left != null && targetNode.right != null) {
            // 第三种情况
            // 如果要删除的节点是有两棵子树的节点
            // 向右子树找最小值
//                targetNode.value = delRightTreeMin(targetNode.right);
            // 向左子树找最大值
            targetNode.value = delLiftTreeMax(targetNode.left);
        } else {
            // 第二种情况
            // 如果要删除的节点是只有一棵子树的的节点
            // 如果要删除的节点有左子节点
            if (targetNode.left != null) {
                if (parent != null) {
                    // 如果 targetNode 是 parent 的左子节点
                    if (parent.left.value == value) {
                        parent.left = targetNode.left;
                    } else { // 如果 targetNode 是 parent 的右子节点
                        parent.right = targetNode.left;
                    }
                } else {
                    root = targetNode.left;
                }
            } else { // 如果要删除的节点有右子节点
                if (parent != null) {
                    // 如果 targetNode 是 parent 的左子节点
                    if (parent.left.value == value) {
                        parent.left = targetNode.right;
                    } else { // 如果 targetNode 是 parent 的右子节点
                        parent.right = targetNode.right;
                    }
                } else {
                    root = targetNode.right;
                }
            }
        }
    }
}

/**
 * 查找要删除节点的父节点
 *
 * @param value 要删除节点的值
 * @return 如果找到,放回父节点,否则,返回 null
 */
public Node searchParent(int value) {
    if (root == null) {
        return null;
    } else {
        return root.searchParent(value);
    }
}

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

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

相关文章

了解华为(H3C)网络设备和OSI模型基本概念

目录 一&#xff0c;认识华为 1.华为发展史 2.华为网络设备介绍 3.VRP概述 二&#xff0c;OSI七层模型 1.七层模型详细表格 2.各层的作用 3.数据在各层之间的传递过程 4.OSI四层网络模型 一&#xff0c;认识华为 官网&#xff1a;https://www.huawei.com/cn/ 1.华为发…

记录一个CMD命令异常 文件名、目录名或卷标语法不正确。

由git clone下来导致缺少符号 使用文档格式转换-转为windows-CR LF即可。 当前测试的命令内容 >cs 文件名、目录名或卷标语法不正确。 ho 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 系统默认的nodepad好像不能转换&#xff0c;直接新建一个文件&am…

【GTest学习】

1. GTest简介&#xff1a; GTest 就是 Google Test, 它是一个免费开源的测试框架, 用于编写测试用 C语言编写的程序(C 程序也能用, 但是需要用 C编译器编译)。gtest的官方网站是&#xff1a;http://code.google.com/p/googletest/ 2.GTest下载与环境搭建&#xff1a; GTest 下…

【雕爷学编程】Arduino动手做(195)---HT16k33 矩阵 8*8点阵屏模块4

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

MFC计算分贝

分贝的一种定义是&#xff0c;表示功率量之比的一种单位&#xff0c;等于功率强度之比的常用对数的10倍&#xff1b; 主要用于度量声音强度&#xff0c;常用dB表示&#xff1b; 其计算&#xff0c;摘录网上一段资料&#xff1b; 声音的分贝值可以通过以下公式计算&#xff1…

python爬虫(七)_urllib2:urlerror和httperror

python爬虫(七)_urllib2&#xff1a;urlerror和httperror urllib2的异常错误处理 在我们用urlopen或opener.open方法发出一个请求时&#xff0c;如果urlopen或opener.open不能处理这个response&#xff0c;就产生错误。 这里主要说的是URLError和HTTPError,以及对它们的错误…

Vue Router 的query和params的区别?

区别一&#xff1a; &#xff08;1&#xff09;query相当于get请求&#xff0c;页面跳转的时候可以在地址栏看到请求参数 &#xff08;2&#xff09;params相当于post请求&#xff0c;参数不会在地址栏中显示&#xff0c;所以用params传值相对安全 &#xff08;简记&#xff1…

架构训练营学习笔记:5-1 计算架构模式之多级缓存架构

序 本节主要是计算架构。 多级缓存架构 缓存与缓冲&#xff1a;通常场景是读缓存&#xff0c;写缓冲。 缓存技术的本质&#xff1a;空间换时间&#xff0c;因此缓存架构属于高性能计算 架构。 缓存设计框架 主要考虑存什么&#xff1f;存多久&#xff1f;存哪里&#xff1f;如…

数字图像处理 --- 相机的内参与外参(CV学习笔记)

Pinhole Camera Model&#xff08;针孔相机模型&#xff09; 针孔相机是一种没有镜头、只有一个小光圈的简单相机。 光线穿过光圈并在相机的另一侧呈现倒立的图像。为了建模方便&#xff0c;我们可以把物理成像平面(image plane)上的图像移到实际场景(3D object)和焦点(focal p…

leetcode357周赛

2810. 故障键盘 核心思想&#xff1a;自己想的笨办法&#xff0c;枚举s&#xff0c;然后遇到i就翻转。比较好的方法就是双端队列&#xff0c;遇到i字母原本往后加的就往前加&#xff0c;然后读的时候反过来读&#xff0c;往前加的就往后加&#xff0c;读的话就从前往后&#x…

Java并发系列之八:ThreadPoolExecutor

线程池的意义 在讲解线程池之前&#xff0c;有些读者可能存在这样的疑惑&#xff1a;为什么需要线程池&#xff0c;线程池有什么优越性&#xff1f; 关于这个问题&#xff0c;主要从两个角度来进行解答: 减少开销 在大部分JVM上&#xff0c;用户线程与操作系统内核线程是1:1…

【论文阅读】对抗溯源图主机入侵检测系统的模仿攻击(NDSS-2023)

作者&#xff1a;伊利诺伊大学芝加哥分校-Akul Goyal、Gang Wang、Adam Bates&#xff1b;维克森林大学-Xueyuan Han、 引用&#xff1a;Goyal A, Han X, Wang G, et al. Sometimes, You Aren’t What You Do: Mimicry Attacks against Provenance Graph Host Intrusion Detect…

第一百二十三天学习记录:C++提高:STL-vector容器(下)(黑马教学视频)

vector插入和删除 功能描述&#xff1a; 对vector容器进行插入、删除操作 函数原型&#xff1a; push_back(ele); //尾部插入元素ele pop_back(); //删除最后一个元素 insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele insert(const_iterator pos, int cou…

Arduino 项目笔记 | Arduino LED Memory Game 颜色记忆游戏机

成果展示 颜色记忆游戏机 &#xff5c; Arduino DIY 1. 线路链连接 1.1 原理图 1.2 PCB 免费PCB打样 Arduino LED Memory Game 颜色记忆机资料下载 1.3 烧录 Bootloader 第二部分&#xff1a;Burn bootloader 2. 程序实现 #define NOTE_B0 31 #define NOTE_C1 33 #define NOT…

在Linux上进行项目部署--手动和自动

在Linux上进行项目部署–手动和自动 文章目录 在Linux上进行项目部署--手动和自动1、手动部署项目2、通过Shell脚本自动部署项目 1、手动部署项目 1、在IDEA中开发SpringBoot项目并打成jar包 在idea中的Maven中的package&#xff08;基于Springboot项目&#xff09; 2、将jar包…

React Native连接Zebra斑马打印机通过发送CPCL指令打印(Android 和 iOS通用)

自 2015 年发布以来&#xff0c;React Native 已成为用于构建数千个移动应用程序的流行跨平台移动开发框架之一。通常&#xff0c;我们有开发人员询问如何将 Link-OS SDK 与 React Native 应用程序集成&#xff0c;以便在 Zebra 打印机上打印标签。在本教程中&#xff0c;我们将…

机器视觉赛道持续火热,深眸科技坚持工业AI视觉切入更多应用领域

随着深度学习等算法的突破、算力的不断提升以及海量数据的持续积累&#xff0c;人工智能逐渐从学术界向工业界落地。而机器视觉作为人工智能领域中一个正在快速发展的分支&#xff0c;广泛应用于工业制造的识别、检测、测量、定位等场景&#xff0c;相较于人眼&#xff0c;在精…

系统架构设计高级技能 · 软件可靠性分析与设计(三)【系统架构设计师】

系列文章目录 系统架构设计高级技能 软件架构概念、架构风格、ABSD、架构复用、DSSA&#xff08;一&#xff09;【系统架构设计师】 系统架构设计高级技能 系统质量属性与架构评估&#xff08;二&#xff09;【系统架构设计师】 系统架构设计高级技能 软件可靠性分析与设计…

记录:在 TransnormerLLM 的纯线性注意力(改)中,出现的值异常现象

实验记录 注意&#xff0c;我为了让线性注意力在 fp16-mix 中稳定训练&#xff0c;作为以下修改。 输入线性注意力前&#xff0c;q 和 k 均做了以下操作 q q / torch.norm(q, dim-1, keepdimTrue) k k / torch.norm(k, dim-1, keepdimTrue)把 SRmsNorm 替换为普通的 RmsNo…

QT6 QML CMake工程添加qml到qrc中

参考QT官方文档 前言:使用qt6.2.4 qml的CMake工程时遇到qrc中的qml文件和图片无法使用的情况,查了好久终于找到解决办法,在此记录一下 1. 新建qml.qrc资源文件 可以在目录下看到qrc文件表示新建成功 2.给qrc文件添加前缀 添加完成后如下: 3. 修改CMakeLists.txt 我需要在一个q…