【数据结构】二叉搜索树的功能实现详解

news2024/9/20 20:27:37

文章目录

  • 二叉搜索树
  • 查找
  • 插入
  • 删除
    • 找到要删除的节点
    • 删除节点
      • 1. 要删除节点的左孩子为空
      • 2. 要删除节点的右孩子为空
      • 3. 要删除的节点的左右孩子都不为空
    • 完整代码

二叉搜索树

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树image.png|545

其中序遍历是一颗有序的树

查找

二叉搜索树的查找效率非常高

  • 因为二叉搜索树的左边都比我小,右边都比我大
  • 要找比我小的树,就只需要在左树中找,直接最多可以去掉一半的数据
  • 每次到达一个根节点都可以一次性排除掉最多一半的数据

时间复杂度:

  • 最好情况下: O ( l o g N ) O(logN) O(logN)
  • 最坏情况下: O ( N ) O(N) O(N),单分支,将整棵树遍历完

因为这颗二叉搜索树是由一个一个节点构成的,所以先定义出节点

  • 左孩子
  • 右孩子
  • 节点的值
    并定义出头结点
public class BinarySearchTree {  
  
    static class TreeNode {  
        public int val;  
        public TreeNode left;  
        public TreeNode right;  
  
        public TreeNode(int val) {  
            this.val = val;  
        }   
    }  
    
    public TreeNode root = null;  
}

每次去看一下 curval 和我们要找的 key 的大小关系

  1. 如果 cur.val < key,那么就往右边走
  2. 如果 cur.val > key,那么就往左边走
  3. 如果 cur.val = key,那么就找到了|468
public TreeNode search(int key) {  
    TreeNode cur = root;  
    while(cur != null) {  
        if(cur.val < key) {  
            cur = cur.right;  
        } else if (cur.val > key) {  
            cur = cur.left;  
        }else  
            return cur;  
    }    
    return null;  
}

插入

所有的插入都是插入到了叶子节点

  1. 原来的树为空,则直接插入

  2. 当树不为空时
    若要找到需要插入到的叶子结点的位置,就需要定位到最后父亲节点的叶子结点为 null 的时候。但当 cur 走到叶子结点的时候,就找不到此叶子结点的父亲节点了,所以需要多一个 parent 节点,用来记录当前的父亲节点,好方便随时可以定位到目标叶子结点的父亲节点,后续通过父亲节点进行赋值操作

public void insert(int key) {  
    TreeNode node = new TreeNode(key);  
    TreeNode cur = root;  
    TreeNode parent = null;  
    while (cur != null) {  
        if (cur.val < key) {  
            parent = cur;  
            cur = cur.right;  
        } else if (cur.val > key) {  
            parent = cur;  
            cur = cur.left;  
        } else {  
            return;  
        }    
    }    
	//此循环走完,parent 指向的节点就是需要插入的节点位置的父亲节点  
    if (parent.val > key) {  
        parent.left = node;  
    } else (parent.val < key) {  
        parent.right = node;  
    }
}
  • 值相同的时候,不能进行重复插入
  • while 循环结束,cur 指向要插入的叶子结点,parent 指向需要插入的节点的父亲节点
  • 之后对父亲节点和 key 进行比较,选择插入哪一边

删除

删除包含很多种情况

  1. 需要删除的节点的左孩子为空
  2. 需要删除的节点的右孩子为空
  3. 需要删除的节点的左右孩子都不为空

找到要删除的节点

首先需要找到需要删除的节点

public void remove(int key) {  
    TreeNode cur = root;  
    TreeNode parent = null;  
    while (cur != null) {  
        if(cur.val < key) {  
            parent = cur;  
            cur = cur.right;  
        } else if (cur.val > key) {  
            parent = cur;  
            cur = cur.left;  
        }else {  
            //此时就是找到了要删除的节点  
            removeNode(parent,cur);  
            return;  
        }    
    }
}
  • 当执行到 else 的时候,就是找到要删除的节点了
  • 随后完善删除操作==> removeNode

删除节点

1. 要删除节点的左孩子为空

  1. curroot,则 root = cur.right image.png|214

  1. cur 不是 rootcurparent.left,则 parent.left = cur.rightimage.png|297

  1. cur 不是 rootcurparent.right,则 parent.right = cur.rightimage.png|365
