引言
二叉搜索树(Binary Search Tree, BST)是一种经典的动态数据结构,因其高效的查找、插入和删除操作(平均时间复杂度O(logn)),广泛应用于数据库索引、缓存系统、排序算法等领域。本文将基于C语言实现一个完整的BST,详细解析其核心原理、关键操作及三种遍历方式,并通过测试用例验证功能正确性。
一、BST基础概念:定义与核心性质
1.1 BST的定义
二叉搜索树(BST)是一种特殊的二叉树,满足以下性质:
- 左子树性质:对于任意节点,其左子树中所有节点的键值均小于该节点的键值。
- 右子树性质:对于任意节点,其右子树中所有节点的键值均大于或等于该节点的键值(注:部分定义要求严格大于,本文采用“大于等于”以支持重复值处理)。
- 递归结构:左子树和右子树本身也是BST。
1.2 BST与普通二叉树的区别
普通二叉树仅要求节点最多有两个子节点,而BST通过键值的有序性赋予了更强大的功能:
- 高效查找:利用有序性,可通过比较键值快速缩小搜索范围。
- 动态排序:插入和删除操作自动维护有序性,无需额外排序步骤。
- 范围查询:可高效查询某个区间内的所有键值(如“查找所有大于10且小于50的键”)。
二、节点结构设计:为什么选择int类型?
2.1 TreeNode结构体解析
1 2 3 4 5 6 7
| typedef int KeyType; // 键类型为整数
typedef struct node { KeyType key; // 节点键值(唯一标识) struct node *left; // 左子树指针(指向更小键值的子树) struct node *right; // 右子树指针(指向更大或相等的子树) } TreeNode;
|
- key:存储节点的唯一键值,是BST有序性的核心依据。
- left/right:分别指向左子树和右子树的根节点,构成递归结构。
2.2 为什么选择int类型?
当前代码中KeyType
被定义为int
,这是为了简化实现并满足大多数基础场景的需求。实际应用中,可通过以下方式扩展为泛型:
- 使用
void*
类型存储任意类型的键值。
- 添加一个比较函数指针(如
int (*compare)(const void*, const void*)
),用于自定义键值的比较逻辑(如字符串、结构体等)。
三、增删改查实现:核心操作的逻辑与细节
3.1 插入操作(Insert):保持BST性质的关键
插入操作的目标是将新节点添加到正确位置,同时维持BST的有序性。当前代码采用递归实现,逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| // 辅助函数:递归插入节点(返回插入后的子树根) static TreeNode *bst_insert_helper(TreeNode *node, KeyType key, bool *inserted) { if (!node) { // 空位置插入新节点 TreeNode *new_node = (TreeNode *)malloc(sizeof(TreeNode)); new_node->key = key; new_node->left = new_node->right = NULL; *inserted = true; // 标记插入成功 return new_node; }
if (key == node->key) { // 键已存在,插入失败 *inserted = false; return node; } else if (key < node->key) { // 插入左子树 node->left = bst_insert_helper(node->left, key, inserted); } else { // 插入右子树(允许等于,取决于需求) node->right = bst_insert_helper(node->right, key, inserted); } return node; }
|
关键逻辑:
- 终止条件:当当前节点为空时,创建新节点并插入。
- 键值比较:若键已存在(
key == node->key
),标记插入失败(当前代码忽略重复键);若键更小,递归插入左子树;否则递归插入右子树。
- 保持有序性:通过递归路径确保新节点最终位于正确位置,维持BST的左小右大性质。
测试验证:
插入序列{50, 30, 20, 40, 70, 60, 80}
后,BST的结构如下:
1 2 3 4 5
| 50 / \ 30 70 / \ / \ 20 40 60 80
|
3.2 查找操作(Search):利用有序性快速定位
查找操作通过比较键值与当前节点的键值,逐步缩小搜索范围。当前代码提供递归实现:
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 辅助函数:递归搜索节点 static TreeNode *bst_search_helper(TreeNode *node, KeyType key) { if (!node) return NULL; // 空树或未找到 if (key == node->key) { return node; // 找到目标节点 } else if (key < node->key) { return bst_search_helper(node->left, key); // 搜索左子树 } else { return bst_search_helper(node->right, key); // 搜索右子树 } }
|
时间复杂度分析:
- 平均情况:O(logn)(树高为logn,每次比较缩小一半范围)。
- 最坏情况:O(n)(树退化为链表,如插入序列为
{1,2,3,4,...}
)。
3.3 删除操作(Delete):维持树结构的核心挑战
删除操作需处理三种情况,并确保删除后树仍满足BST性质:
情况1:节点无子节点(叶子节点)
直接删除节点,父节点对应指针置空。
情况2:节点只有一个子节点
用子节点替换当前节点,父节点指针指向子节点。
情况3:节点有两个子节点
找到右子树的最小节点(后继节点),用其键值替换当前节点的键值,然后删除右子树中的最小节点(避免破坏BST性质)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| // 辅助函数:递归删除节点(返回删除后的子树根) static TreeNode *bst_delete_helper(TreeNode *node, KeyType key, bool *deleted) { if (!node) { // 未找到目标节点 *deleted = false; return NULL; }
if (key < node->key) { // 目标在左子树 node->left = bst_delete_helper(node->left, key, deleted); } else if (key > node->key) { // 目标在右子树 node->right = bst_delete_helper(node->right, key, deleted); } else { // 找到目标节点 *deleted = true;
// 情况1:无左子树(只有右子树或无子树) if (!node->left) { TreeNode *temp = node->right; free(node); return temp; } // 情况2:无右子树(只有左子树) else if (!node->right) { TreeNode *temp = node->left; free(node); return temp; }
// 情况3:有两个子节点(找右子树的最小节点替换) TreeNode *min_node = bst_min(node->right); // 找右子树最小节点 node->key = min_node->key; // 替换当前节点的键 node->right = bst_delete_helper(node->right, min_node->key, deleted); // 删除右子树的最小节点 } return node; }
|
关键细节:
- 后继节点:右子树的最小节点(最左节点)是删除双子节点时的最佳替换候选,因为它不会破坏左子树的有序性。
- 内存管理:删除节点后需释放其内存,避免内存泄漏。
3.4 修改操作(Update):先查找后更新的完整流程
修改操作需先找到目标节点,再更新其键值。需注意:修改键值后可能破坏BST性质,因此需重新调整树结构(或直接删除旧节点并插入新节点)。
当前代码未直接提供修改函数,但可通过以下步骤实现:
1 2 3 4 5 6 7 8 9 10 11 12
| bool bst_update(BST *tree, KeyType old_key, KeyType new_key) { TreeNode *node = bst_search(tree, old_key); if (!node) return false; // 旧键不存在
// 方法1:删除旧节点,插入新节点(简单但可能影响性能) bst_delete(tree, old_key); return bst_insert(tree, new_key);
// 方法2:直接修改键值(需调整树结构,复杂度较高) // 注意:修改后需确保左子树所有节点 < new_key,右子树所有节点 >= new_key // 若new_key < node->key,需将原左子树中 >= new_key的节点移到右子树,反之亦然(不推荐) }
|
四、三种遍历方式:深度优先搜索(DFS)的应用
遍历是访问树中所有节点的过程,BST的三种经典遍历方式(前序、中序、后序)均基于深度优先搜索(DFS),区别在于访问节点的时机。
4.1 前序遍历(根→左→右)
逻辑:先访问根节点,再递归遍历左子树,最后递归遍历右子树。
应用场景:复制树结构、序列化(如JSON格式)。
递归实现:
1 2 3 4 5 6
| static void bst_preorder_helper(TreeNode *node) { if (!node) return; printf("%d ", node->key); // 访问根节点 bst_preorder_helper(node->left); // 遍历左子树 bst_preorder_helper(node->right); // 遍历右子树 }
|
4.2 中序遍历(左→根→右)
逻辑:先递归遍历左子树,再访问根节点,最后递归遍历右子树。
特性:BST的中序遍历结果是有序序列(升序)。
应用场景:排序、验证BST性质。
递归实现:
1 2 3 4 5 6
| static void bst_inorder_helper(TreeNode *node) { if (!node) return; bst_inorder_helper(node->left); // 遍历左子树 printf("%d ", node->key); // 访问根节点 bst_inorder_helper(node->right); // 遍历右子树 }
|
4.3 后序遍历(左→右→根)
逻辑:先递归遍历左子树,再递归遍历右子树,最后访问根节点。
应用场景:释放内存(确保子节点先被释放)、后序表达式求值。
递归实现:
1 2 3 4 5 6
| static void bst_postorder_helper(TreeNode *node) { if (!node) return; bst_postorder_helper(node->left); // 遍历左子树 bst_postorder_helper(node->right); // 遍历右子树 printf("%d ", node->key); // 访问根节点 }
|
五、完整示例与测试:验证功能正确性
5.1 测试代码说明
以下测试用例覆盖插入、重复插入、搜索、删除和遍历操作,验证BST的核心功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| void test_bst() { BST *bst = bst_create(); if (!bst) { printf("创建BST失败\n"); return; }
// 测试插入操作 int keys_to_insert[] = {50, 30, 20, 40, 70, 60, 80}; for (int i = 0; i < sizeof(keys_to_insert)/sizeof(keys_to_insert[0]); i++) { if (bst_insert(bst, keys_to_insert[i])) { printf("插入键 %d 成功\n", keys_to_insert[i]); bst_inorder(bst); // 中序遍历应输出有序序列 printf(" "); } else { printf("插入键 %d 失败(已存在)\n", keys_to_insert[i]); } }
// 测试重复插入 bst_insert(bst, 50); // 应失败 bst_inorder(bst); // 序列不变 printf("\n");
// 测试搜索操作 int keys_to_search[] = {40, 90}; for (int i = 0; i < sizeof(keys_to_search)/sizeof(keys_to_search[0]); i++) { TreeNode *result = bst_search(bst, keys_to_search[i]); if (result) { printf("找到键 %d\n", keys_to_search[i]); } else { printf("未找到键 %d\n", keys_to_search[i]); } }
// 测试删除操作 int keys_to_delete[] = {20, 50, 100}; for (int i = 0; i < sizeof(keys_to_delete)/sizeof(keys_to_delete[0]); i++) { if (bst_delete(bst, keys_to_delete[i])) { printf("删除键 %d 成功\n", keys_to_delete[i]); bst_inorder(bst); // 中序遍历应保持有序 printf("\n"); } else { printf("删除键 %d 失败(不存在)\n", keys_to_delete[i]); } }
bst_destroy(bst); printf("BST销毁成功\n"); }
|
5.2 测试结果验证
运行测试代码,输出结果如下(关键步骤):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 插入键 50 成功 50 插入键 30 成功 30 50 插入键 20 成功 20 30 50 插入键 40 成功 20 30 40 50 插入键 70 成功 20 30 40 50 70 插入键 60 成功 20 30 40 50 60 70 插入键 80 成功 20 30 40 50 60 70 80 插入键 50 失败(已存在) 20 30 40 50 60 70 80
找到键 40 未找到键 90
删除键 20 成功 30 40 50 60 70 80 删除键 50 成功 30 40 60 70 80 删除键 100 失败(不存在) 30 40 60 70 80
BST销毁成功
|
六、总结与扩展
6.1 BST的优势与局限
- 优势:高效的动态排序(插入、删除、查找平均O(logn))、支持范围查询。
- 局限:最坏情况下树高为n(退化为链表),时间复杂度退化为O(n)(可通过平衡BST如AVL树、红黑树优化)。
6.2 扩展方向
- 泛型支持:使用
void*
和比较函数指针,支持任意类型的键值。
- 平衡BST:实现AVL树或红黑树,确保最坏时间复杂度为O(logn)。
- 迭代实现:将递归操作改为迭代(使用栈模拟递归),避免栈溢出(适用于大深度树)。
通过本文的详细解析,读者已掌握BST的核心原理与实现细节。实际应用中,可根据需求选择递归或迭代实现,并根据数据规模选择普通BST或平衡BST。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #ifndef BST_H #define BST_H
#include <stdbool.h> #include <stdlib.h> #include <errno.h>
typedef int KeyType; // 键类型为整数
// 二叉搜索树节点结构体 typedef struct node { KeyType key; // 节点键值(唯一) struct node *left; // 左子树指针 struct node *right; // 右子树指针 } TreeNode;
// 二叉搜索树结构体(封装根节点) typedef struct { TreeNode *root; // 根节点指针 } BST;
// 基础操作函数声明 BST *bst_create(); // 创建空BST bool bst_insert(BST *tree, KeyType key); // 插入新节点(键唯一) TreeNode *bst_search(BST *tree, KeyType key); // 搜索节点(返回节点指针) bool bst_delete(BST *tree, KeyType key); // 删除节点(键存在时成功) void bst_destroy(BST *tree); // 销毁BST(释放所有内存)
void bst_preorder(BST *tree); void bst_inorder(BST *tree); void bst_postorder(BST *tree);
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #include "bst.h" #include <stdio.h>
// 打印三种遍历结果的辅助函数 void print_traversals(BST *bst) { printf("先序遍历结果: "); bst_preorder(bst); printf("\n");
printf("中序遍历结果: "); bst_inorder(bst); printf("\n");
printf("后序遍历结果: "); bst_postorder(bst); printf("\n"); }
// 测试函数 void test_bst() { // 创建空BST BST *bst = bst_create(); if (!bst) { printf("创建BST失败\n"); return; }
// 测试插入操作 int keys_to_insert[] = { 50, 30, 20, 40, 70, 60, 80 }; int num_keys = sizeof(keys_to_insert) / sizeof(keys_to_insert[0]); for (int i = 0; i < num_keys; i++) { if (bst_insert(bst, keys_to_insert[i])) { printf("成功插入键: %d\n", keys_to_insert[i]); print_traversals(bst); } else { printf("插入键 %d 失败,键已存在\n", keys_to_insert[i]); } }
// 测试重复插入 if (!bst_insert(bst, 50)) { printf("成功检测到重复键 50,插入失败\n"); print_traversals(bst); }
// 测试搜索操作 int keys_to_search[] = { 40, 90 }; int num_search_keys = sizeof(keys_to_search) / sizeof(keys_to_search[0]); for (int i = 0; i < num_search_keys; i++) { TreeNode *result = bst_search(bst, keys_to_search[i]); if (result) { printf("找到键: %d\n", keys_to_search[i]); } else { printf("未找到键: %d\n", keys_to_search[i]); } }
// 测试删除操作 int keys_to_delete[] = { 20, 50, 100 }; int num_delete_keys = sizeof(keys_to_delete) / sizeof(keys_to_delete[0]); for (int i = 0; i < num_delete_keys; i++) { if (bst_delete(bst, keys_to_delete[i])) { printf("成功删除键: %d\n", keys_to_delete[i]); print_traversals(bst); } else { printf("删除键 %d 失败,键不存在\n", keys_to_delete[i]); } }
// 销毁BST bst_destroy(bst); printf("BST已成功销毁\n"); }
int main() { test_bst(); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
| #include "bst.h" #include <stdio.h> #include <stdlib.h>
// 创建空BST BST *bst_create() { BST *bst = (BST *)malloc(sizeof(BST)); if (!bst) { perror("BST内存分配失败"); exit(EXIT_FAILURE); } bst->root = NULL; // 初始根节点为空 return bst; }
// 辅助函数:递归插入节点(返回插入后的子树根) static TreeNode *bst_insert_helper(TreeNode *node, KeyType key, bool *inserted) { if (!node) { // 空位置插入新节点 TreeNode *new_node = (TreeNode *)malloc(sizeof(TreeNode)); if (!new_node) { perror("新节点内存分配失败"); exit(EXIT_FAILURE); } new_node->key = key; new_node->left = new_node->right = NULL; *inserted = true; // 标记插入成功 return new_node; }
if (key == node->key) { // 键已存在,插入失败 *inserted = false; return node; } else if (key < node->key) { // 插入左子树 node->left = bst_insert_helper(node->left, key, inserted); } else { // 插入右子树 node->right = bst_insert_helper(node->right, key, inserted); } return node; }
// 插入新节点(键唯一,存在则失败) bool bst_insert(BST *tree, KeyType key) { if (!tree) return false; bool inserted = false; tree->root = bst_insert_helper(tree->root, key, &inserted); return inserted; }
// 辅助函数:递归搜索节点 static TreeNode *bst_search_helper(TreeNode *node, KeyType key) { if (!node) return NULL; // 空树或未找到 if (key == node->key) { return node; // 找到目标节点 } else if (key < node->key) { return bst_search_helper(node->left, key); // 搜索左子树 } else { return bst_search_helper(node->right, key); // 搜索右子树 } }
// 搜索节点(返回节点指针,不存在返回NULL) TreeNode *bst_search(BST *tree, KeyType key) { if (!tree) return NULL; return bst_search_helper(tree->root, key); }
// 辅助函数:找以node为根的最小节点(最左节点) static TreeNode *bst_min(TreeNode *node) { while (node && node->left) { node = node->left; } return node; } // 辅助函数:递归删除节点(返回删除后的子树根) static TreeNode *bst_delete_helper(TreeNode *node, KeyType key, bool *deleted) { if (!node) { // 未找到目标节点 *deleted = false; return NULL; }
if (key < node->key) { // 目标在左子树 node->left = bst_delete_helper(node->left, key, deleted); } else if (key > node->key) { // 目标在右子树 node->right = bst_delete_helper(node->right, key, deleted); } else { // 找到目标节点 *deleted = true;
// 情况1:叶子节点或只有一个子节点 if (!node->left) { // 只有右子树或无子树 TreeNode *temp = node->right; free(node); // 释放当前节点 return temp; // 返回右子树(可能为NULL) } else if (!node->right) { // 只有左子树 TreeNode *temp = node->left; free(node); // 释放当前节点 return temp; // 返回左子树(可能为NULL) }
// 情况2:有两个子节点(找右子树的最小节点替换) TreeNode *min_node = bst_min(node->right); // 找右子树最小节点 node->key = min_node->key; // 替换当前节点的键 // 删除右子树中的最小节点(递归) node->right = bst_delete_helper(node->right, min_node->key, deleted); } return node; }
// 删除节点(键存在时成功) bool bst_delete(BST *tree, KeyType key) { if (!tree) return false; bool deleted = false; tree->root = bst_delete_helper(tree->root, key, &deleted); return deleted; }
// 辅助函数:递归销毁所有节点 static void bst_destroy_helper(TreeNode *node) { if (!node) return; bst_destroy_helper(node->left); // 销毁左子树 bst_destroy_helper(node->right); // 销毁右子树 free(node); // 释放当前节点 }
// 销毁BST(释放所有内存) void bst_destroy(BST *tree) { if (!tree) return; bst_destroy_helper(tree->root); // 销毁所有节点 free(tree); // 释放BST结构体 }
// 深度优先-先序遍历(根-左-右) // 先序遍历辅助函数(递归核心) static void bst_preorder_helper(TreeNode *node) { if (!node) return; // 递归终止条件:当前节点为空 printf("%d ", node->key); // 访问当前节点(根) bst_preorder_helper(node->left); // 遍历左子树 bst_preorder_helper(node->right); // 遍历右子树 } void bst_preorder(BST *tree) { // 检查树或根节点是否为空,避免空指针解引用 if (!tree || !tree->root) return; // 调用辅助函数处理递归 bst_preorder_helper(tree->root); }
// 中序遍历辅助函数(递归核心) static void bst_inorder_helper(TreeNode *node) { if (!node) return; bst_inorder_helper(node->left); // 遍历左子树 printf("%d ", node->key); // 访问当前节点(根) bst_inorder_helper(node->right); // 遍历右子树 } // 深度优先-中序遍历(左-根-右) void bst_inorder(BST *tree) { if (!tree || !tree->root) return; bst_inorder_helper(tree->root); }
// 后序遍历辅助函数(递归核心) static void bst_postorder_helper(TreeNode *node) { if (!node) return; bst_postorder_helper(node->left); // 遍历左子树 bst_postorder_helper(node->right); // 遍历右子树 printf("%d ", node->key); // 访问当前节点(根) } // 深度优先-后序遍历(左-右-根) void bst_postorder(BST *tree) { if (!tree || !tree->root) return; bst_postorder_helper(tree->root); }
|