在这篇文章中,我们将详细探讨一个二叉树的实现,包括节点的创建、插入、遍历、删除和销毁等基本操作。代码采用C语言编写,使用结构体定义节点,并通过指针实现树的操作。
1. 数据结构定义
首先,我们定义一个节点结构体 `Node`,它包含三个成员:
typedef struct Node {
int data; // 节点存储的数据
struct Node* l_child; // 指向左子节点的指针
struct Node* r_child; // 指向右子节点的指针
}*node_ptr;
这里,`data`用于存储节点的值,`l_child`和`r_child`分别指向该节点的左子节点和右子节点。
2. 节点创建
节点的创建通过 `creat_node` 函数实现:
node_ptr creat_node(int value) {
node_ptr p = (node_ptr)malloc(sizeof(Node));
if (p == NULL) {
printf("node_ptr error\n");
return NULL;
}
p->data = value;
p->l_child = NULL;
p->r_child = NULL;
return p;
}
在这个函数中,我们使用 `malloc` 动态分配内存。如果内存分配成功,节点的 `data` 被赋值,左右子节点指针被初始化为 `NULL`。
3. 插入节点
插入节点的逻辑在 `insert_node` 函数中实现:
node_ptr insert_node(Node *& root, int value) {
if (root == NULL) {
root = creat_node(value);
printf("%d 插入成功\n", value);
}
else if (value < root->data) {
root->l_child = insert_node(root->l_child, value);
printf("%d 插入成功\n", value);
}
else if(value > root->data) {
root->r_child = insert_node(root->r_child, value);
printf("%d 插入成功\n", value);
}
return root;
}
这个函数采用递归方式插入节点。首先检查当前根节点是否为空,如果为空,则创建新节点并赋值。如果不为空,则比较待插入的值与当前节点的值,决定向左子树或右子树插入。
4. 遍历树
我们实现了三种遍历方式:前序遍历、中序遍历和后序遍历。
前序遍历
void preorder_traversal(Node *& root) {
if (root != NULL) {
printf("前序遍历:%d\n", root->data);
preorder_traversal(root->l_child);
preorder_traversal(root->r_child);
}
}
前序遍历的顺序是:根 -> 左 -> 右。
中序遍历
void inorder_traversal(Node *& root) {
if (root != NULL) {
inorder_traversal(root->l_child);
printf("中序遍历:%d\n", root->data);
inorder_traversal(root->r_child);
}
}
中序遍历的顺序是:左 -> 根 -> 右。
后序遍历
void postorder_traversal(Node *& root) {
if (root != NULL) {
postorder_traversal(root->l_child);
postorder_traversal(root->r_child);
printf("后序遍历:%d\n", root->data);
}
}
后序遍历的顺序是:左 -> 右 -> 根。
5. 删除节点
删除节点的逻辑在 `delete_node` 函数中实现:
node_ptr delete_node(Node *& root, int value) {
if (root == NULL) {
return root;
}
if (value < root->data) {
root->l_child = delete_node(root->l_child, value);
} else if(value > root->data) {
root->r_child = delete_node(root->r_child, value);
} else {
// 节点找到了
if (root->l_child == NULL) {
node_ptr temp = root->r_child;
free(root);
return temp;
} else if (root->r_child == NULL) {
node_ptr temp = root->l_child;
free(root);
return temp;
}
node_ptr temp = find_min(root->r_child);
root->data = temp->data;
root->r_child = delete_node(root->r_child, temp->data);
}
return root;
}
删除节点的步骤包括:找到要删除的节点,处理三种情况(节点没有子节点、只有一个子节点、两个子节点)。
处理具有两个非空子节点的节点:
在二叉搜索树(BST)中,当我们需要删除一个具有两个非空子节点的节点时,我们需要找到一个合适的节点来替换它,以保持二叉搜索树的性质,即对于树中的每个节点,其左子树的所有节点的值都小于该节点的值,而其右子树的所有节点的值都大于该节点的值。
选择右子树中的最小节点(后继节点)作为替换节点的原因如下:
-
保持顺序:后继节点是紧跟在被删除节点之后的下一个较大节点。因此,用后继节点的值替换被删除节点的值可以保持树的有序性。
-
简化操作:后继节点保证在其左子树中没有节点(因为它是最小的),这意味着它最多只有一个右子节点。因此,在替换之后,我们可以安全地删除后继节点,而不需要再次处理复杂的子树结构。
-
避免破坏树结构:如果我们选择左子树中的最大节点(前驱节点)来替换,虽然也可以保持树的性质,但在某些情况下可能会导致树的不平衡。而选择后继节点通常会导致更平衡的树结构。
具体操作步骤如下:
- 找到要删除的节点。
- 找到该节点的右子树中的最小节点(后继节点)。
- 将后继节点的值复制到要删除的节点。
- 在右子树中递归删除后继节点。由于后继节点已经被复制到了原来的位置,现在删除后继节点不会影响树的性质。
这样的操作保证了在删除节点后,整个二叉搜索树仍然保持有序,同时也简化了删除操作,因为后继节点最多只有一个子节点,使得删除后继节点变得相对简单。
6. 查找最小节点
查找最小节点的函数如下:
node_ptr find_min(Node *& root) {
while (root->l_child != NULL) {
root = root->l_child;
}
return root;
}
通过不断向左子树移动,找到最小值节点。
7. 释放树的内存
最后,我们实现了一个释放树内存的函数:
void free_tree(Node* root) {
if (root != NULL) {
free_tree(root->l_child);
free_tree(root->r_child);
free(root);
}
}
这个函数通过递归的方式释放每个节点的内存,避免内存泄漏。
结论
通过上述实现,我们构建了一个基本的二叉树结构,支持节点的插入、删除和遍历等操作。这些基础操作为更复杂的数据结构和算法奠定了基础,理解这些基本概念对学习更高级的数据结构非常重要。希望这篇文章能帮助你更好地理解二叉树及其操作。
Lk.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef struct Node {
int data;
struct Node* l_child;
struct Node* r_child;
}*node_ptr;
extern node_ptr creat_node(int value);
extern node_ptr insert_node(Node *& root, int value);
extern void preorder_traversal(Node *& root);
extern void inorder_traversal(Node *& root);
extern void postorder_traversal(Node *& root);
extern node_ptr delete_node(Node *& root, int value);
extern node_ptr find_min(Node *& root);
extern void free_tree(Node* root);
Lk.cpp
#include "Lk.h"
node_ptr creat_node(int value)
{
node_ptr p = (node_ptr)malloc(sizeof(Node));
if (p == NULL) {
printf("node_ptr error\n");
return NULL;
}
p->data = value;
p->l_child = NULL;
p->r_child = NULL;
return p;
}
node_ptr insert_node(Node *& root, int value)
{
if (root == NULL) {
root = creat_node(value);
printf("%d 插入成功\n", value);//每层对应的root不一样
}
else if (value < root->data) {
root->l_child = insert_node(root->l_child, value);
printf("%d 插入成功\n", value);
}
else if(value > root->data)
{
root->r_child = insert_node(root->r_child, value);
printf("%d 插入成功\n", value);
}
return root;
}
void preorder_traversal(Node *& root)//根 左 右
{
if (root != NULL) {
printf("前序遍历:%d\n", root->data);
preorder_traversal(root->l_child);
preorder_traversal(root->r_child);
}
}
void inorder_traversal(Node *& root)//左 根 右
{
if (root != NULL) {
preorder_traversal(root->l_child);
printf("中序遍历:%d\n", root->data);
preorder_traversal(root->r_child);
}
}
void postorder_traversal(Node *& root)//左 右 根
{
if (root != NULL) {
preorder_traversal(root->l_child);
preorder_traversal(root->r_child);
printf("后序遍历:%d\n", root->data);
}
}
node_ptr delete_node(Node *& root, int value)
{
if (root == NULL)
{
return root;
}
if (value < root->data) {
root->l_child = delete_node(root->l_child, value);
}else if(value > root->data){
root->r_child = delete_node(root->r_child, value);
}else{
if (root->l_child == NULL) {
node_ptr temp = root->r_child;
free(root);
return temp;
}
else if (root->r_child == NULL) {
node_ptr temp = root->l_child;
free(root);
return temp;
}
node_ptr temp = find_min(root->r_child);
root->data = temp->data;
root->r_child = delete_node(root->r_child, temp->data);
}
return root;
}
node_ptr find_min(Node *& root)
{
while (root->l_child!=NULL)
{
root = root->l_child;
}
return root;
}
void free_tree(Node* root) {
if (root != NULL) {
free_tree(root->l_child);
free_tree(root->r_child);
free(root);
}
}
main.cpp
#include"Lk.h"
int main() {
/*extern node_ptr creat_node(int value);
extern node_ptr insert_node(Node * &root, int value);
extern void preorder_traversal(Node * &root);
extern void inorder_traversal(Node * &root);
extern void postorder_traversal(Node * &root);
extern node_ptr delete_node(Node * &root, int value);
extern node_ptr find_min(Node * &root);
extern void free_tree(Node * root);*/
node_ptr root = NULL;
for(int i=1;i<6;i++)
insert_node(root, i);
printf("删除前:\n");
preorder_traversal(root);
inorder_traversal(root);
postorder_traversal(root);
root = delete_node(root, 1);
root = delete_node(root, 2);
root = delete_node(root, 3);
printf("删除后:\n");
preorder_traversal(root);
inorder_traversal(root);
postorder_traversal(root);
free_tree(root);
return 0;
}
运行结果: