二叉树是一种非常重要的数据结构,它的结构可以用链式存储方式来实现。
下面我们介绍一下如何使用C语言来实现一个二叉树,并提供了相应的代码注解。
#include <stdio.h>
#include <stdlib.h>
// 节点结构体
struct node {
int data; // 节点数据
struct node* left; // 左子树指针
struct node* right; // 右子树指针
};
// 新建节点
struct node* new_node(int data) {
struct node* node = (struct node*) malloc(sizeof(struct node)); // 为新节点分配内存空间
node->data = data; // 设置新节点数据
node->left = NULL; // 左子树指针置空
node->right = NULL; // 右子树指针置空
return node; // 返回新节点指针
}
// 插入节点
struct node* insert_node(struct node* node, int data) {
if (node == NULL) { // 如果当前节点为空
return new_node(data); // 则创建一个新节点
} else if (data <= node->data) { // 如果待插入数据小于当前节点数据
node->left = insert_node(node->left, data); // 则插入到左子树
} else { // 否则插入到右子树
node->right = insert_node(node->right, data);
}
return node; // 返回当前节点
}
// 查找节点
struct node* find_node(struct node* node, int data) {
if (node == NULL) { // 如果当前节点为空,则返回 NULL
return NULL;
} else if (node->data == data) { // 如果找到了对应的节点
return node; // 则返回该节点指针
} else if (data <= node->data) { // 否则继续在左子树中查找
return find_node(node->left, data);
} else { // 继续在右子树中查找
return find_node(node->right, data);
}
}
// 中序遍历二叉树
void inorder_traversal(struct node* node) {
if (node != NULL) {
inorder_traversal(node->left); // 遍历左子树
printf("%d ", node->data); // 输出当前节点数据
inorder_traversal(node->right); // 遍历右子树
}
}
// 主函数
int main() {
struct node* root = NULL; // 根节点指针置空
root = insert_node(root, 50); // 插入节点
insert_node(root, 30);
insert_node(root, 20);
insert_node(root, 40);
insert_node(root, 70);
insert_node(root, 60);
insert_node(root, 80);
printf("Inorder traversal: ");
inorder_traversal(root); // 中序遍历二叉树
struct node* found_node = find_node(root, 70);
if (found_node != NULL) {
printf("\nFound node with data %d\n", found_node->data); // 输出找到的节点数据
} else {
printf("\nNode not found\n"); // 没有找到对应节点
}
return 0; // 程序正常结束
}
在上述代码中,我们定义了一个 struct node
结构体来表示二叉树的节点,其中包含节点的数据 data
,左子树指针 left
和右子树指针 right
。接着定义了 new_node()
函数来创建一个新节点,该函数分配内存空间并初始化节点的数据和指针。然后定义了 insert_node()
函数来插入节点,该函数通过比较当前节点的数据与待插入数据的大小来决定节点应该插入到左子树还是右子树中,插入完成后返回当前节点。接着定义了 find_node()
函数来查找节点,该函数通过比较当前节点的数据与待查找数据的大小来决定在左子树还是右子树中查找,如果找到了对应的节点,则返回该节点的指针;否则返回 NULL。最后,我们定义了 inorder_traversal()
函数来中序遍历二叉树,该函数先遍历左子树,然后输出当前节点数据,最后遍历右子树。在主函数中,我们首先创建一个根节点指针 root
,然后依次插入节点。接着调用 inorder_traversal()
函数来中序遍历二叉树,并输出结果。最后,我们调用 find_node()
函数来查找数据为 70 的节点,并输出结果。
接下来,我们将进一步完善二叉树的实现,添加节点的删除和销毁操作。
节点的删除操作
在二叉树中删除一个节点需要考虑多种情况,包括删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。在这里我们分别介绍这三种情况的处理方法。
删除叶子节点
如果待删除的节点是叶子节点,那么直接将其删除即可。代码如下:
struct node *delete_node(struct node *root, int data) {
if (root == NULL) {
return NULL;
}
if (data < root->data) {
root->left = delete_node(root->left, data);
} else if (data > root->data) {
root->right = delete_node(root->right, data);
} else {
if (root->left == NULL && root->right == NULL) {
free(root);
root = NULL;
}
}
return root;
}
在上述代码中,我们首先判断根节点是否为空。如果为空,则直接返回 NULL。如果待删除数据小于当前节点数据,那么递归地调用 delete_node() 函数来删除左子树中的节点。如果待删除数据大于当前节点数据,那么递归地调用 delete_node() 函数来删除右子树中的节点。否则,如果当前节点没有左子树也没有右子树,说明当前节点就是待删除的叶子节点,直接释放当前节点所占的内存空间即可。
删除只有一个子节点的节点
如果待删除的节点只有一个子节点,那么直接将其子节点连接到父节点上即可。代码如下:
struct node *delete_node(struct node *root, int data) {
if (root == NULL) {
return NULL;
}
if (data < root->data) {
root->left = delete_node(root->left, data);
} else if (data > root->data) {
root->right = delete_node(root->right, data);
} else {
if (root->left == NULL) {
struct node *temp = root->right;
free(root);
root = temp;
} else if (root->right == NULL) {
struct node *temp = root->left;
free(root);
root = temp;
}
}
return root;
}
在上述代码中,如果待删除数据小于当前节点数据,那么递归地调用 delete_node() 函数来删除左子树中的节点。如果待删除数据大于当前节点数据,那么递归地调用 delete_node() 函数来删除右子树中的节点。否则,如果当前节点只有左子树或右子树,那么用其子节点替换当前节点即可。
删除有两个子节点的节点
如果待删除的节点有两个子节点,那么需要找到其左子树中最大的节点或右子树中最小的节点来替换当前节点。我们先来介绍找到左子树中最大节点的情况。其实这种情况是左子树中最大节点没有右子树的情况。具体实现如下:
struct node *delete_node(struct node *root, int data) {
if (root == NULL) {
return NULL;
}
if (data < root->data) {
root->left = delete_node(root->left, data);
} else if (data > root->data) {
root->right = delete_node(root->right, data);
} else {
if (root->left != NULL && root->right != NULL) {
struct node *temp = root->left;
while (temp->right != NULL) {
temp = temp->right;
}
root->data = temp->data;
root->left = delete_node(root->left, temp->data);
} else {
struct node *temp = root;
if (root->left != NULL) {
root = root->left;
} else {
root = root->right;
}
free(temp);
}
}
return root;
}
在上述代码中,我们首先判断根节点是否为空。如果为空,则直接返回 NULL。如果待删除数据小于当前节点数据,那么递归地调用 delete_node() 函数来删除左子树中的节点。如果待删除数据大于当前节点数据,那么递归地调用 delete_node() 函数来删除右子树中的节点。否则,如果当前节点有两个子节点,那么找到其左子树中最大的节点,用其值替换当前节点的值,然后递归地删除左子树中的最大节点。如果当前节点只有一个子节点或没有子节点,那么直接用其子节点或 NULL 替换当前节点即可。
节点的销毁操作
最后,我们需要在程序结束时销毁二叉树,释放所有节点所占用的内存空间。代码如下:
void destroy_tree(struct node *root) {
if (root != NULL) {
destroy_tree(root->left);
destroy_tree(root->right);
free(root);
}
}
在上述代码中,我们递归地遍历二叉树,并依次释放每个节点所占用的内存空间。
现在,我们已经完成了二叉树的实现,包括节点的插入、查找、删除和销毁等操作。
完整代码如下:
#include <stdio.h>
#include <stdlib.h>
struct node {
int data;
struct node *left;
struct node *right;
};
struct node *create_node(int data) {
struct node *new_node = (struct node*) malloc(sizeof(struct node));
new_node->data = data;
new_node->left = NULL;
new_node->right = NULL;
return new_node;
}
struct node *insert_node(struct node *root, int data) {
if (root == NULL) {
return create_node(data);
}
if (data < root->data) {
root->left = insert_node(root->left, data);
} else if (data > root->data) {
root->right = insert_node(root->right, data);
}
return root;
}
struct node *search_node(struct node *root, int data) {
if (root == NULL || root->data == data) {
return root;
}
if (data < root->data) {
return search_node(root->left, data);
} else {
return search_node(root->right, data);
}
}
struct node *delete_node(struct node *root, int data) {
if (root == NULL) {
return NULL;
}
if (data < root->data) {
root->left = delete_node(root->left, data);
} else if (data > root->data) {
root->right = delete_node(root->right, data);
} else {
if (root->left != NULL && root->right != NULL) {
struct node *temp = root->left;
while (temp->right != NULL) {
temp = temp->right;
}
root->data = temp->data;
root->left = delete_node(root->left, temp->data);
} else {
struct node *temp = root;
if (root->left != NULL) {
root = root->left;
} else {
root = root->right;
}
free(temp);
}
}
return root;
}
void inorder_traversal(struct node *root) {
if (root != NULL) {
inorder_traversal(root->left);
printf("%d ", root->data);
inorder_traversal(root->right);
}
}
void destroy_tree(struct node *root) {
if (root != NULL) {
destroy_tree(root->left);
destroy_tree(root->right);
free(root);
}
}
int main() {
struct node *root = NULL;
root = insert_node(root, 5);
insert_node(root, 3);
insert_node(root, 7);
insert_node(root, 1);
insert_node(root, 4);
insert_node(root, 6);
insert_node(root, 8);
inorder_traversal(root);
printf("\n");
delete_node(root, 5);
inorder_traversal(root);
printf("\n");
destroy_tree(root);
return 0;
}
在上述代码中,我们首先定义了节点结构体,包括节点的数据值、左子节点和右子节点。然后,我们定义了创建节点、插入节点、查找节点、删除节点、中序遍历、销毁二叉树等函数,并分别进行了实现。
在 main() 函数中,我们首先创建了一个空的二叉树 root,然后依次插入了 5、3、7、1、4、6、8 这几个节点,并对二叉树进行了中序遍历,打印出了二叉树中所有节点的值。
接下来,我们删除了值为 5 的节点,并再次对二叉树进行中序遍历,打印出了删除节点后的二叉树所有节点的值。
最后,我们调用 destroy_tree() 函数销毁了整个二叉树,释放了所有的内存空间。