【数据结构与算法】第20篇:二叉树的链式存储与四种遍历(前序、中序、后序、层序)

一、二叉树的链式存储

1.1 节点结构

每个节点包含三部分:

  • 数据域

  • 左孩子指针

  • 右孩子指针

c

复制代码
typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode, *Tree;

1.2 创建节点

c

复制代码
TreeNode* createNode(int value) {
    TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
    if (node == NULL) {
        printf("内存分配失败\n");
        return NULL;
    }
    node->data = value;
    node->left = NULL;
    node->right = NULL;
    return node;
}

1.3 构建二叉树

为了方便测试,我们先手动构建一棵二叉树:

text

复制代码
        1
       / \
      2   3
     / \   \
    4   5   6

c

复制代码
TreeNode* buildTree() {
    TreeNode *root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);
    root->right->right = createNode(6);
    return root;
}

二、二叉树的递归遍历

2.1 前序遍历(根→左→右)

访问顺序:先访问根节点,再遍历左子树,最后遍历右子树。

text

复制代码
示例树的前序:1 → 2 → 4 → 5 → 3 → 6

c

复制代码
void preorder(TreeNode *root) {
    if (root == NULL) return;
    printf("%d ", root->data);      // 访问根
    preorder(root->left);           // 遍历左子树
    preorder(root->right);          // 遍历右子树
}

递归调用栈(以前序为例):

text

复制代码
preorder(1)
  ├─ 打印1
  ├─ preorder(2)
  │    ├─ 打印2
  │    ├─ preorder(4)
  │    │    ├─ 打印4
  │    │    ├─ preorder(NULL)
  │    │    └─ preorder(NULL)
  │    └─ preorder(5)
  │         ├─ 打印5
  │         └─ ...
  └─ preorder(3)
       ├─ 打印3
       └─ preorder(6)
            └─ ...

2.2 中序遍历(左→根→右)

访问顺序:先遍历左子树,再访问根节点,最后遍历右子树。

text

复制代码
示例树的中序:4 → 2 → 5 → 1 → 3 → 6

c

复制代码
void inorder(TreeNode *root) {
    if (root == NULL) return;
    inorder(root->left);            // 遍历左子树
    printf("%d ", root->data);      // 访问根
    inorder(root->right);           // 遍历右子树
}

2.3 后序遍历(左→右→根)

访问顺序:先遍历左子树,再遍历右子树,最后访问根节点。

text

复制代码
示例树的后序:4 → 5 → 2 → 6 → 3 → 1

c

复制代码
void postorder(TreeNode *root) {
    if (root == NULL) return;
    postorder(root->left);          // 遍历左子树
    postorder(root->right);         // 遍历右子树
    printf("%d ", root->data);      // 访问根
}

三、层序遍历(广度优先)

3.1 算法思路

层序遍历按树的层次从上到下、从左到右访问节点。需要借助队列

  1. 根节点入队

  2. 当队列不为空时:

    • 出队一个节点,访问它

    • 如果左孩子存在,左孩子入队

    • 如果右孩子存在,右孩子入队

text

复制代码
示例树的层序:1 → 2 → 3 → 4 → 5 → 6

3.2 队列实现

c

复制代码
// 队列节点
typedef struct QueueNode {
    TreeNode *node;
    struct QueueNode *next;
} QueueNode;

typedef struct {
    QueueNode *front;
    QueueNode *rear;
} Queue;

void initQueue(Queue *q) {
    q->front = q->rear = NULL;
}

int isEmpty(Queue *q) {
    return q->front == NULL;
}

void enqueue(Queue *q, TreeNode *node) {
    QueueNode *newNode = (QueueNode*)malloc(sizeof(QueueNode));
    newNode->node = node;
    newNode->next = NULL;
    if (isEmpty(q)) {
        q->front = q->rear = newNode;
    } else {
        q->rear->next = newNode;
        q->rear = newNode;
    }
}

TreeNode* dequeue(Queue *q) {
    if (isEmpty(q)) return NULL;
    QueueNode *temp = q->front;
    TreeNode *node = temp->node;
    q->front = q->front->next;
    if (q->front == NULL) q->rear = NULL;
    free(temp);
    return node;
}

3.3 层序遍历实现

c

复制代码
void levelOrder(TreeNode *root) {
    if (root == NULL) return;
    
    Queue q;
    initQueue(&q);
    enqueue(&q, root);
    
    while (!isEmpty(&q)) {
        TreeNode *cur = dequeue(&q);
        printf("%d ", cur->data);
        
        if (cur->left != NULL) {
            enqueue(&q, cur->left);
        }
        if (cur->right != NULL) {
            enqueue(&q, cur->right);
        }
    }
}

四、完整代码演示

c

复制代码
#include <stdio.h>
#include <stdlib.h>

// 二叉树节点
typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

// 队列节点
typedef struct QueueNode {
    TreeNode *node;
    struct QueueNode *next;
} QueueNode;

typedef struct {
    QueueNode *front;
    QueueNode *rear;
} Queue;

// 二叉树操作
TreeNode* createNode(int value) {
    TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
    if (node == NULL) return NULL;
    node->data = value;
    node->left = NULL;    node->right = NULL;
    return node;
}

TreeNode* buildTree() {
    TreeNode *root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);
    root->right->right = createNode(6);
    return root;
}

// 队列操作
void initQueue(Queue *q) {
    q->front = q->rear = NULL;
}

