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

数据结构学习之路(八)----二叉搜索树(BST):从原理到代码实现

一、什么是二叉搜索树

二叉搜索树(Binary Search Tree,BST),也叫二叉排序树或二叉查找树,是在普通二叉树的基础上增加了有序性约束的一种数据结构。

它的核心规则很简单:

  • 若左子树不空,则左子树上所有结点的值均小于根结点的值

  • 若右子树不空,则右子树上所有结点的值均大于根结点的值

  • 左右子树同样满足上述规则

这个递归定义保证了整棵树的中序遍历结果是一个从小到大排好序的序列,这也是二叉搜索树名字中“搜索”二字的由来。

二、为什么需要二叉搜索树

二叉搜索树最大的优势在于查找效率。我们可以在不进行中序遍历生成有序数组的情况下,直接利用树的结构进行查找。

查找的逻辑非常直观:从根节点开始,如果目标值等于当前节点值就找到了;如果小于当前节点值就去左子树找;如果大于就去右子树找。这个过程可以递归实现,也可以非递归实现。

但二叉搜索树并非完美无缺。它的查找效率取决于树的高度。在极端情况下,如果插入的数据本身是有序的(比如 1,2,3,4,5,6),构造出的二叉搜索树会退化成单链表,查找效率从 O(log n) 降为 O(n)。这也正是后面要讲的平衡二叉树(AVL 树)要解决的问题。

三、二叉搜索树的代码实现

下面我们结合完整的 C 语言代码来逐一讲解 BST 的核心操作。

