在本教程中,您将学习二进制搜索树的工作原理。此外,您还将找到C中二叉搜索树的示例。
二叉搜索树是一种数据结构,它可以让我们快速地维护一个排序的数字列表。
- 它被称为二叉树,因为每个树节点最多有两个子节点。
- 它被称为搜索树,因为它可以在O(log(n))的时间内,用来搜索数字的存在。
将二叉搜索树与常规二叉树分开的属性是:
- 左子树的所有节点都小于根节点
- 右子树的所有节点都大于根节点
- 每个节点的两个子树也是二叉搜索树,即它们具有上述两个属性
右边的二叉树不是二叉搜索树,因为节点“3”的右边子树包含一个小于它的值。
有两种基本操作可以在二叉搜索树上执行:搜索和插入。
搜索操作
该算法依赖于二叉搜索树的性质,即如果每个左子树的值低于根,而每个右子树的值高于根。
如果值比根值小,我们可以确定值不在右子树中,我们只需要在左子树中搜索;如果值比根值大,我们可以确定值不在左子树中,我们只需要在右子树中搜索。
算法
If root == NULL
return NULL;
If number == root->data
return root->data;
If number < root->data
return search(root->left)
If number > root->data
return search(root->right)
让我们尝试用图表将其可视化。
找不到4,因此遍历8的左子树
依然找不到4,遍历3的右子树
找不到4,因此遍历6的左子树
找到4
如果找到该值,我们将返回该值,以便它在每个递归步骤中传播,如下图所示。
您可能已经注意到,我们已经调用了 return search(struct node*) 四次。当我们得到新节点或NULL值时,值会一次一次返回,直到search(root)返回最终结果。
如果在任何子树中找到该值,则向上传递该值,以便最终返回该值,否则返回null。
如果没有找到该值,我们最终会到达一个叶节点的左或右子节点,该节点为NULL,它会被传递并返回。
插入操作
在正确的位置插入一个值类似于搜索,因为我们试图保持左子树小于根值,右子树大于根值的规则。
根据值的不同,我们转到右子树或左子树,当到达左子树或右子树为空的点时,我们将新节点放在那里。
算法
If node == NULL
return createNode(data)
if (data < node->data)
node->left = insert(node->left, data);
else if (data > node->data)
node->right = insert(node->right, data);
return node;
算法并不像看上去那么简单。让我们试着想象一下如何将一个数字添加到现有的二叉搜索树中。
4<8,因此需要将4放到8的左子树。
4>3,因此将4放到3的右子树
4<6,因此将4放到6的左子树
将4插入到6的左子树
我们已经附加了节点,但是我们仍然必须退出函数,同时不对树的其余部分造成任何损坏。因此 return node; 在末尾派上了用场。在node等于NULL的情况下,返回新创建的节点并将其附加到父节点。否则,在返回到根节点之前,将返回未经更改的节点。
这确保了当我们向上移动树时,其他节点连接不会改变。
该图显示了最后返回根元素的重要性,这样根元素才能在向上递归步骤中不丢失其位置。
删除操作
从二叉搜索树中删除节点的情况有三种。
第一种
在第一种情况下,要删除的节点是叶节点。在这种情况下,只需从树中删除节点。
4将被删除
删除节点
第二种
在第二种情况下,要删除的节点有一个子节点。在这种情况下,请遵循以下步骤:
- 将该节点替换为其子节点。
- 删除原始位置的子节点。
6将被删除
将其子节点的值复制到节点并删除子节点
最终树
第三种
在第三种情况下,要删除的节点有两个子节点。在这种情况下,请遵循以下步骤:
- 获取该节点的顺序继承节点。
- 用顺序继承节点替换节点。
- 移除原始位置的顺序继承节点。
3将被删除
将顺序继承节点(4)的值复制到节点
删除顺序继承节点
C示例
// Binary Search Tree operations in C
#include <stdio.h>
#include <stdlib.h>
struct node {
int key;
struct node *left, *right;
};
// Create a node
struct node *newNode(int item) {
struct node *temp = (struct node *)malloc(sizeof(struct node));
temp->key = item;
temp->left = temp->right = NULL;
return temp;
}
// Inorder Traversal
void inorder(struct node *root) {
if (root != NULL) {
// Traverse left
inorder(root->left);
// Traverse root
printf("%d -> ", root->key);
// Traverse right
inorder(root->right);
}
}
// Insert a node
struct node *insert(struct node *node, int key) {
// Return a new node if the tree is empty
if (node == NULL) return newNode(key);
// Traverse to the right place and insert the node
if (key < node->key)
node->left = insert(node->left, key);
else
node->right = insert(node->right, key);
return node;
}
// Find the inorder successor
struct node *minValueNode(struct node *node) {
struct node *current = node;
// Find the leftmost leaf
while (current && current->left != NULL)
current = current->left;
return current;
}
// Deleting a node
struct node *deleteNode(struct node *root, int key) {
// Return if the tree is empty
if (root == NULL) return root;
// Find the node to be deleted
if (key < root->key)
root->left = deleteNode(root->left, key);
else if (key > root->key)
root->right = deleteNode(root->right, key);
else {
// If the node is with only one child or no child
if (root->left == NULL) {
struct node *temp = root->right;
free(root);
return temp;
} else if (root->right == NULL) {
struct node *temp = root->left;
free(root);
return temp;
}
// If the node has two children
struct node *temp = minValueNode(root->right);
// Place the inorder successor in position of the node to be deleted
root->key = temp->key;
// Delete the inorder successor
root->right = deleteNode(root->right, temp->key);
}
return root;
}
// Driver code
int main() {
struct node *root = NULL;
root = insert(root, 8);
root = insert(root, 3);
root = insert(root, 1);
root = insert(root, 6);
root = insert(root, 7);
root = insert(root, 10);
root = insert(root, 14);
root = insert(root, 4);
printf("Inorder traversal: ");
inorder(root);
printf("\nAfter deleting 10\n");
root = deleteNode(root, 10);
printf("Inorder traversal: ");
inorder(root);
}
二叉搜索树的复杂度
时间复杂度
操作 | 最佳复杂度 | 平均复杂度 | 最差复杂度 |
---|---|---|---|
查找 | O(log n) | O(log n) | O(n) |
插入 | O(log n) | O(log n) | O(n) |
删除 | O(log n) | O(log n) | O(n) |
这里,n是树中的节点数。
空间复杂度
所有操作的空间复杂度为O(n)。
二叉搜索树的应用
- 用于数据库的多级索引;
- 用于动态排序;
- 用于管理Unix内核中的虚拟内存区域。
参考文档
[1]Parewa Labs Pvt. Ltd.Balanced Binary Tree[EB/OL].https://www.programiz.com/dsa/binary-search-tree,2020-01-01.