【附C源码】二叉搜索树的C语言实现
前言
二叉搜索树(Binary Search Tree,BST)作为一种基础且重要的数据结构,在计算机科学领域有着广泛的应用。本文将介绍一种基于C语言的二叉搜索树实现方案,涵盖其核心原理、代码实现细节以及使用方式。
核心原理
二叉搜索树的核心性质在于:对于树中的任意节点,其左子树中所有节点的值均小于该节点的值,右子树中所有节点的值均大于该节点的值。这一性质保证了中序遍历二叉搜索树时,输出结果必然是有序的。
基于这一性质,二叉搜索树在平均情况下能够以 O(log n) 的时间复杂度完成插入、删除和查找操作。当然,在最坏情况下(如树退化为链表),时间复杂度会退化为 O(n),这也是后续需要引入平衡机制的原因。
数据结构定义
首先定义树节点和树结构体:
c
typedef int Data;
typedef struct TreeNode {
Data data;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
typedef struct {
TreeNode* root;
int size;
} Tree;
此处采用 typedef int Data 的方式对数据类型进行抽象,便于后续扩展为其他类型。树结构体中维护 size 字段,使得获取节点数量的操作能够在 O(1) 时间内完成。
关键操作实现
节点插入
插入操作的核心逻辑是从根节点出发,根据待插入值与当前节点值的大小关系,决定向左子树或右子树递归查找,直至找到合适的空位置:
c
bool treeInsert(Tree* t, Data val) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->data = val;
newNode->left = NULL;
newNode->right = NULL;
if (!t->root) {
t->root = newNode;
t->size++;
return true;
}
TreeNode* current = t->root;
TreeNode* parent = NULL;
while (current) {
parent = current;
if (val < current->data) {
current = current->left;
} else if (val > current->data) {
current = current->right;
} else {
free(newNode); // 重复值不插入
return false;
}
}
if (val < parent->data) {
parent->left = newNode;
} else {
parent->right = newNode;
}
t->size++;
return true;
}
该实现采用迭代方式而非递归,避免了函数调用栈的开销。同时,代码中明确处理了重复值的情况,保证树中不存在重复节点。
节点删除
删除操作是二叉搜索树中最复杂的操作,需要根据待删除节点的子节点数量分三种情况处理:
- 叶子节点:直接删除,无需额外操作
- 单子节点:用子节点替代被删除节点
- 双子节点:用右子树的最小值(或左子树的最大值)替代被删除节点,然后递归删除该最小值节点
c
TreeNode* treeRemoveNode(TreeNode* node, Data val) {
if (!node) return NULL;
if (val < node->data) {
node->left = treeRemoveNode(node->left, val);
} else if (val > node->data) {
node->right = treeRemoveNode(node->right, val);
} else {
if (!node->left) {
TreeNode* temp = node->right;
free(node);
return temp;
} else if (!node->right) {
TreeNode* temp = node->left;
free(node);
return temp;
}
TreeNode* temp = treeFindMin(node->right);
node->data = temp->data;
node->right = treeRemoveNode(node->right, temp->data);
}
return node;
}
此处选择右子树的最小值作为替代节点,是因为该值必然大于左子树所有节点且小于右子树其余节点,满足二叉搜索树的性质。
树的遍历
三种基本遍历方式的递归实现:
c
void treeInOrderNode(TreeNode* node) {
if (!node) return;
treeInOrderNode(node->left);
printf("%d ", node->data);
treeInOrderNode(node->right);
}
中序遍历的输出结果即为升序序列,这一特性在实际应用中经常被利用。前序遍历和后序遍历的实现结构类似,仅调整访问顺序即可。
可视化输出
为了便于调试和验证,实现了一个简单的树形打印函数:
c
void treePrintNode(TreeNode* node, int depth) {
if (!node) return;
treePrintNode(node->right, depth + 1);
for (int i = 0; i < depth; i++) {
printf(" ");
}
printf("%d\n", node->data);
treePrintNode(node->left, depth + 1);
}
该函数采用右-根-左的遍历顺序,配合缩进输出,使得树的结构能够直观地呈现在终端上。
内存管理
C语言不像高级语言具备自动垃圾回收机制,因此内存管理需要格外谨慎。本实现中,销毁树的操作采用后序遍历的顺序递归释放节点:
c
void treeNodeDestroy(TreeNode* node) {
if (!node) return;
treeNodeDestroy(node->left);
treeNodeDestroy(node->right);
free(node);
}
必须确保子节点先于父节点释放,否则会导致内存泄漏或访问非法内存。
使用示例
以下是一个完整的使用示例:
c
int main() {
Tree* t = treeCreate();
treeInsert(t, 50);
treeInsert(t, 30);
treeInsert(t, 70);
treeInsert(t, 20);
treeInsert(t, 40);
treePrint(t);
treeInOrder(t);
printf("树高度: %d\n", treeHeight(t));
printf("叶子节点数: %d\n", treeCountLeaves(t));
treeRemove(t, 30);
treePrint(t);
treeDestroy(t);
return 0;
}
性能分析
| 操作 | 平均时间复杂度 | 最坏时间复杂度 |
|---|---|---|
| 插入 | O(log n) | O(n) |
| 删除 | O(log n) | O(n) |
| 查找 | O(log n) | O(n) |
| 遍历 | O(n) | O(n) |
当数据以随机顺序插入时,树的高度接近 log n,各项操作效率较高。但如果数据已排序,树将退化为链表,此时需要考虑引入AVL树或红黑树等自平衡机制。
总结
本文介绍了一种完整的二叉搜索树C语言实现方案。该实现涵盖了树的基本操作,代码结构清晰,注释完整,适合作为数据结构学习的参考材料。对于生产环境的使用,建议在此基础上增加错误处理机制,并根据实际场景考虑是否需要引入平衡策略。
完整的代码实现可在文末获取,如有问题或建议,欢迎交流讨论。
⚠️源码地址:https://github.com/anjuxi/C-binary_search_-tree/tree/main