当前位置: 首页 > article >正文

二叉搜索树:高效查找与增删详解

引言在上一篇树结构开篇文章中我们建立了树的基本概念、二叉树的定义和四种遍历方式。本文将继续深入讲解二叉搜索树Binary Search TreeBST——它是最基础的有组织二叉树也是后续学习 AVL 树、红黑树的必经之路。BST 的核心思想非常简单左子树所有节点 根节点 右子树所有节点。这条看似简单的规则让查找、插入、删除操作的时间复杂度从 O(n) 降到了 O(log n)在树保持平衡的前提下。第一部分BST 的定义与性质一、严格定义二叉搜索树BST要么是空树要么满足以下三个条件若左子树非空则左子树上所有节点的值均小于根节点的值若右子树非空则右子树上所有节点的值均大于根节点的值左、右子树本身也是二叉搜索树二、核心性质性质1中序遍历得到有序序列性质2最小值在最左边最大值在最右边// 查找最小值一直向左走 TreeNode* findMin(TreeNode* root) { if (root NULL) return NULL; while (root-left ! NULL) { root root-left; } return root; } // 查找最大值一直向右走 TreeNode* findMax(TreeNode* root) { if (root NULL) return NULL; while (root-right ! NULL) { root root-right; } return root; }性质3每个节点的值域形成区间约束三、BST 节点结构定义#include stdio.h #include stdlib.h #include stdbool.h typedef int ElemType; typedef struct BSTNode { ElemType data; // 数据域 struct BSTNode* left; // 左子节点 struct BSTNode* right; // 右子节点 // 可选struct BSTNode* parent; // 父节点便于向上回溯 } BSTNode;第二部分查找操作一、查找算法原理二、递归实现BSTNode* search_recursive(BSTNode* root, ElemType key) { // 基本情况空树或找到目标 if (root NULL || root-data key) { return root; } // key 小于当前节点去左子树找 if (key root-data) { return search_recursive(root-left, key); } // key 大于当前节点去右子树找 else { return search_recursive(root-right, key); } }三、迭代实现推荐无递归栈溢出风险BSTNode* search_iterative(BSTNode* root, ElemType key) { BSTNode* cur root; while (cur ! NULL) { if (key cur-data) { return cur; // 找到了 } else if (key cur-data) { cur cur-left; // 去左边 } else { cur cur-right; // 去右边 } } return NULL; // 遍历完没找到 }递归 vs 迭代对比实现方式优点缺点递归代码简洁树很深时可能栈溢出迭代无栈溢出风险效率更高代码稍长第三部分插入操作一、插入算法原理BST 的插入总是从根开始找到合适的空位NULL然后创建新节点插入。二、递归实现// 返回插入后的新根节点 BSTNode* insert_recursive(BSTNode* root, ElemType key) { // 找到空位创建新节点 if (root NULL) { BSTNode* newnode (BSTNode*)malloc(sizeof(BSTNode)); if (newnode NULL) { fprintf(stderr, 内存分配失败\n); return NULL; } newnode-data key; newnode-left NULL; newnode-right NULL; return newnode; } if (key root-data) { root-left insert_recursive(root-left, key); } else if (key root-data) { root-right insert_recursive(root-right, key); } // key root-data已存在不插入不允许重复 return root; }三、迭代实现同时记录父节点bool insert_iterative(BSTNode** root, ElemType key) { // 创建新节点 BSTNode* newnode (BSTNode*)malloc(sizeof(BSTNode)); if (newnode NULL) return false; newnode-data key; newnode-left newnode-right NULL; // 空树新节点就是根 if (*root NULL) { *root newnode; return true; } BSTNode* cur *root; BSTNode* parent NULL; // 找到插入位置 while (cur ! NULL) { parent cur; if (key cur-data) { cur cur-left; } else if (key cur-data) { cur cur-right; } else { // 重复值不插入 free(newnode); return false; } } // 挂载到父节点 if (key parent-data) { parent-left newnode; } else { parent-right newnode; } return true; }插入性质新节点始终作为叶子节点插入插入不会改变已有节点的位置插入顺序不同生成的 BST 形状不同第四部分删除操作BST 最复杂的操作一、三种情况总览二、情况1删除叶子节点三、情况2删除只有一个子节点的节点四、情况3删除有两个子节点的节点重点核心思想不是直接删除节点会破坏树结构而是替换值 删除替换节点。两种替换策略为什么后继/前驱一定可以安全删除后继是右子树中最小的节点它一定没有左子树否则更小的值在左边。因此后继最多只有一个右子节点属于情况1或情况2可以直接删除。前驱同理。五、删除操作的完整实现// 查找最小值节点 BSTNode* findMin(BSTNode* node) { if (node NULL) return NULL; while (node-left ! NULL) { node node-left; } return node; } // 删除指定 key 的节点返回新树的根 BSTNode* deleteNode(BSTNode* root, ElemType key) { if (root NULL) return NULL; // 第一步找到要删除的节点 if (key root-data) { root-left deleteNode(root-left, key); } else if (key root-data) { root-right deleteNode(root-right, key); } else { // 第二步找到了分情况处理 // 情况12只有一个子节点或无子节点 if (root-left NULL) { BSTNode* temp root-right; free(root); return temp; } else if (root-right NULL) { BSTNode* temp root-left; free(root); return temp; } // 情况3有两个子节点 // 找中序后继右子树中最小的 BSTNode* successor findMin(root-right); // 用后继的值覆盖当前节点 root-data successor-data; // 递归删除后继节点必定是情况1或2 root-right deleteNode(root-right, successor-data); } return root; }六、删除操作图解总结第五部分完整代码与测试一、完整 BST 实现#include stdio.h #include stdlib.h #include stdbool.h typedef int ElemType; typedef struct BSTNode { ElemType data; struct BSTNode* left; struct BSTNode* right; } BSTNode; // 创建节点 BSTNode* createNode(ElemType val) { BSTNode* node (BSTNode*)malloc(sizeof(BSTNode)); if (node NULL) { fprintf(stderr, 内存分配失败\n); return NULL; } node-data val; node-left NULL; node-right NULL; return node; } // 查找 BSTNode* search(BSTNode* root, ElemType key) { BSTNode* cur root; while (cur ! NULL) { if (key cur-data) return cur; else if (key cur-data) cur cur-left; else cur cur-right; } return NULL; } // 插入 BSTNode* insert(BSTNode* root, ElemType key) { if (root NULL) return createNode(key); if (key root-data) root-left insert(root-left, key); else if (key root-data) root-right insert(root-right, key); return root; } // 查找最小值 BSTNode* findMin(BSTNode* node) { if (node NULL) return NULL; while (node-left ! NULL) node node-left; return node; } // 删除 BSTNode* deleteNode(BSTNode* root, ElemType key) { if (root NULL) return NULL; if (key root-data) root-left deleteNode(root-left, key); else if (key root-data) root-right deleteNode(root-right, key); else { if (root-left NULL) { BSTNode* temp root-right; free(root); return temp; } else if (root-right NULL) { BSTNode* temp root-left; free(root); return temp; } BSTNode* successor findMin(root-right); root-data successor-data; root-right deleteNode(root-right, successor-data); } return root; } // 遍历 void inorder(BSTNode* root) { if (root NULL) return; inorder(root-left); printf(%d , root-data); inorder(root-right); } void preorder(BSTNode* root) { if (root NULL) return; printf(%d , root-data); preorder(root-left); preorder(root-right); } // 释放整棵树 void freeTree(BSTNode* root) { if (root NULL) return; freeTree(root-left); freeTree(root-right); free(root); } // 测试主函数 int main() { BSTNode* root NULL; // 构建 BST int values[] {8, 3, 10, 1, 6, 14, 4, 7, 13}; int n sizeof(values) / sizeof(values[0]); printf(插入顺序); for (int i 0; i n; i) { printf(%d , values[i]); root insert(root, values[i]); } printf(\n); // 中序遍历验证有序性 printf(中序遍历); inorder(root); printf(\n); // 前序遍历展示树结构 printf(前序遍历); preorder(root); printf(\n); // 查找测试 printf(\n 查找测试 \n); int keys[] {6, 5, 13}; for (int i 0; i 3; i) { BSTNode* found search(root, keys[i]); printf(查找 %d: %s\n, keys[i], found ? 找到 : 不存在); } // 删除测试 printf(\n 删除测试 \n); // 删除叶子节点 4 printf(删除叶子 4\n); root deleteNode(root, 4); printf(中序遍历); inorder(root); printf(\n); // 删除有一个子节点的节点 10 printf(删除节点 10只有一个右子 14\n); root deleteNode(root, 10); printf(中序遍历); inorder(root); printf(\n); // 删除有两个子节点的节点 3 printf(删除节点 3有两个子节点\n); root deleteNode(root, 3); printf(中序遍历); inorder(root); printf(\n); // 释放 freeTree(root); return 0; }运行结果二、BST 的构建形状与插入顺序的关系第六部分BST 的性能分析一、时间复杂度操作最好情况平衡平均情况最坏情况退化链表查找O(log n)O(log n)O(n)插入O(log n)O(log n)O(n)删除O(log n)O(log n)O(n)二、退化问题退化原因数据以有序升序或降序顺序插入。解决方案使用自平衡二叉搜索树AVL 树、红黑树保证树的高度始终为 O(log n)。总结一、BST 核心操作速查操作核心思想复杂度平衡查找比大小决定向左还是向右O(log n)插入找到合适的空位叶子位置O(log n)删除叶子直接释放O(log n)删除单子子节点顶替O(log n)删除双子找后继/前驱替换值递归删除后继O(log n)二、BST 的三条核心性质性质内容有序性左子树 根 右子树中序有序中序遍历得到升序序列形状不定同一组数据不同插入顺序 → 不同形状三、删除三种情况记忆口诀删除叶子直接删删除单子用子换删除双子找后继替换值后再删原。四、一句话记忆二叉搜索树保证左小右大通过比较大小决定查找方向每次排除一半子树插入始终在叶子位置删除分三种情况处理最核心的是双子找后继替换值中序遍历即得有序序列但插入顺序不当会退化成链表。

