目录

插入的本质是什么?

如何寻找“合法”的位置?—— 模拟查找过程

递归插入(Recursive Insert)—— 优雅的实现

代码逐步完善

总结


上一节我们从第一性原理搞清楚了二叉搜索树(BST)是什么,以及为什么要有它。现在,我们来解决下一个核心问题:如何向一个已有的 BST 中插入一个新元素?

插入的本质是什么?

我们有一个新数字,比如 10,要把它插入到上次创建的这棵树里:

数据结构:二叉搜索树(Binary Search Tree)-CSDN博客

    15/  \8    20/ \
3   12

在动手之前,我们必须牢记 BST 的唯一天条(The Golden Rule):

对于任何节点,其左子树的所有值都比它小,右子树的所有值都比它大。

所以,插入一个新节点,本质上就两个步骤:

  1. 找到一个“合法”的位置:这个位置必须能容纳新节点,并且新节点放进去之后,整棵树依然要满足 BST 的“天条”。

  2. 执行插入动作:把新节点安家落户在这个位置上。


如何寻找“合法”的位置?—— 模拟查找过程

这个寻找位置的过程,其实和我们查找一个数的过程一模一样。我们就像这个新来的数字 10,要在这棵树里找个家。

    15/  \8    20/ \
3   12

从根节点 15 开始问路:

  • 10 小于 15 (10 < 15)。

  • 根据天条,10 的家一定在 15左子树里。我们往左走。

来到节点 8

  • 10 大于 8 (10 > 8)。

  • 根据天条,10 的家一定在 8右子树里。我们往右走。

来到节点 12

  • 10 小于 12 (10 < 12)。

  • 根据天条,10 的家一定在 12左子树里。我们往左走。

12 的左边是什么?

  • NULL!一个空位!

  • 这是一个“死胡同”,我们没法再往下走了。

这个“死胡同”(NULL 指针)就是我们梦寐以求的“合法”位置。为什么?

  • 把它放在这里,它会成为 12 的左孩子。因为 10 < 12,这满足了对于节点 12 的天条。

  • 同时,10 > 810 < 15,它也满足了对于它所有祖先节点的天条。

  • 最重要的是,把它放在一个 NULL 的位置,作为一个新的叶子节点,我们不需要改动树中任何其他节点的现有连接关系,这是成本最低的操作!

 新节点插入的位置,必然是树中某个节点的 NULLleftright 指针所在的位置。寻找这个位置的过程,就是一个模拟查找的过程。


递归插入(Recursive Insert)—— 优雅的实现

现在我们用代码来实现这个逻辑。递归是处理树结构最自然、最优雅的方式。

递归的思想: 我们把一个大问题,分解成一个性质相同、但规模更小的子问题。 对于插入操作:

  • 大问题:在以 root 为根的整棵树中插入 value

  • 子问题

    • 如果 value < root->data,问题就缩小为:在以 root->left 为根的左子树中插入 value

    • 如果 value > root->data,问题就缩小为:在以 root->right 为根的右子树中插入 value

这个分解过程什么时候结束呢?—— 当我们遇到一个 NULL 的节点时,也就是找到了那个“死胡同”,这就是递归的基本情况(Base Case)

代码逐步完善

函数签名和基本情况 (Base Case)

首先,我们需要一个函数。它应该做什么?

  • 它需要知道从哪个节点开始(Node* root)。

  • 它需要知道要插入的值是多少(int value)。

  • 当插入完成后,树的结构可能发生变化(比如,从一棵空树变成有一个节点的树),所以这个函数必须返回新树的根节点指针。

#include <cstddef> // 为了 NULLstruct Node {int data;Node* left;Node* right;Node(int value) {data = value;left = right = NULL;}
};/** 函数功能:向以 root 为根的树中插入 value* 返回值:  返回插入新节点后,树(或子树)的根节点*/
Node* insert(Node* root, int value) {// 基本情况 (Base Case):// 如果当前的 root 是 NULL (无论是整棵树为空,还是我们已经走到了一个“死胡同”)// 这就是我们要插入新节点的地方。if (root == NULL) {// 创建一个新节点,它自己就是一棵子树return new Node(value);}// ... 递归的步骤还没写 ...
}

