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

二叉树的链式存储与遍历实现

从树的概念出发,讲解二叉树的基本性质、满二叉树与完全二叉树,并给出C语言链式存储实现,包括前中后序与层序遍历、节点计算、完全二叉树判定等操作,附带完整的队列辅助代码。

微码行者发布于 2026/6/22更新于 2026/9/1037 浏览
二叉树的链式存储与遍历实现

工作中只要需要表示层级关系,就会碰到树结构。它是一种非线性结构,由一个根节点和若干互不相交的子树组成,每棵子树本身又是一棵树。理解树的关键在于递归:根节点没有前驱,其他节点有且只有一个父节点,但可以有多个孩子。

这种结构里有一些常用的叫法,对照下面这张图就很直观。

树结构示例

  • 结点拥有的子树数量叫度,比如 A 的度是 6。
  • 度为 0 的结点是叶结点,像 B、H、P 这种。
  • 一个结点如果有子结点,它就是子结点的父结点(比如 A 是 B 的父结点);反过来 B 是 A 的子结点。
  • 树中结点的最大层次是树的高度或深度,图中这棵树高度是 4。

二叉树

二叉树是树的度最大为 2 的特殊情况,也就是说每个结点最多有两个分叉。普通树的结构太灵活,工程里用得最多的其实就是二叉树。

它由根结点加上左子树和右子树组成——注意左右是有顺序的,不能颠倒,所以二叉树是一种有序树。

二叉树组成

两种特殊的二叉树要记住:

  • 满二叉树:每一层的结点数都达到最大值。如果层数为 K,结点总数就是 2K - 1。
  • 完全二叉树:深度为 K 的二叉树,其结点与同深度满二叉树中编号从 1 到 n 的结点一一对应。满二叉树就是特殊的完全二叉树。

满二叉树与完全二叉树

完全二叉树的编号是连续的,中间断了就不是完全二叉树。这个性质让它能用数组顺序存储:物理上是一个数组,逻辑上还是一棵树。非完全二叉树要是硬用数组,会浪费很多空间,所以后面我们会用链式结构来存。

二叉树还有几个常用性质:

  1. 第 i 层最多有 2(i-1) 个结点(根层数为 1)。
  2. 深度为 h 的二叉树最大结点数是 2h - 1。
  3. 如果叶结点个数是 n₀,度为 2 的结点个数是 n₂,那么 n₀ = n₂ + 1。
  4. 满二叉树的深度 h = log₂(n + 1)。

完全二叉树的下标关系(数组从 0 开始编号):

  • 父亲下标 i,左孩子是 2i + 1,右孩子是 2i + 2。
  • 孩子下标 i,父亲就是 (i - 1) / 2。

前提是这些下标对应的结点确实存在。

链式存储与节点定义

非完全二叉树如果强行用数组,空间利用率太低,所以需要链式存储。每个结点用一个结构体表示,包含数据域、左指针和右指针。为了方便换类型,用 typedef 重定义一下。

// BTNode.h
# once


  BTDataType;

 
    BTDataType data;
    
    
}BTNode;
pragma
#include "Queue.h"
typedef
char
typedef
struct BinaryTreeNode {
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;

构建二叉树

构建的时候给定一个前序遍历的数组,例如 "ABD##E#H##CF##G##",其中 # 代表空节点。递归实现很自然:遇到 # 就返回空,否则创建结点,然后递归构建左子树和右子树。

// BTNode.c
BTNode* BinaryTreeCreate(BTDataType* ch, int* pi) {
    if (ch[*pi] == '#') {
        return NULL;
    }
    BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
    if (newnode == NULL) {
        perror("malloc fail");
        return NULL;
    }
    newnode->data = ch[(*pi)++];
    newnode->left = BinaryTreeCreate(ch, pi);
    (*pi)++;
    newnode->right = BinaryTreeCreate(ch, pi);
    return newnode;
}

二叉树遍历

递归遍历是基础操作,前序、中序、后序的名字就是按'根'访问的时机来的。