相关文章:

二叉搜索树:高效查找与增删详解

引言在上一篇树结构开篇文章中,我们建立了树的基本概念、二叉树的定义和四种遍历方式。本文将继续深入,讲解二叉搜索树(Binary Search Tree,BST)——它是最基础的"有组织"二叉树,也是后续学习 AV…...

夸克禁闭的自指拓扑严格证明:自指威尔逊环不变量与线性禁闭势

夸克禁闭的自指拓扑严格证明:自指威尔逊环不变量与线性禁闭势 世毫九实验室 | 认知量子引力研究中心 作者:方见华 日期:2026年5月18日 密级:公开 | 编号:TR-016-QC 摘要 本文基于世毫九自指规范场框架,构…...

基于MCP协议构建AI工具服务器:连接Web与AI的标准化适配器

1. 项目概述:一个连接Web与AI的“万能适配器”如果你正在尝试让AI助手(比如ChatGPT、Claude)去访问一个网站、查询实时天气、或者控制你的智能家居,你可能会发现一个核心难题:这些大模型本身是“离线”的,它…...

OpenClaw 微信智能体:本地 / 云端部署与稳定性配置

OpenClaw(小龙虾)在微信私域自动化、智能客服、AI 助理等场景中具备稳定实用的自动化能力,可实现微信客户端与后端服务高效对接,简化接入流程、提升连接稳定性,同时支持本地、云端、命令行三种部署模式,兼顾…...