3.1 节点与树的结构定义
typedef struct BS_Node { Element_t data; // 节点存储的数据 struct BS_Node* left; // 左子树指针 struct BS_Node* right; // 右子树指针 } BS_Node; typedef struct BSTree { int count; // 树中节点总数 BS_Node* root; // 根节点指针 } BSTree;
3.2 创建树

创建一棵空树,初始化计数器为 0,根节点置为 NULL:

BSTree* createBSTree() { BSTree* tree = malloc(sizeof(BSTree)); if (tree == NULL) { return NULL; } tree->count = 0; tree->root = NULL; return tree; }
3.3 插入操作

插入操作的核心思想是:新节点一定插入在叶子节点的位置

static BS_Node* createBSNode(Element_t e) { BS_Node* node = malloc(sizeof(BS_Node)); if (node == NULL) { printf("malloc node failed!\n"); return NULL; } node->data = e; node->left = node->right = NULL; return node; } static BSTree* insertBSNodeRecur(BSTree* tree, BS_Node* node, Element_t e) { if (node == NULL) { tree->count++; return createBSNode(e); } if (e < node->data) { node->left = insertBSNodeRecur(tree, node->left, e); } else if (e > node->data) { node->right = insertBSNodeRecur(tree, node->right, e); } return node; } void insertBSTreeRecur(BSTree* tree, Element_t e) { if (tree) { tree->root = insertBSNodeRecur(tree, tree->root, e); } }

如果当前节点为空,就在此创建新节点;否则根据值的大小决定去左子树还是右子树继续递归。

3.4 中序遍历

中序遍历(左-根-右)会输出一个有序序列,这是验证 BST 正确性的常用方法:

static void visitedBSTree(BS_Node* node) { if (node) { printf("%d ", node->data); } } static void inOrderBSNode(BS_Node* node) { if (node) { inOrderBSNode(node->left); visitedBSTree(node); inOrderBSNode(node->right); } } void inOrderBSTree(BSTree* tree) { if (tree) { inOrderBSNode(tree->root); } }
3.5 查找最值

根据 BST 的性质:

  • 最小值一定在树的最左节点(一直往左走到底)

  • 最大值一定在树的最右节点(一直往右走到底)

static BS_Node* minElement(BS_Node* node) { while (node && node->left) { node = node->left; } return node; }
3.6 删除操作

删除是 BST 中最复杂的操作,需要分三种情况处理:

情况一:删除叶子节点— 直接删除即可,不影响其他节点的排列规则。

情况二:删除度为 1 的节点— 该节点缺失左子树或右子树,删除后让它的子节点直接接替它的位置。

情况三:删除度为 2 的节点— 需要找到该节点的前驱(左子树中的最大值)或后继(右子树中的最小值)来替换它,然后删除那个前驱/后继节点。

代码实现如下:

static BS_Node* deleteBSNode(BSTree* tree, BS_Node* node, Element_t e) { if (node == NULL) { return NULL; } if (e < node->data) { node->left = deleteBSNode(tree, node->left, e); } else if (e > node->data) { node->right = deleteBSNode(tree, node->right, e); } else { BS_Node* tmp = NULL; // 度为0(叶子节点)或度为1(只有右子树) if (node->left == NULL) { tmp = node->right; free(node); tree->count--; return tmp; } // 度为1(只有左子树) if (node->right == NULL) { tmp = node->left; free(node); tree->count--; return tmp; } // 度为2:找后继节点(右子树中的最小值)来替换 tmp = minElement(node->right); node->data = tmp->data; node->right = deleteBSNode(tree, node->right, tmp->data); } return node; } void deleteBSTree(BSTree* tree, Element_t e) { if (tree) { tree->root = deleteBSNode(tree, tree->root, e); } }

注意:原代码中处理度为 2 的节点时,node->left == NULLnode->right == NULL的判断顺序有先后——先判断左子树为空(此时无论右子树是否为空,都用右子树接替),再判断右子树为空。

3.7 计算树的高度

树的高度决定了搜索效率的上限:

int heightBSTree(BS_Node* node) { if (node == NULL) { return 0; } int leftHeight = heightBSTree(node->left); int rightHeight = heightBSTree(node->right); if (leftHeight > rightHeight) { return ++leftHeight; } else { return ++rightHeight; } }
3.8 完整测试
void test01() { Element_t data[] = { 50, 33, 25, 7, 91, 21, 35, 28, 55 }; BSTree* tree = createBSTree(); if (tree == NULL) { return; } for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++) { insertBSTreeRecur(tree, data[i]); } printf("中序遍历结果: "); inOrderBSTree(tree); printf("\ntree height: %d\n", heightBSTree(tree->root)); deleteBSTree(tree, 21); printf("删除21后: "); inOrderBSTree(tree); } int main() { test01(); return 0; }

四、总结

二叉搜索树通过有序性约束,将查找、插入、删除的平均时间复杂度降到了 O(log n)。它的核心操作——查找、插入、删除——都可以用递归优雅地实现。

不过正如前面提到的,BST 在面对有序数据时会退化成链表。为了应对这种情况,计算机科学家们发明了平衡二叉树(AVL 树),通过引入平衡因子(左子树高度与右子树高度之差)的概念,确保任意节点的平衡因子不超过 1,从而保证树的高度始终维持在 O(log n) 级别。

http://www.cnnetsun.cn/news/3329694.html

相关文章:

  • 初级电池寿命延长技术:NBM7100A与PIC18F97J94低功耗方案
  • ROS TF坐标变换从原理到C++实战详解
  • Unity InputField onEndEdit事件优化:区分回车提交与失焦的跨平台解决方案
  • 关于我 幸运-草
  • Java面试官最看重的几个能力指标
  • ROS2接口入门:从.msg/.srv/.action定义到通信实战
  • RISC-V 流水线数据冒险:3种解决方案(前递/停顿/寄存器堆)性能开销实测
  • ComfyUI-Manager:为什么这个插件管理工具能成为AI工作流开发者的必备神器?
  • Deepseek大模型:从代码生成到系统设计的AI助手实战指南
  • AI项目本地部署实战:从环境配置到性能优化全指南
  • C++性能优化:预定义与函数优化的核心技巧与实践
  • 终极指南:R3nzSkin英雄联盟换肤工具完整使用教程
  • HarmonyOS APP实战-画图APP - 第3篇:矩形与圆形绘制
  • AD5593R与PIC18F4550的混合信号系统设计与优化
  • 用户生命状态分析(看板搭建)
  • 3分钟解锁微信多设备登录:免Root实现手机平板同时在线
  • 航空台架、氢能实验室、半导体设备:Spectite 电气贯穿件模块化密封方案解析
  • 从黑光视觉到高算力SoC:爱芯元智边缘计算芯片矩阵全景
  • 落地千行百业:爱芯元智边缘AI如何重构智慧城市与工业视觉
  • 深入理解Linux多线程互斥锁:从临界区到底层原子指令的完整闭环
  • AI Agent控制框架(Harness)工程实践:从系统架构到生产部署
  • 论文选题可以免费生成的AI写作辅助网站有哪些
  • 9款AI论文平台:一键生成毕业论文、期刊论文、开题报告与文献综述
  • C++ deque内存块大小对性能的影响与优化策略
  • NoteExpress 3.8 文献管理实战:从10篇引文到定稿格式的3步自动化流程
  • DeepL Chrome翻译插件:让外文网页阅读变得像母语一样自然
  • 基于OpenAI Codex的自动化代码审查:从原理到工程实践
  • PHP代码审计基础——面向对象(四)
  • SlowFast行为识别算法详解:从双通路原理到PyTorch实战
  • Android Studio 2023.3 + ConstraintLayout 2.1:5个可视化编辑器高效技巧与避坑指南