1. 什么是二叉搜索树
二叉搜索树(Binary Search Tree,简称 BST)是一种特殊的二叉树,它满足以下性质:
- 若左子树不为空,则左子树上所有节点的值均小于根节点的值。
- 若右子树不为空,则右子树上所有节点的值均大于根节点的值。
- 左右子树本身也分别是二叉搜索树。
基于这一性质,二叉搜索树在理想情况下可以实现 O(log n) 的查找、插入和删除操作,是许多高效数据结构(如 AVL 树、红黑树)的基础。
2. 二叉搜索树的结构定义
在 C 语言中,我们通常使用结构体来定义二叉搜索树的节点。每个节点包含一个数据域和两个指针域,分别指向左孩子和右孩子。
c
#include <stdio.h>
#include <stdlib.h>
// 定义二叉搜索树节点
typedef struct Node {
int data; // 数据域
struct Node *left; // 左孩子指针
struct Node *right; // 右孩子指针
} Node;
// 创建新节点
Node* createNode(int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
3. 二叉搜索树的插入操作
插入操作遵循二叉搜索树的性质:从根节点开始,若待插入的值小于当前节点,则向左子树递归插入;若大于当前节点,则向右子树递归插入;直到找到空位置为止。
c
// 插入节点(递归实现)
Node* insert(Node *root, int data) {
// 找到空位置,创建新节点
if (root == NULL) {
return createNode(data);
}
// 小于当前节点,插入左子树
if (data < root->data) {
root->left = insert(root->left, data);
}
// 大于当前节点,插入右子树
else if (data > root->data) {
root->right = insert(root->right, data);
}
// 值已存在,不重复插入(可根据需求处理)
return root;
}
4. 二叉搜索树的查找操作
查找操作同样利用二叉搜索树的有序性:从根节点开始,若目标值等于当前节点则查找成功;若小于当前节点则向左子树查找;若大于当前节点则向右子树查找。若遇到空节点仍未找到,则说明目标值不存在。
c
// 查找节点(递归实现)
Node* search(Node *root, int target) {
// 空树或找到目标值
if (root == NULL || root->data == target) {
return root;
}
// 目标值小于当前节点,向左子树查找
if (target < root->data) {
return search(root->left, target);
}
// 目标值大于当前节点,向右子树查找
return search(root->right, target);
}
5. 二叉搜索树的删除操作
删除操作是二叉搜索树中最复杂的操作,需要分三种情况讨论:
- 情况一:待删除节点是叶子节点,直接删除即可。
- 情况二:待删除节点只有一个孩子,用其孩子节点替代它。
- 情况三:待删除节点有两个孩子,通常用其左子树中的最大节点或右子树中的最小节点来替代它。
c
// 查找以 root 为根的最小节点
Node* findMin(Node *root) {
while (root->left != NULL) {
root = root->left;
}
return root;
}
// 删除节点(递归实现)
Node* deleteNode(Node *root, int target) {
// 空树,直接返回
if (root == NULL) {
return root;
}
// 目标值小于当前节点,向左子树递归删除
if (target < root->data) {
root->left = deleteNode(root->left, target);
}
// 目标值大于当前节点,向右子树递归删除
else if (target > root->data) {
root->right = deleteNode(root->right, target);
}
// 找到待删除节点
else {
// 情况一:叶子节点或只有一个孩子
if (root->left == NULL) {
Node *temp = root->right;
free(root);
return temp;
}
else if (root->right == NULL) {
Node *temp = root->left;
free(root);
return temp;
}
// 情况三:有两个孩子,用右子树最小节点替代
Node *temp = findMin(root->right);
root->data = temp->data;
root->right = deleteNode(root->right, temp->data);
}
return root;
}
6. 二叉搜索树的遍历
二叉搜索树支持三种经典的深度优先遍历方式:前序遍历、中序遍历和后序遍历。其中,中序遍历二叉搜索树可以得到一个升序序列,这是二叉搜索树最重要的特性之一。
c
// 前序遍历:根 -> 左 -> 右
void preorder(Node *root) {
if (root == NULL) return;
printf("%d ", root->data);
preorder(root->left);
preorder(root->right);
}
// 中序遍历:左 -> 根 -> 右(结果为升序)
void inorder(Node *root) {
if (root == NULL) return;
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
// 后序遍历:左 -> 右 -> 根
void postorder(Node *root) {
if (root == NULL) return;
postorder(root->left);
postorder(root->right);
printf("%d ", root->data);
}
7. 完整示例与测试
下面给出一个完整的 C 语言程序,演示二叉搜索树的创建、插入、查找、删除和遍历操作。
c
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *left;
struct Node *right;
} Node;
Node* createNode(int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
Node* insert(Node *root, int data) {
if (root == NULL) {
return createNode(data);
}
if (data < root->data) {
root->left = insert(root->left, data);
} else if (data > root->data) {
root->right = insert(root->right, data);
}
return root;
}
Node* search(Node *root, int target) {
if (root == NULL || root->data == target) {
return root;
}
if (target < root->data) {
return search(root->left, target);
}
return search(root->right, target);
}
Node* findMin(Node *root) {
while (root->left != NULL) {
root = root->left;
}
return root;
}
Node* deleteNode(Node *root, int target) {
if (root == NULL) {
return root;
}
if (target < root->data) {
root->left = deleteNode(root->left, target);
} else if (target > root->data) {
root->right = deleteNode(root->right, target);
} else {
if (root->left == NULL) {
Node *temp = root->right;
free(root);
return temp;
} else if (root->right == NULL) {
Node *temp = root->left;
free(root);
return temp;
}
Node *temp = findMin(root->right);
root->data = temp->data;
root->right = deleteNode(root->right, temp->data);
}
return root;
}
void inorder(Node *root) {
if (root == NULL) return;
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
void freeTree(Node *root) {
if (root == NULL) return;
freeTree(root->left);
freeTree(root->right);
free(root);
}
int main() {
Node *root = NULL;
int values[] = {50, 30, 70, 20, 40, 60, 80};
int n = sizeof(values) / sizeof(values[0]);
// 构建二叉搜索树
for (int i = 0; i < n; i++) {
root = insert(root, values[i]);
}
printf("中序遍历结果:");
inorder(root);
printf("\n");
// 查找操作
Node *found = search(root, 40);
if (found != NULL) {
printf("查找 40:找到\n");
} else {
printf("查找 40:未找到\n");
}
// 删除操作
root = deleteNode(root, 30);
printf("删除 30 后中序遍历:");
inorder(root);
printf("\n");
// 释放内存
freeTree(root);
return 0;
}
8. 复杂度分析与总结
二叉搜索树的性能取决于树的高度:
| 操作 | 平均时间复杂度 | 最坏时间复杂度 |
|---|---|---|
| 查找 | O(log n) | O(n) |
| 插入 | O(log n) | O(n) |
| 删除 | O(log n) | O(n) |
当二叉搜索树退化为一条链时(例如按升序插入数据),其性能会退化为 O(n)。为了解决这一问题,后续发展出了 AVL 树、红黑树等自平衡二叉搜索树,它们通过旋转操作保持树的高度平衡,从而保证操作始终在 O(log n) 时间内完成。
二叉搜索树是数据结构学习中的重要基石,理解其原理和实现,对于掌握更复杂的平衡树结构大有裨益。