int isEmpty(Queue *q) {
    return q->front == NULL;
}

void enqueue(Queue *q, TreeNode *node) {
    QueueNode *newNode = (QueueNode*)malloc(sizeof(QueueNode));
    newNode->node = node;
    newNode->next = NULL;
    if (isEmpty(q)) {
        q->front = q->rear = newNode;
    } else {
        q->rear->next = newNode;
        q->rear = newNode;
    }
}

TreeNode* dequeue(Queue *q) {
    if (isEmpty(q)) return NULL;
    QueueNode *temp = q->front;
    TreeNode *node = temp->node;
    q->front = q->front->next;
    if (q->front == NULL) q->rear = NULL;
    free(temp);
    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;
    
    Queue q;
    initQueue(&q);
    enqueue(&q, root);
    
    while (!isEmpty(&q)) {
        TreeNode *cur = dequeue(&q);
        printf("%d ", cur->data);
        
        if (cur->left != NULL) {
            enqueue(&q, cur->left);
复制代码
enqueue(&q, cur->left);
        }
        if (cur->right != NULL) {
            enqueue(&q, cur->right);
        }
    }
}

// 释放二叉树
void freeTree(TreeNode *root) {
    if (root == NULL) return;
    freeTree(root->left);
    freeTree(root->right);
    free(root);
}

int main() {
    TreeNode *root = buildTree();
    
    printf("二叉树结构:\n");
    printf("        1\n");
    printf("       / \\\n");
    printf("      2   3\n");
    printf("     / \\   \\\n");
    printf("    4   5   6\n\n");
    
    printf("前序遍历(根左右): ");
    preorder(root);
    printf("\n");
    
    printf("中序遍历(左根右): ");
    inorder(root);
    printf("\n");
    
    printf("后序遍历(左右根): ");
    postorder(root);
    printf("\n");
    
    printf("层序遍历: ");
    levelOrder(root);
    printf("\n");
    
    freeTree(root);
    return 0;
}

运行结果:

text

复制代码
二叉树结构:
        1
       / \
      2   3
     / \   \
    4   5   6

前序遍历(根左右): 1 2 4 5 3 6 
中序遍历(左根右): 4 2 5 1 3 6 
后序遍历(左右根): 4 5 2 6 3 1 
层序遍历: 1 2 3 4 5 6 

五、四种遍历的对比总结

遍历方式 访问顺序 示例结果 应用场景
前序 根→左→右 1,2,4,5,3,6 复制树、求表达式前缀
中序 左→根→右 4,2,5,1,3,6 二叉搜索树输出有序序列
后序 左→右→根 4,5,2,6,3,1 删除树、求表达式后缀
层序 按层从左到右 1,2,3,4,5,6 广度优先搜索、树的可视化

六、递归与栈的关系

递归遍历的本质是利用了系统调用栈

text

复制代码
前序遍历的递归调用过程:
preorder(1)
  ├─ 打印1
  ├─ preorder(2)
  │    ├─ 打印2
  │    ├─ preorder(4)
  │    │    └─ 打印4
  │    └─ preorder(5)
  │         └─ 打印5
  └─ preorder(3)
       ├─ 打印3
       └─ preorder(6)
            └─ 打印6

每层递归调用都会把当前状态压栈,返回时弹出。理解这个过程对掌握递归非常重要。


七、复杂度分析

遍历方式 时间复杂度 空间复杂度
前序/中序/后序 O(n) O(h)(h为树高,最坏O(n))
层序 O(n) O(w)(w为最大宽度,最坏O(n))

八、小结

这一篇我们实现了二叉树的链式存储和四种遍历:

要点 说明
链式存储 节点包含data、left、right
前序遍历 根→左→右,递归实现
中序遍历 左→根→右,递归实现
后序遍历 左→右→根,递归实现
层序遍历 借助队列,按层访问

递归三要素

  1. 终止条件(root == NULL)

  2. 处理当前层

  3. 递归调用左右子树

下一篇我们讲由遍历序列重构二叉树。


九、思考题

  1. 已知前序遍历序列 1 2 4 5 3 6,中序遍历序列 4 2 5 1 3 6,如何还原二叉树?

  2. 递归遍历的空间复杂度为什么是O(h)?最坏情况下是多少?

  3. 如何用栈实现非递归的前序遍历?

  4. 层序遍历中,如何区分每一层?(即按层输出,而不是一行输出所有)

欢迎在评论区讨论你的答案。

相关推荐
顶点多余2 小时前
POSIX信号量+生产消费模型应用+环形缓冲区实现
linux·c++
_MyFavorite_2 小时前
JAVA重点基础、进阶知识及易错点总结(17)线程安全 & synchronized 同步锁
java·开发语言·安全
_MyFavorite_2 小时前
JAVA重点基础、进阶知识及易错点总结(13)File 类 + 路径操作
java·开发语言
不会写DN2 小时前
如何使用PHP创建图像验证码
android·开发语言·php
m0_564876842 小时前
提示词工程Zero-Shot、One-Shot、Few-Shot
人工智能·深度学习·学习
菜菜的顾清寒2 小时前
力扣HOT100(16)除了自身以外数组的乘积
算法·leetcode·职场和发展
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章45-圆卡尺
图像处理·人工智能·opencv·算法·计算机视觉
禾小西2 小时前
深入理解 Java String:从底层原理到高性能优化实战
java·开发语言·性能优化
￰meteor2 小时前
【函数指针】
c++