跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

数据结构初阶:二叉树的链式存储与实现

二叉树链式存储结构详解,涵盖节点定义、手动构建、四种遍历方式(前序、中序、后序、层序)、节点统计及深度计算等核心操作。通过递归与队列结合实现完整功能,适合初学者掌握基础数据结构逻辑。

CodeArtist发布于 2026/3/21更新于 2026/10/2592 浏览
数据结构初阶:二叉树的链式存储与实现

一、二叉树的链式结构

顺序存储虽然直观,但在处理稀疏树或动态增长场景时存在空间浪费。链式存储通过指针将节点连接起来,更加灵活。二叉树的链式结构主要分为二叉链(含左右指针)和三叉链(含父指针),本教程重点讲解最常用的二叉链。

// 定义二叉树节点
typedef char Elemtype;
typedef struct BinaryTree {
    Elemtype data;
    struct BinaryTree* leftChild;
    struct BinaryTree* rightChild;
} BTNode;

每个节点包含数据域和两个指针域,分别指向左子树和右子树的根节点地址。这种结构天然适合递归操作。

二、二叉树的创建

在掌握基本性质后,我们手动构建一棵二叉树来演示节点链接。相比复杂的增删查改逻辑,手动初始化有助于理解树形结构。

1. 节点创建辅助函数

创建节点需分配内存并初始化指针,防止野指针。

BTNode* BuyNode(Elemtype data) {
    BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
    if (newNode == NULL) {
        perror("create new node fail!");
        return NULL;
    }
    newNode->data = data;
    newNode->leftChild = newNode->rightChild = NULL;
    return newNode;
}

注意:务必检查 malloc 返回值,并在返回前确保指针已初始化。

2. 手动链接节点

通过调用 BuyNode 生成节点,再手动设置 leftChild 和 rightChild 指针。

BTNode* createNewBT() {
    // 创建节点 A-I, O
    BTNode* NodeA = BuyNode('A');
    BTNode* NodeB = BuyNode('B');
    BTNode* NodeC = BuyNode('C');
    BTNode* NodeD = BuyNode('D');
    BTNode* NodeE = BuyNode('E');
    BTNode* NodeF = BuyNode('F');
    BTNode* NodeG = BuyNode('G');
    BTNode* NodeH = BuyNode('H');
    BTNode* NodeI = BuyNode('I');
    BTNode* NodeO = BuyNode('O');

    // 建立连接关系
    NodeA->leftChild = NodeB;   NodeA->rightChild = NodeC;
    NodeB->leftChild = NodeD;   NodeB->rightChild = NodeE;
    NodeD->leftChild = NodeH;   
    NodeC->leftChild = NodeF;   NodeF->leftChild = NodeI;
    NodeH->leftChild = NodeO;

    return NodeA; // 返回根节点
}

这棵树的结构是:根为 A,左子树 B,右子树 C... 以此类推。实际开发中,通常通过数组序列化字符串(如 "ABD#H#O##CE#F#I##")来自动构建,但手动构建更利于调试。

三、链式二叉树的基本操作

1. 三种深度优先遍历

前序遍历 (根 -> 左 -> 右)

访问根节点后,先递归左子树,再递归右子树。

void PreOrder(BTNode* root) {
    if (root == NULL) {
        printf("NULL ");
        return;
    }
    printf("%c ", root->data);
    PreOrder(root->leftChild);
    PreOrder(root->rightChild);
}
中序遍历 (左 -> 根 -> 右)

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

void InOrder(BTNode* root) {
    if (root == NULL) {
        printf("NULL ");
        return;
    }
    InOrder(root->leftChild);
    printf("%c ", root->data);
    InOrder(root->rightChild);
}
后序遍历 (左 -> 右 -> 根)

左右子树均遍历完后,才访问根节点。常用于释放内存。

