链式二叉树 —— 用指针构建的树形世界

🌳 链式二叉树 ------ 用指针构建的树形世界

✅ 专为初学者设计

✅ 图文比喻 + 递归思想讲解

包含完整 C 语言代码(头文件 + 实现 + 测试)

✅ 覆盖:创建、遍历、统计、查找、销毁、层序、完全二叉树判断等核心操作


第一章:为什么用"链"表示二叉树?

数组适合完全二叉树(如堆),但现实中的树往往"歪歪扭扭"------有些结点只有左孩子,有些只有右孩子,甚至中间空一大片。

这时候,链式结构就派上用场了!

链式二叉树的结点结构

每个结点包含三部分:

  • 数据域:存值(比如 1、2、'A')
  • 左指针:指向左孩子
  • 右指针:指向右孩子
c 复制代码
typedef int BTDataType;  // 可根据需要改为 char、double 等

typedef struct BinaryTreeNode {
    struct BinaryTreeNode* left;   // 左孩子
    struct BinaryTreeNode* right;  // 右孩子
    BTDataType val;                // 数据
} BTNode;

💡 这就像每个人手里拿着两张"名片":一张给左娃,一张给右娃。


第二章:手动创建一棵二叉树(先跑起来!)

为了快速入门,我们不从输入建树开始 ,而是手动构造一棵树用于测试。

辅助函数:创建新结点

c 复制代码
BTNode* BuyNode(BTDataType x) {
    BTNode* node = (BTNode*)malloc(sizeof(BTNode));
    if (!node) {
        perror("malloc failed");
        exit(-1);
    }
    node->val = x;
    node->left = node->right = NULL;
    return node;
}

手动建树函数

我们构造如下这棵树:

c 复制代码
        1
       / \
      2   4
     /   / \
    3   5   6
       /
      7
c 复制代码
BTNode* CreateTree() {
    BTNode* n1 = BuyNode(1);
    BTNode* n2 = BuyNode(2);
    BTNode* n3 = BuyNode(3);
    BTNode* n4 = BuyNode(4);
    BTNode* n5 = BuyNode(5);
    BTNode* n6 = BuyNode(6);
    BTNode* n7 = BuyNode(7);

    n1->left = n2;
    n1->right = n4;
    n2->left = n3;
    n4->left = n5;
    n4->right = n6;
    n5->left = n7;

    return n1;  // 返回根结点
}

✅ 这样我们就有了一个"玩具树",可以用来测试各种操作!


第三章:二叉树的遍历 ------ 走遍整棵树

由于二叉树是递归定义 的(根 + 左子树 + 右子树),所以遍历也天然适合用递归实现。

三种深度优先遍历

遍历方式 访问顺序 口诀
前序遍历 根 → 左 → 右 "自己先干,再叫左右"
中序遍历 左 → 根 → 右 "左边干完,自己上,右边收尾"
后序遍历 左 → 右 → 根 "左右都干完,自己最后收工"
完整代码实现(带 NULL 标记便于理解结构)
c 复制代码
// 前序遍历:根 左 右
void PreOrder(BTNode* root) {
    if (root == NULL) {
        printf("N ");
        return;
    }
    printf("%d ", root->val);
    PreOrder(root->left);
    PreOrder(root->right);
}

// 中序遍历:左 根 右
void InOrder(BTNode* root) {
    if (root == NULL) {
        printf("N ");
        return;
    }
    InOrder(root->left);
    printf("%d ", root->val);
    InOrder(root->right);
}

// 后序遍历:左 右 根
void PostOrder(BTNode* root) {
    if (root == NULL) {
        printf("N ");
        return;
    }
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%d ", root->val);
}

🔍 对于上面那棵树,输出结果为:

  • 前序:1 2 3 N N N 4 5 7 N N N 6 N N
  • 中序:N 3 N 2 N 1 N 7 N 5 N 4 N 6 N
  • 后序:N N 3 N N 2 N N N 7 N N 5 N N 6 4 1
    💡 实际应用中,若不需要打印 N,可去掉 if (root == NULL) 中的 printf

第四章:二叉树的常用操作(递归实现)

求结点总数