  • 前序:根 → 左 → 右
  • 中序:左 → 根 → 右
  • 后序:左 → 右 → 根

实现几乎一样,区别只在什么时候打印数据。

// 前序遍历
void BinaryTreePrevOrder(BTNode* root) {
    if (root == NULL) {
        return;
    }
    printf("%c ", root->data);
    BinaryTreePrevOrder(root->left);
    BinaryTreePrevOrder(root->right);
}

// 中序遍历
void BinaryTreeInOrder(BTNode* root) {
    if (root == NULL) {
        return;
    }
    BinaryTreeInOrder(root->left);
    printf("%c ", root->data);
    BinaryTreeInOrder(root->right);
}

// 后序遍历
void BinaryTreePostOrder(BTNode* root) {
    if (root == NULL) {
        return;
    }
    BinaryTreePostOrder(root->left);
    BinaryTreePostOrder(root->right);
    printf("%c ", root->data);
}

层序遍历就不靠递归了,要用队列。思路很简单:先把根入队,然后每次取出队头结点,打印它,再把它的左右孩子(如果存在)入队,直到队列为空。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root) {
    Queue Q;
    QueueInit(&Q);
    if (root) {
        QueuePush(&Q, root);
    }
    while (!QueueEmpty(&Q)) {
        BTNode* front = QueueFront(&Q);
        printf("%c ", front->data);
        QueuePop(&Q);
        if (front->left) {
            QueuePush(&Q, front->left);
        }
        if (front->right) {
            QueuePush(&Q, front->right);
        }
    }
    QueueDestroy(&Q);
}

常用操作

有了递归的思维,结点个数、高度这些统计函数写起来都很短。

// 结点个数
int BinaryTreeSize(BTNode* root) {
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

// 二叉树高度
int BinaryTreeHight(BTNode* root) {
    if (root == NULL) {
        return 0;
    }
    int leftH = BinaryTreeHight(root->left);
    int rightH = BinaryTreeHight(root->right);
    return (leftH > rightH ? leftH : rightH) + 1;
}

// 第 k 层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k) {
    if (root == NULL) {
        return 0;
    }
    if (k == 1) {
        return 1;
    }
    return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

// 查找值为 x 的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
    if (root == NULL) {
        return NULL;
    }
    if (root->data == x) {
        return root;
    }
    BTNode* ret1 = BinaryTreeFind(root->left, x);
    if (ret1) {
        return ret1;
    }
    BTNode* ret2 = BinaryTreeFind(root->right, x);
    if (ret2) {
        return ret2;
    }
    return NULL;
}

// 判断是否是完全二叉树
int BinaryTreeComplete(BTNode* root) {
    Queue Q;
    QueueInit(&Q);
    if (root) {
        QueuePush(&Q, root);
    }
    while (!QueueEmpty(&Q)) {
        BTNode* front = QueueFront(&Q);
        QueuePop(&Q);
        if (front == NULL) {
            break;
        }
        QueuePush(&Q, front->left);
        QueuePush(&Q, front->right);
    }
    while (!QueueEmpty(&Q)) {
        BTNode* front = QueueFront(&Q);
        QueuePop(&Q);
        if (front != NULL) {
            QueueDestroy(&Q);
            return 0;
        }
    }
    QueueDestroy(&Q);
    return 1;
}

完整代码结构

层序遍历和完全二叉树判定都需要一个队列,下面给出队列的实现。

// Queue.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int QDataType;

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

typedef struct Queue {
    QNode* front;
    QNode* tail;
    int size;
}Queue;

void QueueInit(Queue* q);
void QueuePush(Queue* q, QDataType data);
void QueuePop(Queue* q);
QDataType QueueFront(Queue* q);
QDataType QueueBack(Queue* q);
int QueueSize(Queue* q);
int QueueEmpty(Queue* q);
void QueueDestroy(Queue* q);
// Queue.c
#include "Queue.h"

void QueueInit(Queue* q) {
    assert(q);
    q->front = NULL;
    q->tail = NULL;
    q->size = 0;
}

void QueuePush(Queue* q, QDataType data) {
    assert(q);
    QNode* tmp = (QNode*)malloc(sizeof(QNode));
    if (tmp == NULL) {
        perror("malloc");
        return;
    }
    tmp->data = data;
    tmp->next = NULL;
    if (q->size == 0) {
        q->front = tmp;
        q->tail = tmp;
    } else {
        q->tail->next = tmp;
        q->tail = tmp;
    }
    q->size++;
}

void QueuePop(Queue* q) {
    assert(q);
    assert(q->size > 0);
    if (q->size == 1) {
        free(q->front);
        q->front = NULL;
        q->tail = NULL;
    } else {
        QNode* del = q->front;
        q->front = q->front->next;
        free(del);
    }
    q->size--;
}

QDataType QueueFront(Queue* q) {
    assert(q);
    assert(q->size > 0);
    return q->front->data;
}

QDataType QueueBack(Queue* q) {
    assert(q);
    assert(q->size > 0);
    return q->tail->data;
}

int QueueSize(Queue* q) {
    assert(q);
    return q->size;
}

int QueueEmpty(Queue* q) {
    assert(q);
    return q->size == 0;
}

void QueueDestroy(Queue* q) {
    assert(q);
    QNode* cur = q->front;
    while (cur) {
        QNode* next = cur->next;
        free(cur);
        cur = next;
    }
    q->front = q->tail = NULL;
    q->size = 0;
}

最后用一个测试文件把上面这些函数串起来。

#include "BTNode.h"

int main() {
    char ch[100] = "ABD##E#H##CF##G##";
    int i = 0;
    BTNode* T = BinaryTreeCreate(ch, &i);

    printf("节点个数:%d\n", BinaryTreeSize(T));
    printf("叶子节点个数:%d\n", BinaryTreeLeafSize(T));
    printf("高度:%d\n", BinaryTreeHight(T));
    printf("第 3 层节点个数:%d\n", BinaryTreeLevelKSize(T, 3));

    BTNode* ret = BinaryTreeFind(T, 'G');
    if (ret) {
        printf("找到 G: %c\n", ret->data);
    }

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

    printf("前序:");
    BinaryTreePrevOrder(T);
    printf("\n");
    printf("中序:");
    BinaryTreeInOrder(T);
    printf("\n");
    printf("后序:");
    BinaryTreePostOrder(T);
    printf("\n");
    printf("层序:");
    BinaryTreeLevelOrder(T);
    printf("\n");

    BinaryTreeDestory(&T);
    return 0;
}

整套代码可以直接编译运行,各种遍历和统计函数的输出很直观。理解这些底层逻辑,对后续设计算法和处理复杂的数据关系会有帮助。

目录

  1. 二叉树
  2. 链式存储与节点定义
  3. 构建二叉树
  4. 二叉树遍历
  5. 常用操作
  6. 完整代码结构

更多推荐文章

查看全部
  • 基于 ComfyUI 工作流的 Stable Diffusion 服装替换指南
  • 基于 Llama 3 构建 RAG 语音助手:集成 Qdrant、Whisper 与 LangChain
  • 全球老龄化背景下的护理机器人发展研究
  • 无人机远程路径规划技术:A*算法与 GPS 定位实现
  • 基于 ESP32 与 Arduino 的 Web 控制 LED 入门教程
  • DTS-BLY-5S 分布式光纤测温主机:20km 覆盖与 FPGA 架构
  • 大模型微调核心技术:LoRA 原理、实践与常见问题解析
  • Semantic Kernel Python 进阶:Prompt 模板函数嵌套调用实战
  • Java Web 开发实战:数据库操作与会话管理
  • LazyLLM 多 Agent 应用全流程实践:源码部署与可视化 Web 调试
  • LeRobot 深度解析:5 大核心模块构建机器人学习系统
  • C/C++ 输入输出技巧与性能优化
  • 基于 Spring Boot 的学生成绩管理系统设计与实现
  • FPGA 原型验证平台中 Vivado 许可证动态加载方法
  • OpenClaw v2026.3.7 版本功能详解:AI 代理框架更新
  • Hibernate 集合映射实战:Set、List、Bag 与 Map 配置详解
  • Linux System V 共享内存实战:底层原理、封装与避坑指南
  • Android 热修复原理与 HotFix 框架实现详解
  • 机场出租车调度问题数学建模实战解析(含 Python 模拟代码)
  • 2024 年大语言模型(LLM)微调方法全面总结

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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