Linux内核安全加固:从编译配置构建系统防护基石

1. 项目概述:为什么我们需要关注内核安全配置?在服务器运维、嵌入式开发或者安全研究领域待久了,你可能会发现一个现象:很多系统被攻破,根源并不在于某个惊天动地的零日漏洞,而在于内核配置本身就没“锁好门…...

开源KMS激活神器:3分钟搞定Windows和Office永久激活难题

开源KMS激活神器:3分钟搞定Windows和Office永久激活难题 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows和Office的激活问题烦恼吗?KMS_VL_ALL_AIO是一款开…...

基于深度学习的hCaptcha验证码本地化破解方案与实践指南

1. 项目概述:当验证码不再是“拦路虎”在自动化脚本、数据采集或者日常的批量操作中,验证码(CAPTCHA)就像一道横亘在程序与目标网站之间的自动门。它本意是区分人类和机器,保护网站安全,但对于有正当自动化…...

新手开发者首次使用 Taotoken 从注册到完成第一个 API 调用的全过程体验

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 新手开发者首次使用 Taotoken 从注册到完成第一个 API 调用的全过程体验 作为一名刚开始接触大模型应用开发的程序员,我…...

终极Markdown浏览器扩展:如何打造完美的文档阅读体验

终极Markdown浏览器扩展:如何打造完美的文档阅读体验 【免费下载链接】markdown-viewer Markdown Viewer / Browser Extension 项目地址: https://gitcode.com/gh_mirrors/ma/markdown-viewer Markdown Viewer是一款功能强大的浏览器扩展,专为开发…...