c 复制代码
int BinaryTreeSize(BTNode* root) {
    if (root == NULL) return 0;
    return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

🧠 思想:当前结点(1个) + 左子树大小 + 右子树大小


求叶子结点数(度为0的结点)

c 复制代码
int BinaryTreeLeafSize(BTNode* root) {
    if (root == NULL) return 0;
    if (root->left == NULL && root->right == NULL)
        return 1;  // 是叶子
    return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

求第 k 层的结点个数

c 复制代码
int BinaryTreeLevelKSize(BTNode* root, int k) {
    if (root == NULL || k < 1) return 0;
    if (k == 1) return 1;  // 当前层就是第k层
    // 否则去左右子树找第 (k-1) 层
    return BinaryTreeLevelKSize(root->left, k - 1) +
           BinaryTreeLevelKSize(root->right, k - 1);
}

求树的高度(深度)

c 复制代码
int BinaryTreeDepth(BTNode* root) {
    if (root == NULL) return 0;
    int leftH = BinaryTreeDepth(root->left);
    int rightH = BinaryTreeDepth(root->right);
    return 1 + (leftH > rightH ? leftH : rightH);
}

⏱️ 时间复杂度:O(n),每个结点访问一次


查找值为 x 的结点

c 复制代码
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
    if (root == NULL) return NULL;
    if (root->val == x) return root;

    // 先在左子树找
    BTNode* leftRet = BinaryTreeFind(root->left, x);
    if (leftRet) return leftRet;

    // 左边没找到,去右边找
    BTNode* rightRet = BinaryTreeFind(root->right, x);
    return rightRet;
}

✅ 返回的是结点地址,可用于后续操作(如修改值)


销毁整棵树(后序释放)

c 复制代码
void BinaryTreeDestroy(BTNode** root) {
    if (*root == NULL) return;
    // 先销毁左右子树
    BinaryTreeDestroy(&(*root)->left);
    BinaryTreeDestroy(&(*root)->right);
    // 再释放自己
    free(*root);
    *root = NULL;  // 防止野指针
}

❗ 注意:传的是 BTNode**,这样才能把外部指针置为 NULL


第五章:层序遍历(广度优先)------ 用队列实现

前中后序是"深度优先",而层序遍历 是"一层一层扫",需要用到队列

队列辅助结构(简版)

假设已有队列实现(支持 QueueInit, QueuePush, QueuePop, QueueFront, QueueEmpty, QueueDestroy),且队列存储 BTNode* 类型。

📌 若你没有队列代码,文末会附上简易队列实现。

层序遍历代码

c 复制代码
void LevelOrder(BTNode* root) {
    if (root == NULL) return;

    Queue q;
    QueueInit(&q);
    QueuePush(&q, root);

    while (!QueueEmpty(&q)) {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        printf("%d ", front->val);  // 访问当前结点

        // 把非空孩子入队
        if (front->left)  QueuePush(&q, front->left);
        if (front->right) QueuePush(&q, front->right);
    }

    QueueDestroy(&q);
}

🌟 输出示例:1 2 4 3 5 6 7(按层从左到右)


第六章:判断是否为完全二叉树

完全二叉树定义

除最后一层外,其他层全满;最后一层结点靠左连续排列,不能有"空洞"。

判断思路(利用层序遍历)

  1. 按层序遍历,遇到 NULL 也入队
  2. 一旦遇到第一个 NULL,后面所有结点都必须是 NULL
  3. 如果后面还有非空结点 → 不是完全二叉树。

代码实现

c 复制代码
bool BinaryTreeComplete(BTNode* root) {
    if (root == NULL) return true;

    Queue q;
    QueueInit(&q);
    QueuePush(&q, root);

    // 步骤1:正常层序,直到遇到第一个 NULL
    while (!QueueEmpty(&q)) {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);

        if (front == NULL) break;

        // 即使孩子是 NULL,也入队(关键!)
        QueuePush(&q, front->left);
        QueuePush(&q, front->right);
    }

    // 步骤2:检查剩余队列是否全是 NULL
    while (!QueueEmpty(&q)) {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        if (front != NULL) {
            QueueDestroy(&q);
            return false;  // 发现非空 → 不是完全二叉树
        }
    }

    QueueDestroy(&q);
    return true;
}

✅ 对于我们构造的树(第5层只有7在最左),它是完全二叉树 吗?

答案:不是 !因为第4层的 n6(6)没有左孩子,但 n5(5)有左孩子(7),导致最后一层不连续。


第七章:完整项目结构(可直接编译运行)

头文件 BinaryTree.h

c 复制代码
// BinaryTree.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "Queue.h"  // 需要队列支持层序遍历

typedef int BTDataType;

typedef struct BinaryTreeNode {
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
    BTDataType val;
} BTNode;

// 创建结点
BTNode* BuyNode(BTDataType x);

// 手动建树(用于测试)
BTNode* CreateTree();

// 遍历
void PreOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root);
void LevelOrder(BTNode* root);

// 统计类
int BinaryTreeSize(BTNode* root);
int BinaryTreeLeafSize(BTNode* root);
int BinaryTreeLevelKSize(BTNode* root, int k);
int BinaryTreeDepth(BTNode* root);

// 查找与销毁
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
void BinaryTreeDestroy(BTNode** root);

// 完全二叉树判断
bool BinaryTreeComplete(BTNode* root);

实现文件 BinaryTree.c(见上文各函数)