思考一下 return new Node(value); 这一行。

它返回一个指向新创建节点(值为value)的指针。谁会接收这个指针呢?是上一层的函数调用。我们马上就能看到。

加入递归步骤

现在,如果 root 不是 NULL,我们就需要决定是往左还是往右。

Node* insert(Node* root, int value) {if (root == NULL) {return new Node(value);}// 递归步骤 (Recursive Step):if (value < root->data) {// 新值应该插入到左子树中// 我们递归调用 insert,让它去处理左子树的问题// **关键一步**: 递归调用返回的结果(可能是新的左子树的根)//            必须被连接到当前节点的左指针上!root->left = insert(root->left, value);} else if (value > root->data) {// 新值应该插入到右子树中// 同理,连接到当前节点的右指针上root->right = insert(root->right, value);}// ... 还有一种情况:value == root->data ...// ... 以及,这个函数在递归调用后应该返回什么?...
}

我们来剖析 root->left = insert(root->left, value); 这句代码,它是递归插入的精髓。

假设我们要在节点 12 的左边插入 10

    15/  \8    20/ \
3   12
  • 当前 root12value10

  • 10 < 12,所以执行 root->left = insert(root->left, value);

  • 此时 root->leftNULL。所以 insert(NULL, 10) 被调用。

  • 根据我们的基本情况,insert(NULL, 10)new Node(10) 并返回指向这个新节点的指针。

  • 这个返回的指针,被赋值给了 root->left (也就是 12left 指针)。

  • 效果达成:新节点 10 被成功地连接为了 12 的左孩子。

处理重复值并完成函数

标准的 BST 通常不允许重复值。如果待插入的值和当前节点的值相等,我们什么都不做,直接返回。

最后,如果当前节点不是 NULL,并且完成了对子树的插入操作后,当前节点本身(root)的地址没有变,所以我们要把它返回给它的上层调用,以保持树的连接。

/** 函数功能:向以 root 为根的树中插入 value* 返回值:  返回插入新节点后,树(或子树)的根节点*/
Node* insert(Node* root, int value) {// 基本情况:如果当前节点是 NULL,说明找到了插入位置。if (root == NULL) {// 创建新节点并返回,上一层的调用会把它连接到树中。return new Node(value);}// 递归步骤:if (value < root->data) {// 向左子树递归插入root->left = insert(root->left, value);} else if (value > root->data) {// 向右子树递归插入root->right = insert(root->right, value);}// else (value == root->data) 的情况:// 我们选择什么都不做,因为不允许重复值。// 将当前的(可能未改变的)根节点指针返回给上一层。// 这一步确保了树中未受影响部分的连接保持不变。return root;
}

如何使用这个函数?

主调函数需要一个 Node* 变量来保存整棵树的根。每次插入,都需要用这个变量来接收 insert 函数的返回值,以防根节点发生改变(比如第一次插入时)。

#include <iostream>// (这里是 Node 结构体和 insert 函数的定义)// 为了方便观察,我们写一个中序遍历函数来打印树
void inorderTraversal(Node* root) {if (root == NULL) {return;}inorderTraversal(root->left);std::cout << root->data << " ";inorderTraversal(root->right);
}int main() {Node* root = NULL; // 一开始,树是空的// 第一次插入,root 会从 NULL 变成新节点的地址root = insert(root, 15);root = insert(root, 8);root = insert(root, 20);root = insert(root, 3);root = insert(root, 12);// 现在我们插入新值 10std::cout << "插入 10 之前的中序遍历: ";inorderTraversal(root); // 输出: 3 8 12 15 20 std::cout << std::endl;root = insert(root, 10);std::cout << "插入 10 之后的中序遍历: ";inorderTraversal(root); // 输出: 3 8 10 12 15 20 std::cout << std::endl;// 尝试插入一个重复的值 12root = insert(root, 12);std::cout << "插入重复值 12 之后的中序遍历: ";inorderTraversal(root); // 输出: 3 8 10 12 15 20 (没有变化)std::cout << std::endl;return 0;
}

注意:inorderTraversal 只是为了验证我们插入的正确性,它本身也是递归的一个经典应用。