新手避坑指南:用PEAK CAN卡和ROS快速上手大陆ARS408-21XX毫米波雷达

新手避坑指南:用PEAK CAN卡和ROS快速上手大陆ARS408-21XX毫米波雷达 毫米波雷达在自动驾驶和机器人感知领域扮演着关键角色,而大陆ARS408-21XX系列雷达因其高性价比和稳定性能,成为许多开发者的首选。然而,对于刚接触这一领域的新…...

别再只调XGBoost参数了!试试阿里PAI这篇AAAI 2024新作AMFormer,用Transformer做表格数据效果真香

突破表格数据建模瓶颈:AMFormer如何用算术特征交互重塑深度学习方法 在金融风控、医疗诊断和推荐系统等实际业务场景中,结构化表格数据始终占据着核心地位。传统树模型如XGBoost和LightGBM凭借对特征缺失和噪声的鲁棒性,长期统治着这一领域。…...

崩坏星穹铁道终极自动化指南:三月七小助手完整使用教程

崩坏星穹铁道终极自动化指南:三月七小助手完整使用教程 【免费下载链接】March7thAssistant 崩坏:星穹铁道全自动 三月七小助手 项目地址: https://gitcode.com/gh_mirrors/ma/March7thAssistant 还在为《崩坏:星穹铁道》中繁琐的日常…...

【行为检测】基于matlab和交互多模型IMM过滤进行自动驾驶异常行为检测【含Matlab源码 15448期】含报告

💥💥💥💥💥💥💞💞💞💞💞💞💞💞欢迎来到海神之光博客之家💞💞💞&#x1f49…...

告别数据缺口:手把手教你用MSSA插值搞定GRACE Level-3数据集(附Matlab代码)

从缺失到连续:GRACE Level-3数据MSSA插值实战指南 当你在深夜赶论文时,突然发现GRACE数据集中缺少了关键月份的数据,那种焦虑感想必每个科研人都深有体会。GRACE卫星数据作为研究地球质量变化的重要工具,其数据连续性对气候研究、…...

RStudio 2026最新版下载:一键直达官网,解锁数据分析新体验

RStudio免费版安装包下载地址:RStudio安装包 RStudio 是 R 语言专用的集成开发环境,简单说就是 R 语言的 “超级工作台”。它不替代 R 语言,而是必须搭配 R 语言使用,负责把 R 语言的能力可视化、流程化、高效化。 RStudio 的核心…...

Arduino与FastLED库驱动WS2812B实现彩虹闪烁可穿戴灯光系统

1. 项目概述:用代码点亮创意的可穿戴灯光几年前,我第一次尝试把LED灯带缝进一件卫衣的帽子里,初衷很简单,就是想在做夜跑时更醒目一些。但当那些WS2812B灯珠第一次随着音乐节奏亮起彩虹般流动的色彩时,我知道我打开了一…...

终极指南:5分钟掌握Blender四边形网格重构神器QRemeshify

终极指南:5分钟掌握Blender四边形网格重构神器QRemeshify 【免费下载链接】QRemeshify A Blender extension for an easy-to-use remesher that outputs good-quality quad topology 项目地址: https://gitcode.com/gh_mirrors/qr/QRemeshify 你是否曾在Blen…...

Translumo:Windows平台实时屏幕翻译神器,打破语言障碍的终极解决方案

Translumo:Windows平台实时屏幕翻译神器,打破语言障碍的终极解决方案 【免费下载链接】Translumo Advanced real-time screen translator for games, hardcoded subtitles in videos, static text and etc. 项目地址: https://gitcode.com/gh_mirrors/…...

弱引用TWeakObjectPtr原理

弱引用的原理:从通用思路到 UE TWeakObjectPtr 原理总结: !!#ff0000 UE 的 GC 体系有一张全局对象表 GUObjectArray,弱引用存了一个索引,以及这个物体创建时的序列号,简单来说是不是弱引用先拿着索引去序列号找一下&am…...