将上述所有函数放入此文件。


简易队列实现(Queue.h + Queue.c

若你没有队列,可用以下简易版本(基于链表):

c 复制代码
// Queue.h
#pragma once
#include "BinaryTree.h"

typedef BTNode* QDataType;

typedef struct QueueNode {
    QDataType data;
    struct QueueNode* next;
} QNode;

typedef struct Queue {
    QNode* head;
    QNode* tail;
} Queue;

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
bool QueueEmpty(Queue* pq);
c 复制代码
// Queue.c
#include "Queue.h"
#include <assert.h>

void QueueInit(Queue* pq) {
    pq->head = pq->tail = NULL;
}

void QueueDestroy(Queue* pq) {
    QNode* cur = pq->head;
    while (cur) {
        QNode* next = cur->next;
        free(cur);
        cur = next;
    }
    pq->head = pq->tail = NULL;
}

void QueuePush(Queue* pq, QDataType x) {
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    newnode->data = x;
    newnode->next = NULL;
    if (pq->tail == NULL) {
        pq->head = pq->tail = newnode;
    } else {
        pq->tail->next = newnode;
        pq->tail = newnode;
    }
}

void QueuePop(Queue* pq) {
    assert(pq && !QueueEmpty(pq));
    if (pq->head->next == NULL) {
        free(pq->head);
        pq->head = pq->tail = NULL;
    } else {
        QNode* del = pq->head;
        pq->head = pq->head->next;
        free(del);
    }
}

QDataType QueueFront(Queue* pq) {
    assert(pq && !QueueEmpty(pq));
    return pq->head->data;
}

bool QueueEmpty(Queue* pq) {
    return pq->head == NULL;
}

主函数测试 main.c

c 复制代码
// main.c
#include "BinaryTree.h"

int main() {
    BTNode* root = CreateTree();

    printf("前序遍历: "); PreOrder(root); printf("\n");
    printf("中序遍历: "); InOrder(root); printf("\n");
    printf("后序遍历: "); PostOrder(root); printf("\n");
    printf("层序遍历: "); LevelOrder(root); printf("\n");

    printf("\n结点总数: %d\n", BinaryTreeSize(root));
    printf("叶子结点数: %d\n", BinaryTreeLeafSize(root));
    printf("第3层结点数: %d\n", BinaryTreeLevelKSize(root, 3));
    printf("树的高度: %d\n", BinaryTreeDepth(root));

    BTNode* found = BinaryTreeFind(root, 5);
    if (found) printf("找到值为5的结点,地址: %p\n", (void*)found);

    printf("是否为完全二叉树: %s\n", 
           BinaryTreeComplete(root) ? "是" : "否");

    BinaryTreeDestroy(&root);
    printf("树已销毁,root = %p\n", (void*)root);

    return 0;
}

🎯 总结:链式二叉树核心要点

操作 方法 关键思想
创建结点 BuyNode malloc + 初始化
遍历 递归(前/中/后) 根据访问顺序调整 printf 位置
层序遍历 队列 FIFO 模拟逐层访问
结点统计 递归累加 分治:1 + 左 + 右
查找 递归搜索 先左后右,找到即返回
销毁 后序释放 先子后父,避免内存泄漏
完全二叉树判断 层序+NULL标记 遇NULL后不能有非空

💡 给初学者的建议

  1. 先画出你构造的树,再对照遍历结果;
  2. 递归函数多用"分治"思想:处理当前 + 递归左右
  3. 层序遍历一定要掌握队列的使用;
  4. 完全二叉树判断是面试高频题!

这份笔记+代码,让你从零彻底掌握链式二叉树的核心操作!

加油,未来的数据结构高手!🌳✨

相关推荐
Mz12211 小时前
day07 和为 K 的子数组
数据结构
java修仙传1 小时前
每日一题,力扣560. 和为 K 的子数组
算法·leetcode
ada7_2 小时前
LeetCode(python)——148.排序链表
python·算法·leetcode·链表
点云SLAM2 小时前
点云配准算法之-Voxelized GICP(VGICP)算法
算法·机器人·gpu·slam·点云配准·vgicp算法·gicp算法
Bona Sun2 小时前
单片机手搓掌上游戏机(二十)—pico运行doom之编译环境
c语言·c++·单片机·游戏机
Albert Edison3 小时前
【项目设计】C++ 高并发内存池
数据结构·c++·单例模式·哈希算法·高并发
我真不会起名字啊3 小时前
C、C++中的sprintf和stringstream的使用
java·c语言·c++
资深web全栈开发3 小时前
LeetCode 3625. 统计梯形的数目 II
算法·leetcode·组合数学
橘颂TA3 小时前
【剑斩OFFER】算法的暴力美学——外观数列
算法·leetcode·职场和发展·结构与算法