// 1.当要删除的节点 cur 的左孩子为空  
if (cur.left == null) {  
    if (cur == root) {  
        // 1.1 要删除的 cur 为根节点  
        root = cur.right;  
    } else if (cur == parent.left) {  
        // 1.2 要删除的 cur 是 parent 的左节点  
        parent.left = cur.right;  
    } else {  
        // 1.3 要删除的 cur 是 parent 的右节点  
        parent.right = cur.right;  
    }  
}

2. 要删除节点的右孩子为空

  1. curroot,则 root = cur.leftimage.png|208

  1. cur 不是 rootcurparent.left,则 parent.left = cur.leftimage.png|304

  1. cur 不是 rootcurparent.right,则 parent.right = cur.leftimage.png|268
// 2. 要删除的节点 cur 的右孩子为空  
else if (cur.right == null) {  
    if (cur == root) {  
        // 2.1 要删除的 cur 是根节点  
        root = cur.left;  
    } else if (cur == parent.left) {  
        // 2.2 要删除的 cur 是 parent 的左节点  
        parent.left = cur.left;  
    } else {  
        // 2.3 要删除的 cur 是 parent 的右节点  
        parent.right = cur.left;  
    }  
}

3. 要删除的节点的左右孩子都不为空

需要使用 替换法 进行删除

  1. 在它的右子树中寻找一个最小的节点,用它的值填补到被删除节点中,再来处理该结点的删除问题

    • 因为要删除的节点 cur 左边都比它小,右边都比它大,所以就cur 的右边找到一个最小的节点,然后让目标节点覆盖掉 cur
    • 目标节点不会出现左右孩子都存在的情况。要么两边都为空,要么还存在一个右节点。(既然此节点是最小的,就不可能还有左子树,因为左子树肯定比此节点小)image.png
  2. 在它的左子树中寻找一个最大的节点,用它的值填补到被删除节点中,再来处理该结点的删除问题

    • 此时这个最大值一定是在左树的最右边,意味着它肯定没有右子树

所以找到最小值的特征是:

  • 此节点左子树为空,且一定在 cur 右树最左边
  • 此节点右子树为空,且一定在 cur 左树最右边

寻找右子树的最小值

// 3.1 右数的最小值  
TreeNode t = cur.right;  
TreeNode tp = cur;  
while (t.left != null) {  
    tp = t;  
    t = tp.left;  
}  
cur.val = t.val;  
if(t == tp.right) {  
//t 和 tp 在起始步就找到了最小值
    tp.right = t.right;  
}else{  
//t 和 tp 在继续移动的过程中找到最小值
    tp.left = t.right;  
}
  • t 是用来定位最小值的,当 t.left == null 的时候,t 就是最小值
  • tp 是用来定位 t 的父节点的,方便后续对节点进行删除(因为节点的删除都要依靠删除节点的父节点进行“改变连接对象”)
  • 在没找到最小节点之前,tpt 一起进行移动
    1. 最开始 tp 在要删除的节点 cur 的位置,tcur 的右节点(起始步)
    2. tp 走到 t 的位置
    3. t 再走向 tp 的左节点(一轮移动结束)
    4. t.left != nulltp 走到 t 的位置
    5. t 再走向 tp 的左节点(一轮移动结束)
  • 如果在起始步就满足 t.left == null 了,则直接进行

完整代码

public void remove(int key) {  
    TreeNode cur = root;  
    TreeNode parent = null;  
    while (cur != null) {  
        if (cur.val < key) {  
            parent = cur;  
            cur = cur.right;  
        } else if (cur.val > key) {  
            parent = cur;  
            cur = cur.left;  
        } else {  
            //此时就是找到了要删除的节点  
            removeNode(parent, cur);  
            return;  
        }  
    }  
}  
  