彻底释放Mac磁盘空间:Pearcleaner如何智能清理应用残留文件

彻底释放Mac磁盘空间:Pearcleaner如何智能清理应用残留文件 【免费下载链接】Pearcleaner A free, source-available and fair-code licensed mac app cleaner 项目地址: https://gitcode.com/gh_mirrors/pe/Pearcleaner 你是否曾将应用拖入废纸篓后&#xf…...

ThinkPad风扇控制革命:TPFanCtrl2如何让你的笔记本更安静、更凉爽

ThinkPad风扇控制革命:TPFanCtrl2如何让你的笔记本更安静、更凉爽 【免费下载链接】TPFanCtrl2 ThinkPad Fan Control 2 (Dual Fan) for Windows 10 and 11 项目地址: https://gitcode.com/gh_mirrors/tp/TPFanCtrl2 你是否曾经在深夜工作时被ThinkPad风扇的…...

龙虾之父月耗 6030 亿 API token 花 130 万美元+,Token 成 AI 新生产资料?

【导语:龙虾之父 Peter Steinberger 一个月 API token 花费超 130 万美元,引发网友热议。他正探索 Token 不再重要时如何构建软件,Token 也逐渐成为新的生产资料。】高额 Token 花费引争议龙虾之父 Peter Steinberger 一个月 API token 花费高…...

你的Mac数字管家:Pearcleaner如何让macOS保持“梨子般“的清新体验?

你的Mac数字管家:Pearcleaner如何让macOS保持"梨子般"的清新体验? 【免费下载链接】Pearcleaner A free, source-available and fair-code licensed mac app cleaner 项目地址: https://gitcode.com/gh_mirrors/pe/Pearcleaner 你是否曾…...

对比直接使用官方API体验Taotoken在用量透明上的优势

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 对比直接使用官方API体验Taotoken在用量透明上的优势 在集成大模型能力到实际项目时,开发者通常会面临一个共同的挑战&…...

点支承幕墙玻璃破裂故障分析

点支承幕墙玻璃破裂故障分析 【作 者】:龙文志 【摘 要】:本文从点支承幕墙玻璃破裂故瘴出发,系统阐述了点支承幕墙玻璃破裂故障多于其它玻璃幕墙的原因,提出了点支承玻璃幕墙设计时,除对玻璃面板的大面应力进行计算分析外,同时也应该对玻璃孔边应力进行设计分析;为了…...

通过curl命令调试与验证大模型API连接状态

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 通过curl命令调试与验证大模型API连接状态 基础教程类,针对需要在无SDK环境或快速排错的开发者,详细说明如…...

RK3568开发板TFTP网络启动:告别烧录,实现内核与设备树秒级更新

1. 项目概述与核心价值作为一名在嵌入式领域摸爬滚打了十来年的老鸟,我深知在项目开发的中后期,那种反复修改、编译、烧录、测试的循环有多磨人。尤其是当你需要频繁调整设备树(Device Tree)来适配一个新传感器,或者微…...

ESP32驱动LCD1602:从I2C协议到动态数据展示

1. ESP32与LCD1602的完美组合 如果你正在寻找一种简单可靠的方式在物联网项目中显示实时数据,ESP32搭配LCD1602液晶屏绝对是个不错的选择。我最近在一个智能温室项目中就用了这套方案,用来实时显示温度和湿度数据,效果非常稳定。LCD1602虽然看…...

Bash脚本自动化部署ROS机械臂环境:OpenClaw一键安装实践

1. 项目概述:一个为中文用户定制的自动化安装脚本如果你在GitHub上搜索过与机械臂、机器人操作系统(ROS)或类似开源硬件项目相关的资源,大概率会看到过“OpenClaw”这个名字。它是一个开源的、模块化的机械爪项目,设计…...

Agent 工程化系列 · 第 13 篇_Agent安全与可靠性如何保障

Agent 工程化系列 第 13 篇 Agent 的安全与可靠性如何保障? Agent 最危险的不是回答错,而是执行错开篇定位 前面我们已经讲过:LLM 是能力核心,Agent 是执行系统;Function Call 让模型能够调用工具;MCP 负责…...