总结

  • 第一性原理:插入操作必须维护 BST 的“左小右大”核心性质。

  • 推导:最简单、成本最低的插入位置是某个叶子节点的 NULL 孩子位置。这个位置可以通过模拟“查找”过程来找到。

  • 递归实现

    • Base Case (基本情况):当前节点为 NULL。这是递归的终点,也是新节点被创建和“返回”的地方。

    • Recursive Step (递归步骤):比较新值和当前节点的值,决定向左还是向右“委托”任务(递归调用)。

    • 关键连接root->left = insert(root->left, ...) 是实现连接的核心。它用递归调用的返回值(可能是新节点,也可能是原来的子树根)来更新自己的孩子指针。

    • 返回值:函数必须返回根节点指针,以确保调用者能正确地更新树的结构。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读】美 MBSE 方法发展分析及启示(2024)

文章目录 论文摘要 论文框架 1. MBSE 方法概述 2. 美国防部的 MBSE 方法政策要求 在这里插入图片描述 3. 美军兵种的 MBSE 方法政策要求 4. 启示 5.总结 参考文献 论文摘要 本文梳理了美国防部基于模型的系统工程(MBSE)方法的发展历程,并剖析 其技术原理;跟踪《数字工程战略…

人工智能训练师复习题目实操题1.1.1 - 1.1.5

列出所有的python 库和 apiimport pandas as pd import numpy as np就这两个库pandas 库 - apinumpy 库 - apimatplotlib.pyplot - apipd.read_csv()np.where(condition,x,y)fillna(methodffill,inplaceTrue)methodbfill,pd.read_excel()np返回结果 series 对象 data[A列].valu…

旅游管理实训室:旅游教育实践育人的关键支撑

在中等职业教育旅游服务与管理专业教学中&#xff0c;旅游管理实训室并非简单的教学场所&#xff0c;而是落实专业教学标准、实现 “理实一体化” 育人的核心阵地。它通过模拟真实职业场景、配置专业实训设备、设计实践教学活动&#xff0c;将抽象的专业知识转化为具体的操作技…

http工作流程

HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;是互联网中客户端与服务器之间传输超文本&#xff08;如HTML、图片、JSON等&#xff09;的核心协议&#xff0c;基于请求-响应模型和TCP/IP协议族工作。其完整工作流程可拆解为以下9个核心步…

正则表达式实用面试题与代码解析专栏

正则表达式是前端表单验证、字符串匹配的核心工具,简洁高效的正则能大幅提升代码性能。本专栏整理了7道高频面试题,包含核心正则表达式、代码实现及关键知识点解析,帮你快速掌握正则实用技巧。 一、正则基础:核心概念与语法 在学习面试题前,先明确几个高频基础语法,这是…

【数据可视化-89】基孔肯雅热病例数据分析与可视化:Python + pyecharts洞察疫情动态

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

云智智慧停充一体云-allnew全新体验-路内停车源码+路外停车源码+充电桩源码解决方案

采用Java主流的微服务技术栈&#xff0c;基于 Spring Cloud Alibaba 的微服务解决方案进行封装的快速开发平台&#xff0c;包含多种常用开箱即用功能的模块&#xff0c;通用技术组件与服务、微服务治理&#xff0c;具备RBAC功能、网关统一鉴权、Xss防跨站攻击、自动生成前后端代…

利用pypy加速pyxlsbwriter生成xlsb文件

上文介绍了python通过DuckDB和pyxlsbwriter模块生成xlsb文件&#xff0c;因为python是解释执行&#xff0c;它的速度有点慢&#xff0c;pypy是另一种python解释器&#xff0c;它使用即时编译&#xff08;JIT&#xff09;技术来提高执行速度。 因为DuckDB与pypy不兼容&#xff0…

【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略

Spring Boot 集成 MyBatis-Plus 全攻略 1. 为什么选择 MyBatis-Plus 零侵入&#xff1a;在 MyBatis 基础上增强&#xff0c;不影响现有功能。内置 CRUD&#xff1a;无需写 XML/SQL&#xff0c;直接调用 BaseMapper 方法。强大插件&#xff1a;分页插件、性能分析、乐观锁、多租…