void LastOrder(BTNode* root) {
    if (root == NULL) {
        printf("NULL ");
        return;
    }
    LastOrder(root->leftChild);
    LastOrder(root->rightChild);
    printf("%c ", root->data);
}

2. 统计与查找

计算总结点个数

利用递归思想:总数 = 左子树节点数 + 右子树节点数 + 1(根节点)。

int BinaryTreeSize_of_Node(BTNode* root) {
    if (root == NULL) return 0;
    return 1 + BinaryTreeSize_of_Node(root->leftChild) + BinaryTreeSize_of_Node(root->rightChild);
}
计算叶子节点个数

叶子节点定义为左右孩子均为空。若当前节点满足条件则计数 1,否则累加左右子树的叶子数。

int BinaryTreesize_of_Leaf(BTNode* root) {
    if (root == NULL) return 0;
    if (root->leftChild == NULL && root->rightChild == NULL) return 1;
    return BinaryTreesize_of_Leaf(root->leftChild) + BinaryTreesize_of_Leaf(root->rightChild);
}
计算第 k 层节点个数

递归时 k 减 1,当 k=1 时说明到达目标层,返回 1。

int BinaryTreeSize_of_K(BTNode* root, int k) {
    if (root == NULL) return 0;
    if (k == 1) return 1;
    return BinaryTreeSize_of_K(root->leftChild, k - 1) + BinaryTreeSize_of_K(root->rightChild, k - 1);
}
计算二叉树深度

深度取决于左右子树深度的最大值加 1。

int BinaryTreeSize_of_Depth(BTNode* root) {
    if (root == NULL) return 0;
    int leftDepth = BinaryTreeSize_of_Depth(root->leftChild);
    int rightDepth = BinaryTreeSize_of_Depth(root->rightChild);
    return 1 + (leftDepth > rightDepth ? leftDepth : rightDepth);
}
查找元素

遍历整棵树,找到目标值即停止。

void BinaryTreeSearch_of_val(BTNode* root, Elemtype val) {
    if (root == NULL) return;
    if (root->data == val) {
        printf("找到了!\n");
        return;
    }
    BinaryTreeSearch_of_val(root->leftChild, val);
    BinaryTreeSearch_of_val(root->rightChild, val);
}

3. 层序遍历与完全二叉树判断

层序遍历需要借助队列(先进先出)。思路是:根入队 -> 出队打印 -> 左右孩子入队 -> 循环直到队空。

void LevelOrder(BTNode* root) {
    Queue q;
    Queue* pQueue = &q;
    InitQueue(pQueue);
    Push_Queue(pQueue, root);
    while (!isEmpty(pQueue)) {
        BTNode* top = GetElemFront(pQueue);
        printf("%c ", top->data);
        Pop_Queue(pQueue);
        if (top->leftChild) Push_Queue(pQueue, top->leftChild);
        if (top->rightChild) Push_Queue(pQueue, top->rightChild);
    }
    DestroyQueue(pQueue);
}

判断完全二叉树同样基于层序遍历。遇到第一个空节点后,后续所有节点必须为空才是完全二叉树。

bool IsBinaryTree_of_Complete(BTNode* root) {
    Queue q;
    Queue* pQueue = &q;
    InitQueue(pQueue);
    Push_Queue(pQueue, root);
    while (!isEmpty(pQueue)) {
        QElemtype top = GetElemFront(pQueue);
        Pop_Queue(pQueue);
        if (top == NULL) break;
        Push_Queue(pQueue, top->leftChild);
        Push_Queue(pQueue, top->rightChild);
    }
    while (!isEmpty(pQueue)) {
        BTNode* top = GetElemFront(pQueue);
        Pop_Queue(pQueue);
        if (top != NULL) {
            DestroyQueue(pQueue);
            return false;
        }
    }
    DestroyQueue(pQueue);
    return true;
}

四、完整代码整合

以下代码整合了二叉树核心逻辑与队列实现。实际项目中建议将队列功能封装为独立头文件引入。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include"Queue.h"

typedef char Elemtype;

typedef struct BinaryTree {
    Elemtype data;
    struct BinaryTree* leftChild;
    struct BinaryTree* rightChild;
} BTNode;

BTNode* BuyNode(Elemtype data) {
    BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
    if (newNode == NULL) { perror("create new node fail!"); return NULL; }
    newNode->data = data;
    newNode->leftChild = newNode->rightChild = NULL;
    return newNode;
}

BTNode* createNewBT() {
    BTNode* NodeA = BuyNode('A');
    BTNode* NodeB = BuyNode('B');
    BTNode* NodeC = BuyNode('C');
    BTNode* NodeD = BuyNode('D');
    BTNode* NodeE = BuyNode('E');
    BTNode* NodeF = BuyNode('F');
    BTNode* NodeG = BuyNode('G');
    BTNode* NodeH = BuyNode('H');
    BTNode* NodeI = BuyNode('I');
    BTNode* NodeO = BuyNode('O');
    NodeA->leftChild = NodeB;   NodeA->rightChild = NodeC;
    NodeB->leftChild = NodeD;   NodeB->rightChild = NodeE;
    NodeD->leftChild = NodeH;   
    NodeC->leftChild = NodeF;   NodeF->leftChild = NodeI;
    NodeH->leftChild = NodeO;
    return NodeA;
}

void PreOrder(BTNode* root) {
    if (root == NULL) { printf("NULL "); return; }
    printf("%c ", root->data); PreOrder(root->leftChild); PreOrder(root->rightChild);
}

void InOrder(BTNode* root) {
    if (root == NULL) { printf("NULL "); return; }
    InOrder(root->leftChild); printf("%c ", root->data); InOrder(root->rightChild);
}

void LastOrder(BTNode* root) {
    if (root == NULL) { printf("NULL "); return; }
    LastOrder(root->leftChild); LastOrder(root->rightChild); printf("%c ", root->data);
}

int BinaryTreeSize_of_Node(BTNode* root) {
    if (root == NULL) return 0;
    return 1 + BinaryTreeSize_of_Node(root->leftChild) + BinaryTreeSize_of_Node(root->rightChild);
}

int BinaryTreesize_of_Leaf(BTNode* root) {
    if (root == NULL) return 0;
    if (root->leftChild == NULL && root->rightChild == NULL) return 1;
    return BinaryTreesize_of_Leaf(root->leftChild) + BinaryTreesize_of_Leaf(root->rightChild);
}

int BinaryTreeSize_of_K(BTNode* root, int k) {
    if (root == NULL) return 0;
    if (k == 1) return 1;
    return BinaryTreeSize_of_K(root->leftChild, k - 1) + BinaryTreeSize_of_K(root->rightChild, k - 1);
}

int BinaryTreeSize_of_Depth(BTNode* root) {
    if (root == NULL) return 0;
    int leftDepth = BinaryTreeSize_of_Depth(root->leftChild);
    int rightDepth = BinaryTreeSize_of_Depth(root->rightChild);
    return 1 + (leftDepth > rightDepth ? leftDepth : rightDepth);
}

void BinaryTreeSearch_of_val(BTNode* root, Elemtype val) {
    if (root == NULL) return;
    if (root->data == val) { printf("找到了!\n"); return; }
    BinaryTreeSearch_of_val(root->leftChild, val);
    BinaryTreeSearch_of_val(root->rightChild, val);
}

void LevelOrder(BTNode* root) {
    Queue q; Queue* pQueue = &q;
    InitQueue(pQueue);
    Push_Queue(pQueue, root);
    while (!isEmpty(pQueue)) {
        BTNode* top = GetElemFront(pQueue);
        printf("%c ", top->data);
        Pop_Queue(pQueue);
        if (top->leftChild) Push_Queue(pQueue, top->leftChild);
        if (top->rightChild) Push_Queue(pQueue, top->rightChild);
    }
    DestroyQueue(pQueue);
}

bool IsBinaryTree_of_Complete(BTNode* root) {
    Queue q; Queue* pQueue = &q;
    InitQueue(pQueue);
    Push_Queue(pQueue, root);
    while (!isEmpty(pQueue)) {
        QElemtype top = GetElemFront(pQueue);
        Pop_Queue(pQueue);
        if (top == NULL) break;
        Push_Queue(pQueue, top->leftChild);
        Push_Queue(pQueue, top->rightChild);
    }
    while (!isEmpty(pQueue)) {
        BTNode* top = GetElemFront(pQueue);
        Pop_Queue(pQueue);
        if (top != NULL) { DestroyQueue(pQueue); return false; }
    }
    DestroyQueue(pQueue);
    return true;
}

int main() {
    BTNode* NodeA = createNewBT();
    printf("前序遍历:"); PreOrder(NodeA); printf("\n");
    printf("中序遍历:"); InOrder(NodeA); printf("\n");
    printf("后序遍历:"); LastOrder(NodeA); printf("\n");
    printf("总结点数:%d\n", BinaryTreeSize_of_Node(NodeA));
    printf("叶子节点数:%d\n", BinaryTreesize_of_Leaf(NodeA));
    printf("第4层节点数:%d\n", BinaryTreeSize_of_K(NodeA, 4));
    printf("深度:%d\n", BinaryTreeSize_of_Depth(NodeA));
    printf("层序遍历:"); LevelOrder(NodeA); printf("\n");
    printf(IsBinaryTree_of_Complete(NodeA) ? "是完全二叉树\n" : "不是完全二叉树\n");
    return 0;
}

以上便是二叉树链式存储的核心实现。理解递归终止条件和指针操作是关键,多动手调试能加深印象。

目录

  1. 一、二叉树的链式结构
  2. 二、二叉树的创建
  3. 1. 节点创建辅助函数
  4. 2. 手动链接节点
  5. 三、链式二叉树的基本操作
  6. 1. 三种深度优先遍历
  7. 前序遍历 (根 -> 左 -> 右)
  8. 中序遍历 (左 -> 根 -> 右)
  9. 后序遍历 (左 -> 右 -> 根)
  10. 2. 统计与查找
  11. 计算总结点个数
  12. 计算叶子节点个数
  13. 计算第 k 层节点个数
  14. 计算二叉树深度
  15. 查找元素
  16. 3. 层序遍历与完全二叉树判断
  17. 四、完整代码整合

更多推荐文章

查看全部
  • 基于 AWS EC2 免费层部署 ClawdBot 自建 AI 助手
  • C++ 字符串操作实战:std::string 的常用接口、迭代器与内存管理
  • Python 入门:编写并运行第一个程序
  • FreeBSD 下使用 BVCP 管理 bhyve 虚拟机(上)
  • AI 生成前端 UI 的三步优化技巧
  • HTTP 请求方式详解:GET、POST 及其他常用方法
  • AI 提示词助力代码重构与可读性提升
  • C++11 左值与右值引用详解:移动语义与返回值优化实战
  • Spring Cloud Alibaba 微服务架构实战指南
  • Nginx 按流量比例反向代理配置详解
  • Python 极简教程:Java 开发者视角入门实战
  • 堆排序算法详解与实现
  • 自然语言处理在客户服务领域的应用与实战
  • LLM Agent 规划工作流深度解析与实现
  • 机器人日志系统十年演进:从故障排查到核心数据资产
  • Claude Skills 实战指南:从安装到自动化开发
  • 一卡通核心交易系统国产数据库实践:架构、迁移与高可用
  • 数据结构:顺序表详解
  • 2025 时序数据库选型:从架构基因到 AI 赋能
  • 前端安全实战:密码存储、XSS 及 CSRF 防护

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online