数据之林的守护者:二叉搜索树的诗意旅程

文章目录

  • 前言
  • 一. 二叉搜索树的概念
    • 1.1 二叉搜索树的定义
      • 1.1.1 为什么使用二叉搜索树?
  • 二. 二叉搜索树的性能分析
    • 2.1 最佳与最差情况
      • 2.1.1 最佳情况
      • 2.1.2 最差情况
    • 2.2 平衡树的优势
  • 三.二叉搜索树的基本操作实现
      • 3.1.1 详细示例
      • 3.1.2 循环实现插入操作
        • 3.1.2.1 逻辑解析:
    • 3.2 查找操作详解
      • 3.2.1 详细示例
      • 3.2.2 循环实现查找操作
        • 3.2.2.1 逻辑解析:
    • 3.3 删除操作详解
      • 3.3.1 详细示例
      • 3.3.2 循环实现删除操作
        • 3.3.2.1 逻辑解析:
    • 3.4 遍历操作详解
      • 3.4.1 中序遍历
        • 3.4.1.1 示例代码
        • 3.4.1.2 逻辑解析:
      • 3.4.2 前序遍历
        • 3.4.2.1 示例代码
        • 3.4.2.2 逻辑解析:
      • 3.4.3 后序遍历
        • 3.4.3.1 示例代码
        • 3.4.3.2 逻辑解析:
  • 小结

在这里插入图片描述

前言

在数据结构的世界里,若说哈希表是快如闪电的追风者,图是联结万物的纽带,那么二叉搜索树(Binary Search
Tree,BST)便是这片数据森林中最古老的守护者。它以平衡与优雅为本,根深叶茂,庇护着无数程序的逻辑之根。

今天,让我们步入这片古老而神秘的森林,探寻二叉搜索树的内涵与美学,感受它在算法中演奏的优雅旋律。

一. 二叉搜索树的概念

1.1 二叉搜索树的定义

二叉搜索树是一种特殊的二叉树,其具有以下特性:

节点的左子树:所有节点的值小于或等于该节点的值。
节点的右子树:所有节点的值大于该节点的值。
每个节点的左右子树也都是二叉搜索树。

在这里插入图片描述
这种结构确保了我们可以有效地进行查找、插入和删除操作。

1.1.1 为什么使用二叉搜索树?

  • 快速查找:由于节点的结构特性,查找操作可以在平均O(log N)时间内完成。
  • 动态数据支持:允许动态插入和删除数据,能够应对频繁变化的数据集。
  • 有序性:通过中序遍历,我们能够得到一个升序的序列,这对于某些算法(如排序)非常有用。

二. 二叉搜索树的性能分析

2.1 最佳与最差情况

2.1.1 最佳情况

完全二叉树:

  • 当树为完全平衡时,查找、插入和删除的时间复杂度均为O(log N)。例如,若插入的顺序是随机的,树可能较为平衡,此时查找、插入和删除的时间复杂度均为O(log N)。
    在这里插入图片描述

2.1.2 最差情况

  • 退化成链表的情况:
    如果数据以有序方式插入(例如:1, 2, 3, …),二叉搜索树将退化为链表,导致每次操作都需遍历整个链表,此时时间复杂度变为O(N)

2.2 平衡树的优势

为了避免最坏情况的发生,平衡二叉树(如AVL树和红黑树)引入了旋转操作,确保在插入和删除时树的高度保持平衡。这样,在任何情况下,操作的时间复杂度均保持在O(log N)。

  • 自平衡机制:通过旋转和重组树的结构,动态维护树的高度,使其尽可能接近O(log N)的状态。

三.二叉搜索树的基本操作实现

3.1 插入操作详解
插入操作是构建二叉搜索树的基本步骤之一。其主要流程如下:

  • 判断树是否为空:
    如果树为空,将新节点设为根节点。这是构建树的第一步。
  • 比较并递归插入:
    从根节点开始,根据节点值的大小决定向左子树还是右子树移动。
  • 找到合适位置后插入:
    当找到一个空位后,将新节点插入。
    在这里插入图片描述
    在这里插入图片描述

3.1.1 详细示例

让我们一步一步实现插入操作:

定义节点结构:

template<class K>
class BSTNode {
public:K _key; // 存储节点的值BSTNode<K>* _left; // 左子节点BSTNode<K>* _right; // 右子节点BSTNode(const K& key) : _key(key), _left(nullptr), _right(nullptr) {}
};
  • 解释:每个节点包含一个值和两个指向左右子节点的指针。使用模板类使得节点能够存储不同类型的数据。

定义树结构:

