Bootstrap

数据结构:二叉树( Binary tree)及其实现

什么是二叉树?

二叉树是一种树形数据结构,每个节点最多有两个子节点,分别称为 左子节点 和 右子节点。二叉树的特点是:

  1. 每个节点最多有两个子节点

  2. 左子节点和右子节点是有序的,不能随意交换。

举个例子:

      1
     / \
    2   3
   / \
  4   5

这是一个二叉树,根节点是 1,左子节点是 2,右子节点是 32 的左子节点是 4,右子节点是 5


二叉树的存储方式

1. 普通存储(顺序存储)

普通存储是用数组来存储二叉树。数组的下标表示节点的位置,父节点和子节点之间的关系通过下标计算得出。

特点

  • 适合存储完全二叉树。

  • 对于非完全二叉树,会浪费大量空间。

父子节点关系

  • 父节点下标为 i,左子节点下标为 2i + 1,右子节点下标为 2i + 2

  • 子节点下标为 i,父节点下标为 (i - 1) / 2

2. 链式存储

链式存储是用链表来存储二叉树。每个节点包含数据、左子节点指针和右子节点指针。

特点

  • 适合存储任意二叉树。

  • 内存利用率高,但需要额外的指针空间。

3. 线索二叉树

线索二叉树是对链式存储的优化,通过利用空指针指向节点的前驱或后继,方便遍历。

特点

  • 可以快速找到节点的前驱和后继。

  • 适合频繁遍历的场景。


二叉树的遍历

二叉树的遍历是指按照某种顺序访问树中的所有节点。常见的遍历方式有:

  1. 先序遍历:根节点 -> 左子树 -> 右子树。

  2. 中序遍历:左子树 -> 根节点 -> 右子树。

  3. 后序遍历:左子树 -> 右子树 -> 根节点。

  4. 层次遍历:按层次从上到下、从左到右访问节点。


二叉树的增删查改

1. 增加节点

在二叉树中增加节点通常是指在指定位置插入一个新节点。

2. 删除节点

在二叉树中删除节点通常是指删除指定节点,并调整树的结构。

3. 查找节点

在二叉树中查找节点通常是指根据值查找节点。

4. 修改节点

在二叉树中修改节点通常是指修改指定节点的值。


C 语言实现二叉树

以下是二叉树的链式存储实现代码,包含初始化、遍历、增删查改等功能。

#include <stdio.h>
#include <stdlib.h>

// 定义二叉树节点结构体
typedef struct TreeNode {
    int data;                   // 数据
    struct TreeNode *left;      // 左子节点指针
    struct TreeNode *right;     // 右子节点指针
} TreeNode;

// 创建新节点
TreeNode* CreateNode(int data) {
    TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 先序遍历
void PreOrder(TreeNode *root) {
    if (root == NULL) return;
    printf("%d ", root->data);  // 访问根节点
    PreOrder(root->left);       // 遍历左子树
    PreOrder(root->right);      // 遍历右子树
}

// 中序遍历
void InOrder(TreeNode *root) {
    if (root == NULL) return;
    InOrder(root->left);        // 遍历左子树
    printf("%d ", root->data);  // 访问根节点
    InOrder(root->right);       // 遍历右子树
}

// 后序遍历
void PostOrder(TreeNode *root) {
    if (root == NULL) return;
    PostOrder(root->left);      // 遍历左子树
    PostOrder(root->right);     // 遍历右子树
    printf("%d ", root->data);  // 访问根节点
}

// 层次遍历
void LevelOrder(TreeNode *root) {
    if (root == NULL) return;

    TreeNode *queue[100];  // 用队列实现层次遍历
    int front = 0, rear = 0;
    queue[rear++] = root;  // 根节点入队

    while (front < rear) {
        TreeNode *node = queue[front++];  // 出队
        printf("%d ", node->data);        // 访问节点

        if (node->left != NULL) {
            queue[rear++] = node->left;   // 左子节点入队
        }
        if (node->right != NULL) {
            queue[rear++] = node->right;  // 右子节点入队
        }
    }
}

// 插入节点
TreeNode* InsertNode(TreeNode *root, int data) {
    if (root == NULL) {
        return CreateNode(data);  // 创建新节点
    }

    if (data < root->data) {
        root->left = InsertNode(root->left, data);  // 插入到左子树
    } else {
        root->right = InsertNode(root->right, data);  // 插入到右子树
    }

    return root;
}

// 查找节点
TreeNode* FindNode(TreeNode *root, int data) {
    if (root == NULL || root->data == data) {
        return root;  // 找到目标节点或遍历结束
    }

    if (data < root->data) {
        return FindNode(root->left, data);  // 在左子树中查找
    } else {
        return FindNode(root->right, data);  // 在右子树中查找
    }
}

// 删除节点
TreeNode* DeleteNode(TreeNode *root, int data) {
    if (root == NULL) return root;

    if (data < root->data) {
        root->left = DeleteNode(root->left, data);  // 在左子树中删除
    } else if (data > root->data) {
        root->right = DeleteNode(root->right, data);  // 在右子树中删除
    } else {
        // 找到目标节点
        if (root->left == NULL) {
            TreeNode *temp = root->right;
            free(root);
            return temp;  // 只有右子节点
        } else if (root->right == NULL) {
            TreeNode *temp = root->left;
            free(root);
            return temp;  // 只有左子节点
        }

        // 有两个子节点,找到右子树的最小节点
        TreeNode *temp = root->right;
        while (temp->left != NULL) {
            temp = temp->left;
        }

        root->data = temp->data;  // 用最小节点的值替换当前节点
        root->right = DeleteNode(root->right, temp->data);  // 删除最小节点
    }

    return root;
}

int main() {
    TreeNode *root = NULL;

    // 插入节点
    root = InsertNode(root, 5);
    root = InsertNode(root, 3);
    root = InsertNode(root, 7);
    root = InsertNode(root, 2);
    root = InsertNode(root, 4);
    root = InsertNode(root, 6);
    root = InsertNode(root, 8);

    // 遍历二叉树
    printf("先序遍历:");
    PreOrder(root);
    printf("\n");

    printf("中序遍历:");
    InOrder(root);
    printf("\n");

    printf("后序遍历:");
    PostOrder(root);
    printf("\n");

    printf("层次遍历:");
    LevelOrder(root);
    printf("\n");

    // 查找节点
    TreeNode *target = FindNode(root, 4);
    if (target != NULL) {
        printf("找到节点:%d\n", target->data);
    } else {
        printf("未找到节点\n");
    }

    // 删除节点
    root = DeleteNode(root, 5);
    printf("删除节点后的中序遍历:");
    InOrder(root);
    printf("\n");

    return 0;
}

二叉树的使用场景

  1. 搜索树:用于快速查找、插入和删除数据。

  2. 表达式树:用于表示数学表达式。

  3. 哈夫曼树:用于数据压缩。


总结

二叉树是一种非常重要的数据结构,适合表示层次关系和递归结构。通过学习二叉树的存储、遍历和操作,你可以更好地理解树形数据结构的原理和应用。希望通过这篇文章,你能轻松掌握二叉树的相关知识!

;