news 2026/7/13 3:13:33

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构学习之路(八)----二叉搜索树(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) 级别。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/13 3:12:29

初级电池寿命延长技术:NBM7100A与PIC18F97J94低功耗方案

1. 初级电池寿命延长的技术挑战与解决方案在物联网设备和便携式电子产品中&#xff0c;不可充电的初级电池&#xff08;如纽扣电池CR2032、柱状电池ER26500等&#xff09;因其体积小、成本低、无需维护等优势被广泛使用。然而这类电池存在两个致命弱点&#xff1a;一是放电容量…

作者头像 李华
网站建设 2026/7/13 3:11:31

ROS TF坐标变换从原理到C++实战详解

1. 为什么坐标变换是ROS里绕不开的“空气”——从机械臂抓取一个杯子说起你刚在ROS里跑通了第一个/cmd_vel控制小车前进&#xff0c;兴奋地给同事演示&#xff1b;结果他问&#xff1a;“如果摄像头看到杯子在图像左上角&#xff0c;怎么让机械臂末端精准伸过去&#xff1f;”你…

作者头像 李华
网站建设 2026/7/13 3:10:17

关于我 幸运-草

hello&#xff0c;你好&#xff01;首先&#xff0c;我很开心&#xff0c;不管是出于何种原因&#xff0c;您看到了这篇文章&#xff01;正如标题一样&#xff0c;这并不是一篇关于技术的文章、只是一篇自我介绍。我不是什么技术大佬、也没有高学历的光环加持、仅仅是一名从事A…

作者头像 李华
网站建设 2026/7/13 3:08:31

Java面试官最看重的几个能力指标

你自以为精通Java&#xff0c;但在面试官眼里可能连及格线都没到上周帮团队面了一个简历上写着“精通Java”的候选人&#xff0c;三年经验&#xff0c;开源项目贡献者&#xff0c;Github小星星也有几百个。技术面问到“HashMap在JDK1.8扩容时怎么保证线程不安全&#xff1f;”他…

作者头像 李华
网站建设 2026/7/13 3:05:32

ROS2接口入门:从.msg/.srv/.action定义到通信实战

1. 为什么ROS2的“接口”是绕不开的第一道门槛刚接触ROS2的人&#xff0c;十有八九会在“接口”这个词上卡住——不是不会写代码&#xff0c;而是根本不知道该往哪儿写、写完之后系统认不认、节点之间到底靠什么“说上话”。我带过二十多届校企联合实训班&#xff0c;每次开课前…

作者头像 李华