private void removeNode(TreeNode parent, TreeNode cur) {  
    // 1.当要删除的节点 cur 的左孩子为空  
    if (cur.left == null) {  
        if (cur == root) {  
            // 1.1 要删除的 cur 为根节点  
            root = cur.right;  
        } else if (cur == parent.left) {  
            // 1.2 要删除的 cur 是 parent 的左节点  
            parent.left = cur.right;  
        } else {  
            // 1.3 要删除的 cur 是 parent 的右节点  
            parent.right = cur.right;  
        }  
  
    // 2. 要删除的节点 cur 的右孩子为空  
    } else if (cur.right == null) {  
        if (cur == root) {  
            // 2.1 要删除的 cur 是根节点  
            root = cur.left;  
        } else if (cur == parent.left) {  
            // 2.2 要删除的 cur 是 parent 的左节点  
            parent.left = cur.left;  
        } else {  
            // 2.3 要删除的 cur 是 parent 的右节点  
            parent.right = cur.left;  
        }  
  
    // 3. 要删除的节点的左右孩子都不为空  
    } else {  
        // 3.1 右数的最小值  
        TreeNode t = cur.right;  
        TreeNode tp = cur;  
        while (t.left != null) {  
            tp = t;  
            t = tp.left;  
        }  
        cur.val = t.val;  
        if(t == tp.right) {  
            //t 和 tp 在起始步就找到了最小值  
            tp.right = t.right;  
        }else{  
            //t 和 tp 在继续移动的过程中找到最小值  
            tp.left = t.right;  
        }  
    }  
}

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

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

相关文章

IOS17双开微信且自动刷新

前提文件 需要在Sideloadly官网下载iTunes和iCloud这2个文件&#xff1a;https://sideloadly.io/&#xff0c; SideStore侧载商店官网下载另外4个文件&#xff1a;https://sidestore.io/ 如图&#xff0c;如果文件无法下载(需要科学上网)可以下载我的资源包尝试 安装步骤 …

C语言学习笔记 Day15(文件管理--下)

Day15 内容梳理&#xff1a; 目录 C语言学习笔记 Day14&#xff08;文件管理--上&#xff09;-CSDN博客 Chapter 10 文件操作 10.2 文件加密、解密 &#xff08;1&#xff09;文件加密 &#xff08;2&#xff09;文件解密 10.3 文件读取、写入 &#xff08;1&#xff…

可视化数据大屏设计案例

可视化数据大屏开发-能源调度大屏展示 可视化数据大屏开发-房地产经营分析 可视化大屏开发-预售业务数据看板 可视化大屏开发-城市路况监测大屏

深入Redis:细谈持久化

Redis的数据是保存在内存中的&#xff0c;内存里面的数据是不持久的&#xff0c;要想做到持久化&#xff0c;必须要把在内存中的数据储存到硬盘上。 Redis速度非常快&#xff0c;数据只有在内存中才有这样的速度&#xff0c;但是为了持久&#xff0c;数据还是要想办法保存到硬…

【分治——归并排序】排序数组的归并方法

目录 1.前言2.题目简介3.求解思路4.示例代码 1.前言 今天简单展示一个归并排序解题&#xff0c;难度简单。 2.题目简介 题目链接&#xff1a;LINK 3.求解思路 4.示例代码 //归并排序 class Solution { public:vector<int> tmp;vector<int> sortArray(vector&…

【Web开发工具】基于Windows系统下的WebStorm安装教程

目录 安装WebStorm1.官网下载WebStorm2.选择安装位置3.关闭应用 下载并解压工具下载地址解压执行Done输入Code 安装WebStorm 1.官网下载WebStorm 我自己的网盘链接: https://pan.baidu.com/s/17PJu8Kf_fdX3gi588ubTqQ?pwd6666 2.选择安装位置 下一步下一步就行&#xff0c…

有手就会之使用Dify构建RAG聊天应用(基于私有知识库和搜索引擎)

之前我的文章里写的是通过langchain来构建RAG应用&#xff0c;对于很多人来说。langchain作为一个框架上手难度大&#xff0c;代码不够直观。但是通过dify你将学会可视化搭建工作流。 什么是dify&#xff1f; Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即…

如何判断B端真实需求?5大经验技巧

准确判断B端真实需求能确保软件开发项目紧贴用户实际需求&#xff0c;减少返工&#xff0c;加快开发速度&#xff0c;提升客户满意度和产品竞争力。如果没有采用有效的技巧和经验来判断B端的真实需求&#xff0c;可能会导致软件开发项目偏离客户实际需求&#xff0c;引发频繁的…

【数据分析预备】Numpy入门

Jupyter Notebook 是一个基于网页的交互式计算环境编写代码、运行代码、查看输出、可视化数据、分享报告文档按单元格运行代码可展示的信息格式更丰富&#xff08;支持Markdown和Latex)交互式运行环境 安装 cmd窗口 pip install notebook启动 jupyter notebook退出 CtrlC …

阿里巴巴alibaba商品采集API获取商品列表商品详情接口

在跨境电商和B2B贸易日益繁荣的今天&#xff0c;如何高效、准确地获取商品信息成为了商家们关注的焦点。阿里巴巴作为全球领先的B2B电商平台&#xff0c;凭借其庞大的商品数据库和强大的技术支持&#xff0c;为商家们提供了丰富的API接口服务&#xff0c;其中alibaba.item_get接…

第十三节:学习Springboot整合mybatis——完整篇(自学Spring boot 3.x的第三天)

大家好&#xff0c;今天记录下学习springboot的第三天。​网创有方 这节详细记录了如何使用springboot整合mybatis方法&#xff0c;并成功实现将请求信息插入本地的mysql数据库。 由于只是为了方便本地验证&#xff0c;实际项目是需要部署到服务器上去的&#xff0c;所以就采用…

MQ专题:消费幂等性

一、提要 1.1 通过本文将获得 消息投递的通用代码 非事务消息的投递事务消息的投递任意延迟消息的投递&#xff0c;不依赖于任何MQ上面这些投递都支持批量的方式投递失败自动重试的代码 幂等消费的通用代码消费失败&#xff0c;衰减式自动重试的通用代码 1.2本文涉及到的主要…

新一代网络研发利器——开物™,让用户每一行代码都贡献在核心创新

随着云计算、人工智能、大数据的快速发展&#xff0c;现有的IT系统越来越复杂&#xff0c;传统网络技术难以满足新的业务需求&#xff0c;DPU技术开始崭露头角&#xff0c;越来越多的企业与研究机构认识到高性能网络处理技术带来的巨大价值&#xff0c;并积极开展创新与实践&am…

985硕闭着眼都有15k以上的月薪

985 闭眼 今天在牛客上看到一篇直呼好家伙的帖子&#xff1a; 这位同学指出&#xff1a;论坛里个个 985 的硕士闭着眼睛都有 15k 以上的月薪&#xff0c;还天天嚷嚷着研究生白读了&#xff0c;天天嚷嚷着反向读研了 ... 通常这样的帖子&#xff0c;都会被评论区喷成筛子。 结果…

微积分复习笔记 Calculus Volume 1 - 1.3Trigonometric Functions

1.3 Trigonometric Functions - Calculus Volume 1 | OpenStax

ELK日志服务收集SpringBoot日志案例

第一步&#xff1a;准备docker-compose文件 首先准备 docker-compose.yaml 文件 version: "3" services:elasticsearch:image: elasticsearch:7.17.2container_name: elasticsearch-servernetworks:- hostenvironment:- "cluster.nameelasticsearch" #设…

NeRF原理学习

一个2020年的工作我现在才来学习并总结它的原理&#xff0c;颇有种“时过境迁”的感觉。这篇总结是基于NeRF原文 NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis 阅读理解后写的&#xff0c;作用是以后如果记不太清了可以回忆。 目的&应用 先说…

80.动态申请内存

目录 一.malloc函数 二.其他注意事项 三.视频教程 在定义变量的时候会在内存中申请空间。除了在定义变量的时候申请内存空间&#xff0c;也可以使用库函数动态申请内存&#xff0c; 一.malloc函数 作用&#xff1a;动态申请一块连续的任意尺寸的内存空间。 函数原型&…

【前端9】手风琴v1.0版本:使用插槽实现动态内容插入的Vue组件

【前端9】手风琴&#xff1a;使用插槽实现动态内容插入的Vue组件 写在最前面一、插槽的基本概念1.默认插槽2.具名插槽 二、实现一个折叠面板组件0.关键点和注意事项1.父组件 App.vue2.子组件 Collapse.vue总结 3.详细解读&#xff08;可以略过&#xff09;父组件子组件 三、小结…

通用后台管理系统实战演示(Vue3 + element-plus)汇总篇三

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…