LangChain 多任务应用开发

Q: LangChain dify coze是竞品关系 都是AI Agent搭建平台&#xff0c;dify和coze 属于低代码&#xff0c;langChain属于高代码&#xff0c;coze优于dify Q&#xff1a;向量数据库是存储向量&#xff0c;做相似度检索的&#xff0c;可以用faiss milvus chromdb Q&#xff1a;使用…

实用技巧:Oracle中精准查看表占用空间大小

目录实用技巧&#xff1a;Oracle中精准查看表占用空间大小一、为什么需要精准统计表空间占用&#xff1f;二、完整查询SQL&#xff1a;覆盖表、LOB、索引三、SQL语句关键逻辑解析1. 基础表&#xff1a;dba_tables 与 dba_tablespaces2. 子查询1&#xff1a;统计表段空间&#x…

openEuler等Linux系统中如何复制移动硬盘的数据

在 openEuler 系统中,提示 “You should mount volume first” ,意思是需要先挂载移动硬盘的分区才能访问: 安装必要软件(针对特殊文件系统) 如果移动硬盘是 NTFS 等非 Linux 原生支持的文件系统格式,需要安装对应的支持软件,以挂载 NTFS 格式移动硬盘为例,需要安装 …

java如何把字符串数字转换成数字类型

在Java中将字符串数字转换为数字类型有多种方法&#xff0c;以下是详细说明和示例代码&#xff1a; 一、基础转换方法 Integer.parseInt() String str "123"; int num Integer.parseInt(str); // 转换为intDouble.parseDouble() String str "3.14"; dou…

WPFC#超市管理系统(6)订单详情、顾客注册、商品销售排行查询和库存提示、LiveChat报表

WPF&C#超市管理系统10. 订单详情10.1 页面布局10.2 功能实现11. 顾客注册12. 商品销售排行查询与库存提示14. LiveChart报表总结10. 订单详情 10.1 页面布局 页面分三行布置&#xff0c;第一行复用OutstorageView界面的第一行&#xff0c;将属性和命令修改为顾客相关第二…

【Linux】文件基础IO

1.关于文件的共识原理 1.文件内容属性 2.文件分为打开的文件和没打开的文件 3.打开的文件&#xff1a; 文件被打开必须先被加载到内存&#xff0c;所以本质是研究进程和文件的关系&#xff0c;一个进程可以打开多个文件。操作系统内部一定存在大量被打开的文件&#xff0c;要进…

基于微信小程序的生态农产销售管理的设计与实现/基于C#的生态农产销售系统的设计与实现、基于asp.net的农产销售系统的设计与实现

基于微信小程序的生态农产销售管理的设计与实现/基于C#的生态农产销售系统的设计与实现、基于asp.net的农产销售系统的设计与实现

Java研学-SpringCloud(五)

一 Nacos 配置中心 1 引入依赖 – services.pom每个微服务都需要<!--配置中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>2 配置文件 –…

.NET 中的延迟初始化:Lazy<T> 与LazyInitializer

标签&#xff1a;线程安全、延迟初始化、按需初始化、提升启动性能 项目地址&#xff1a;NitasDemo/12Lazy/LazyDemo at main Nita121388/NitasDemo 目录Lazy<T>1. 概念2. 基本用法 3. 异常处理 4. 线程安全模式 5. 示例1. 线程安全模式 (ExecutionAndPublication)2. 发…

【LLIE专题】LLIE低照度图像结构先验提取方法

Zero-Shot Day-Night Domain Adaptation with a Physics Prior&#xff08;ICCV,2021&#xff09;专题介绍一、研究背景二、方法1. 物理反射模型与颜色不变特征的推导&#xff08;原理推导、物理依据&#xff09;2. 颜色不变特征的计算&#xff08;特征计算公式整个过程&#x…

Font Awesome Kit 使用详解

在现代网页设计中&#xff0c;图标是提升用户体验的关键元素。而 Font Awesome 作为最受欢迎的图标库&#xff0c;其最新版本 Font Awesome 7 通过 Kit 功能提供了更便捷高效的集成方式。本文将带你全面了解如何使用 Font Awesome Kit&#xff0c;让你的网站图标管理变得轻松高…