template<class K>
class BSTree {
private:BSTNode<K>* _root; // 根节点public:BSTree() : _root(nullptr) {} // 初始化树为空bool Insert(const K& key) {if (_root == nullptr) { // 树为空_root = new BSTNode<K>(key); // 新建根节点return true;}return _InsertRec(_root, key); // 从根节点开始插入}private:bool _InsertRec(BSTNode<K>* node, const K& key) {if (key < node->_key) { // 插入值小于当前节点if (node->_left == nullptr) { // 左子节点为空node->_left = new BSTNode<K>(key); // 创建新节点return true;}return _InsertRec(node->_left, key); // 递归插入} else if (key > node->_key) { // 插入值大于当前节点if (node->_right == nullptr) { // 右子节点为空node->_right = new BSTNode<K>(key); // 创建新节点return true;}return _InsertRec(node->_right, key); // 递归插入}return false; // 处理相等值的逻辑}
};

插入逻辑解析:

  • 首先检查树是否为空,若为空,则直接将新节点设为根节点。
  • 如果不为空,通过比较当前节点的值与要插入值的大小,决定向左或向右移动。
  • 当找到合适的空位时,插入新节点。
  • 如果当前值与要插入值相等,可以选择不插入,或者进行其他处理。

3.1.2 循环实现插入操作

除了递归方式,插入操作也可以用循环实现。以下是使用循环方式的示例代码:

bool InsertIterative(const K& key) {if (_root == nullptr) { // 树为空_root = new BSTNode<K>(key); // 新建根节点return true;}BSTNode<K>* current = _root;BSTNode<K>* parent = nullptr;while (current != nullptr) {parent = current; // 记录父节点if (key < current->_key) {current = current->_left; // 移动到左子节点} else if (key > current->_key) {current = current->_right; // 移动到右子节点} else {return false; // 找到相等值,处理逻辑}}// 根据比较结果将新节点连接到父节点if (key < parent->_key) {parent->_left = new BSTNode<K>(key); // 插入左子节点} else {parent->_right = new BSTNode<K>(key); // 插入右子节点}return true;
}
3.1.2.1 逻辑解析:
  • 循环控制:使用while循环遍历树,直到找到合适的空位插入新节点。
  • 记录父节点:通过记录当前节点的父节点,以便在找到合适位置后,将新节点正确连接。

3.2 查找操作详解

查找操作使我们能够确认一个值是否存在于树中。其步骤如下:

  • 从根节点开始比较:
    判断目标值与当前节点的值大小关系。
  • 决定查找方向:
    若目标值小于当前节点,则向左子树查找;若大于,则向右子树查找。
  • 终止条件:
    如果找到目标值,返回成功;若当前节点为空,则说明值不存在。

3.2.1 详细示例

bool Find(const K& key) {return _FindRec(_root, key); // 从根节点开始查找
}private:
bool _FindRec(BSTNode<K>* node, const K& key) {if (node == nullptr) return false; // 未找到if (key == node->_key) return true; // 找到if (key < node->_key) {return _FindRec(node->_left, key); // 向左子树查找} else {return _FindRec(node->_right, key); // 向右子树查找}
}

查找逻辑解析:

1.从根节点开始进行比较,根据大小关系决定查找方向。
2. 采用递归方式,直到找到目标值或到达空节点。

3.2.2 循环实现查找操作

与插入一样,查找操作也可以用循环实现。以下是循环方式的示例代码:

bool FindIterative(const K& key) {BSTNode<K>* current = _root;while (current != nullptr) {if (key == current->_key) {return true; // 找到目标值} else if (key < current->_key) {current = current->_left; // 向左子树查找} else {current = current->_right; // 向右子树查找}}return false; // 未找到
}
3.2.2.1 逻辑解析:
  • 循环控制:使用while循环遍历树,直至找到目标值或到达空节点。
  • 效率:循环方式避免了递归调用的开销,在处理深度较大的树时,能更有效地利用栈空间。

3.3 删除操作详解

删除操作需要考虑节点的子树情况,包括:

  • 查找节点:首先需要找到要删除的节点。

  • 判断情况:

    • 没有子节点:直接删除。
    • 只有一个子节点:将父节点指向子节点。
    • 有两个子节点:选择用左子树的最大值或右子树的最小值替代删除的节点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3.1 详细示例

bool Erase(const K& key) {return _EraseRec(_root, key); // 从根节点开始删除
}private:
bool _EraseRec(BSTNode<K>*& node, const K& key) {if (node == nullptr) return false; // 未找到if (key < node->_key) {return _EraseRec(node->_left, key); // 向左子树查找} else if (key > node->_key) {return _EraseRec(node->_right, key); // 向右子树查找} else {// 找到要删除的节点if (node->_left == nullptr) {BSTNode<K>* temp = node;node = node->_right; // 更新指向右子节点delete temp; // 删除旧节点} else if (node->_right == nullptr) {BSTNode<K>* temp = node;node = node->_left; // 更新指向左子节点delete temp; // 删除旧节点} else {// 找到替代节点BSTNode<K>* temp = _FindMax(node->_left); // 左子树的最大值node->_key = temp->_key; // 替代值_EraseRec(node->_left, temp->_key); // 删除替代节点}return true;}
}BSTNode<K>* _FindMax(BSTNode<K>* node) {while (node->_right != nullptr) {node = node->_right; // 寻找右子树的最大值}return node; // 返回最大节点
}

删除逻辑解析:

  • 首先查找目标节点,确定其子树情况。
  • 根据情况选择删除操作,并保持树的性质。

3.3.2 循环实现删除操作

虽然递归实现直观,但删除操作也可以用循环实现。以下是循环实现的示例代码:

bool EraseIterative(const K& key) {BSTNode<K>* current = _root;BSTNode<K>* parent = nullptr;// 找到要删除的节点和其父节点while (current != nullptr && current->_key != key) {parent = current;if (key < current->_key) {current = current->_left; // 向左子树查找} else {current = current->_right; // 向右子树查找}}// 如果未找到if (current == nullptr) return false;// 处理删除逻辑if (current->_left == nullptr) {if (current == _root) {_root = current->_right; // 更新根节点} else if (parent->_left == current) {parent->_left = current->_right; // 更新父节点的左指针} else {parent->_right = current->_right; // 更新父节点的右指针}} else if (current->_right == nullptr) {if (current == _root) {_root = current->_left; // 更新根节点} else if (parent->_left == current) {parent->_left = current->_left; // 更新父节点的左指针} else {parent->_right = current->_left; // 更新父节点的右指针}} else {// 找到替代节点BSTNode<K>* successor = _FindMin(current->_right); // 右子树的最小值K successorKey = successor->_key; // 备份替代值EraseIterative(successorKey); // 递归删除替代节点current->_key = successorKey; // 替代当前节点的值}delete current; // 删除当前节点return true;
}BSTNode<K>* _FindMin(BSTNode<K>* node) {while (node && node->_left != nullptr) {node = node->_left; // 寻找左子树的最小值}return node; // 返回最小节点
}
3.3.2.1 逻辑解析:
  • 查找节点:通过循环查找要删除的节点及其父节点。
  • 处理删除逻辑:根据节点的子树情况,选择合适的删除策略。
  • 更新指针:确保在删除节点后,正确更新父节点的指向,保持树的完整性。

3.4 遍历操作详解

遍历操作是对二叉搜索树进行全面访问的方式,通常分为三种基本类型:前序遍历、中序遍历和后序遍历。每种遍历都有其特定的应用场景。

3.4.1 中序遍历

中序遍历(左-根-右)会按顺序输出树中的节点值,使得遍历结果是一个升序序列。

步骤:

  • 先访问左子树。
  • 然后访问根节点。
  • 最后访问右子树。
3.4.1.1 示例代码
void InOrderTraversal(BSTNode<K>* node) {if (node == nullptr) return; // 如果节点为空,返回InOrderTraversal(node->_left); // 递归访问左子树cout << node->_key << " "; // 访问当前节点InOrderTraversal(node->_right); // 递归访问右子树
}
3.4.1.2 逻辑解析:
  • 递归方式:此方法通过递归访问每个节点,确保按顺序访问。
  • 输出顺序:中序遍历确保了节点值的升序排列,对于排序需求非常有用。

3.4.2 前序遍历

前序遍历(根-左-右)常用于复制树结构,因为它先访问根节点。

步骤:

  • 先访问根节点。
  • 然后访问左子树。
  • 最后访问右子树。
3.4.2.1 示例代码
void PreOrderTraversal(BSTNode<K>* node) {if (node == nullptr) return; // 如果节点为空,返回cout << node->_key << " "; // 访问当前节点PreOrderTraversal(node->_left); // 递归访问左子树PreOrderTraversal(node->_right); // 递归访问右子树
}
3.4.2.2 逻辑解析:
  • 根节点优先:此方法适合在需要先处理根节点的场景,例如在构建其他数据结构时。
  • 结构复制:前序遍历有助于复制树结构,因为它提供了节点的先后顺序。

3.4.3 后序遍历

后序遍历(左-右-根)常用于删除树的节点,因为它先访问子节点。

步骤:

  • 先访问左子树。
  • 然后访问右子树。
  • 最后访问根节点。
3.4.3.1 示例代码
void PostOrderTraversal(BSTNode<K>* node) {if (node == nullptr) return; // 如果节点为空,返回PostOrderTraversal(node->_left); // 递归访问左子树PostOrderTraversal(node->_right); // 递归访问右子树cout << node->_key << " "; // 访问当前节点
}
3.4.3.2 逻辑解析:
  • 子节点优先:后序遍历确保在删除节点之前,先处理它的子节点。这种策略在清空树时非常重要。
  • 删除操作:通常用在需要在树结构被修改前完成所有子树处理的场景。

小结

二叉搜索树,是数据世界的守护者。它将秩序注入混乱,以简单的规则诠释了高效与优雅的结合。在我们探寻它的过程中,感受到了算法设计的诗意与哲学。

然而,二叉搜索树的美丽并非永恒,平衡之道是它永远需要面对的考验。正因如此,程序员们才不断在其基础上改良,衍生出更强大的变种。

这片数据之林,因二叉搜索树的存在而繁盛。让我们记住它的规则与教诲,在算法的世界中不断探索,创造属于自己的诗篇。

本篇关于二叉搜索树的介绍就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!
在这里插入图片描述

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

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

相关文章

利索能及 ▏外观专利相似度多少算侵权?

判断是否侵权前提&#xff1a; 双方产品属于同类产品&#xff0c;不属于同类产品的不能比较。 判定同类产品不仅仅要依据《国际外观设计分类表》&#xff0c;还要依据一般商品商品的分类标准来却确定。 简单概括来说&#xff0c;判定侵权前提就是被控侵权产品和外观设计专利…

【编译原理】往年题汇总(山东大学软件学院用)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;编译原理_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

适用于Synology NAS的在线办公套件:ONLYOFFICE安装指南

使用 Synology NAS 上的 ONLYOFFICE 文档&#xff0c;您能在私有云中直接编辑文本文档、电子表格、演示文稿和 PDF&#xff0c;确保工作流程既安全又高效。本指南将分步介绍如何在 Synology 上安装 ONLYOFFICE 文档。 关于 Synology Synology NAS&#xff08;网络附加存储&…

SpringbBoot如何实现Tomcat集群的会话管理

在使用 Tomcat 集群时&#xff0c;由于每个 Tomcat 实例的 Session 存储是独立的&#xff0c;导致无法实现 Session 的共享&#xff0c;这可能影响到用户跨节点的访问。为了实现跨 Tomcat 实例共享 Session&#xff0c;可以使用 Spring Session 配合 Redis 进行集中式会话管理。…

机器人C++开源库The Robotics Library (RL)使用手册(三)

进入VS工程,我们先看看这些功能函数及其依赖库的分布关系: rl命名空间下,主要有八大模块。 搞定VS后将逐个拆解。 1、编译运行 根据报错提示,配置相应错误的库(根据每个人安装位置不同而不同,我的路径如下:) 编译所有,Release版本耗时大约10分钟。 以rlPlan运动…

零基础微信小程序开发——页面导航之声明式导航(保姆级教程+超详细)

&#x1f3a5; 作者简介&#xff1a; CSDN\阿里云\腾讯云\华为云开发社区优质创作者&#xff0c;专注分享大数据、Python、数据库、人工智能等领域的优质内容 &#x1f338;个人主页&#xff1a; 长风清留杨的博客 &#x1f343;形式准则&#xff1a; 无论成就大小&#xff0c;…

Ch9 形态学图像处理

Ch9 形态学图像处理 blog点此处&#xff01;<--------- 四大算子相应性质。 腐蚀、膨胀、开闭之间的含义、关系 文章目录 Ch9 形态学图像处理预备知识(Preliminaries)膨胀和腐蚀(Dilation and Erosion)腐蚀膨胀膨胀与腐蚀的对偶关系 开闭操作(Opening and Closing)开运算闭…

【UE5 C++课程系列笔记】14——GameInstanceSubsystem与动态多播的简单结合使用

效果 通过在关卡蓝图中触发GameInstanceSubsystem包含的委托&#xff0c;来触发所有绑定到这个委托的事件&#xff0c;从而实现跨蓝图通信。 步骤 1. 新建一个C类 这里命名为“SubsystemAndDelegate” 引入GameInstanceSubsystem.h&#xff0c;让“SubsystemAndDelegate”继承…

实战举例——vue.js组件开发

有很多人在问有关Vue的组件开发&#xff0c;虽然之前我的文章里讲过关于前端组件开发的例子&#xff0c;不过作为一个前端技术小白&#xff0c;还是有很多内容需要学习和巩固。我这里用我之前开发过的组件举例&#xff0c;再次为大家简单分享一下基于Vue2和elementUI开发的组件…

devops和ICCID简介

Devops DevOps&#xff08;Development 和 Operations 的组合&#xff09;是一种软件开发和 IT 运维的哲学&#xff0c;旨在促进开发、技术运营和质量保障&#xff08;QA&#xff09;部门之间的沟通、协作与整合。它强调自动化流程&#xff0c;持续集成&#xff08;CI&#xf…

设计宝藏解压密码

设计宝藏官网的解压密码是多少&#xff1f; 设计宝藏解压密码是&#xff1a;memm 设计宝藏一个致力于提供免费资源的宝藏网站。 无论你是Blender、After Effects (AE)、Cinema 4D (C4D)、Premiere Pro (PR)、Photoshop (PS)、Illustrator (AI)的爱好者&#xff0c;还是对CG影…

Android OpenGLES2.0开发(十):FBO离屏渲染

人生是一场单程的旅行&#xff0c;即使有些遗憾我们也没有从头再来的机会&#xff0c;与其纠结无法改变的过去不如微笑着珍惜未来。 Android OpenGLES开发&#xff1a;EGL环境搭建Android OpenGLES2.0开发&#xff08;一&#xff09;&#xff1a;艰难的开始Android OpenGLES2.0…

Magnet: 基于推送的大规模数据处理Shuffle服务

本文翻译自&#xff1a;《Magnet: Push-based Shuffle Service for Large-scale Data Processing》 摘要 在过去的十年中&#xff0c;Apache Spark 已成为大规模数据处理的流行计算引擎。与其他基于 MapReduce 计算范式的计算引擎一样&#xff0c;随机Shuffle操作&#xff08;即…

Pytorch | 利用VA-I-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用VA-I-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集VA-I-FGSM介绍相关定义算法流程 VAI-FGSM代码实现VAI-FGSM算法实现攻击效果 代码汇总vaifgsm.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器&#xff1a; Pytorch | 从零构建AlexNet对…

WebPack3项目升级webpack5的配置调试记录

文章目录 前言一、webpack3环境1.1、知识点记录1.1.1、配置解释1.1.2、webpack与sass版本对应关系1.1.3、CommonJS与ESModule1.1.4、node版本管理nvm1.1.5、sass-loader、sass与node-sass 1.2、其他1.2.1、.d.ts是什么文件1.2.2、react与types/react版本对应关系1.2.3、webpack…

MySQL 数据”丢失”事件之 binlog 解析应用

事件背景 客户反馈在晚间数据跑批后,查询相关表的数据时,发现该表的部分数据在数据库中不存在 从应用跑批的日志来看,跑批未报错,且可查到日志中明确显示当时那批数据已插入到数据库中 需要帮忙分析这批数据丢失的原因。 备注:考虑信息敏感性,以下分析场景测试环境模拟,相关数据…

微信小程序的轮播图学习报告

微信小程序轮播图学习报告 好久都没分享新内容了&#xff0c;实在惭愧惭愧。今天给大家做一个小程序轮播图的学习报告。 先给大家看一下我的项目状态&#xff1a; 很空昂&#xff01;像一个正在修行的老道&#xff0c;空的什么也没有。 但是我写了 4 个 view 容器&#xff0c;…

【RAII | 设计模式】C++智能指针,内存管理与设计模式

前言 nav2系列教材&#xff0c;yolov11部署,系统迁移教程我会放到年后一起更新&#xff0c;最近年末手头事情多&#xff0c;还请大家多多谅解。 上一节我们讲述了C移动语义相关的知识&#xff0c;本期我们来看看C中常用的几种智能指针&#xff0c;并看看他们在设计模式中的运…

微软 CEO 萨提亚・纳德拉:回顾过去十年,展望 AI 时代的战略布局

近日&#xff0c;微软 CEO 萨提亚・纳德拉与著名投资人比尔・格里和布拉德・格斯特纳进行了一场深度对话&#xff0c;回顾了过去十年微软的转型历程&#xff0c;并展望了 AI 时代的战略布局。在这次访谈中&#xff0c;纳德拉分享了他在微软的早期经历&#xff0c;包括他加入微软…

【Java-tesseract】OCR图片文本识别

文章目录 一、需求二、概述三、部署安装四、技术细节五、总结 一、需求 场景需求:是对识别常见的PNG,JPEG,TIFF,GIF图片识别&#xff0c;环境为离线内网。组件要求开源免费&#xff0c;并且可以集成Java生成接口服务。 二、概述 我不做选型对比了,我筛选测试了下Tesseract(v…