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

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

二叉树作为数据结构中的核心非线性结构,涵盖定义、性质及特殊形态。本文重点阐述链式存储实现,包括前中后序递归遍历、层序遍历队列实现,以及节点计数、高度计算、查找与完全性判断等关键算法。通过完整的 C 语言代码示例,展示从节点定义到功能测试的工程化落地过程,适合初学者构建扎实的数据结构基础。

微码行者发布于 2026/2/23更新于 2026/9/861 浏览
数据结构:二叉树初阶与链式实现

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

一、树是什么?

1. 树的定义

树是一种非线性数据结构,由 n(n>=0)个有限结点组成一个具有层次关系的集合。有一个特殊的结点称为根结点,根结点没有前驱结点。除根结点外,其余结点被分成 M(M>0) 个互不相交的集合 T1、T2、……、Tm,其中每一个集合 Ti(1<= i <= m) 又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有 0 个或多个后继。

2. 常见术语

树结构示例

  • 结点的度:一个结点含有的子树的个数称为该结点的度。(例如 A 的度为 6)
  • 叶结点或终端结点:度为 0 的结点称为叶结点。(例如 B、H、P 等)
  • 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点。(例如 A 为 B 的父结点)
  • 子结点:一个结点含有的子树的根结点称为该结点的子结点。(例如 B 为 A 的子结点)
  • 树的高度或深度:树中结点的最大层次。(例如该树高度为 4)

二、二叉树

1. 二叉树的概念

二叉树是一种特殊的树,其树的度最大为 2。也就是说,一个结点最多有两个分叉。由于普通树的结构复杂,实际应用中最常用的是二叉树。

2. 二叉树的组成

二叉树由一个根结点加上两棵别称为左子树和右子树的二叉树组成。注意二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。

二叉树组成

3. 特殊的二叉树

  • 满二叉树:一个二叉树,如果每一层的结点数都达到最大值,则这个二叉树就是满二叉树。如果一个二叉树的层数为 K,且结点总数是 2^K - 1,则它就是满二叉树。
  • 完全二叉树:对于深度为 K 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 至 n 的结点一一对应时称之为完全二叉树。要注意满二叉树是一种特殊的完全二叉树。

满二叉树与完全二叉树

完全二叉树的编号是连续的,中间断开则不是完全二叉树。

4. 二叉树的顺序存储

对于完全二叉树,由于其编号连续,可以用数组进行存储,用下标来进行编号。二叉树顺序存储在物理上是一个数组,而在逻辑上是一颗二叉树。

5. 二叉树的性质

  1. 若规定根结点的层数为 1,则一棵非空二叉树的第 i 层上最多有 2^(i-1) 个结点。
  2. 若规定根结点的层数为 1,则深度为 h 的二叉树的最大结点数是 2^h - 1。
  3. 对任何一棵二叉树,如果度为 0 其叶结点个数为 n₀,度为 2 的分支结点个数为 n₂,则有 n₀ = n₂ + 1。
  4. 若规定根结点的层数为 1,具有 n 个结点的满二叉树的深度,h = log₂(n + 1)。

重要性质: 对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从 0 开始编号:

  • 若父亲在数组中下标为 i,则该父亲的左孩子下标为 2 * i + 1;右孩子下标为 2 * i + 2。
  • 若孩子在数组中下标为 i,则该孩子的父亲下标为 (i - 1) / 2。 注意:前提是这些下标所对应的值存在。

三、二叉树的实现

1. 链式存储

前面提到完全二叉树可以用数组存储,而非完全二叉树若强行用数组会浪费大量空间。此时我们使用链式存储。

链式存储即用链表来表示一颗二叉树,用链来指示元素的逻辑关系。通常的方法创建一链表,使每个结点由三个域组成:数据域和左右指针域。左右指针分别用来给出该结点左孩子和右孩子所在结点的地址。

2. 节点定义

与单链表类似,二叉树中的每一个节点有以下三个成员:

  1. 节点中存储的数据
  2. 指向节点左孩子的指针
  3. 指向节点右孩子的指针

为了便于类型修改,我们使用 typedef 重定义数据类型。

// BTNode.h
#pragma once
#include "Queue.h"

typedef char BTDataType;

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

3. 构建二叉树

这里通过一个前序遍历的数组 "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;
}

4. 二叉树遍历

前序、中序、后序遍历

这三种遍历主要在于访问根节点的时机不同,均采用递归实现。

  • 前序遍历:根 -> 左 -> 右
  • 中序遍历:左 -> 根 -> 右
  • 后序遍历:左 -> 右 -> 根
// 前序遍历
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);
}

5. 其他常用操作

节点个数计算

利用递归,根节点为空返回 0,否则返回左子树 + 右子树 + 1。

int BinaryTreeSize(BTNode* root) {
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
二叉树高度计算

返回左右子树高度较高的一边并 + 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 层节点个数

初始 k,若该层不是目标层次,就 k - 1 并递归左右子树。到达第 k 层返回 1。

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;
}

四、完整代码结构

1. 队列实现 (Queue.h / Queue.c)

// 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;
}

2. 测试文件 (Test.c)

#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. 1. 树的定义
  4. 2. 常见术语
  5. 二、二叉树
  6. 1. 二叉树的概念
  7. 2. 二叉树的组成
  8. 3. 特殊的二叉树
  9. 4. 二叉树的顺序存储
  10. 5. 二叉树的性质
  11. 三、二叉树的实现
  12. 1. 链式存储
  13. 2. 节点定义
  14. 3. 构建二叉树
  15. 4. 二叉树遍历
  16. 前序、中序、后序遍历
  17. 层序遍历
  18. 5. 其他常用操作
  19. 节点个数计算
  20. 二叉树高度计算
  21. 第 k 层节点个数
  22. 查找值为 x 的节点
  23. 判断是否是完全二叉树
  24. 四、完整代码结构
  25. 1. 队列实现 (Queue.h / Queue.c)
  26. 2. 测试文件 (Test.c)

更多推荐文章

查看全部
  • 基于 Python Vue3 Django 的 KTV 包厢服务管理系统
  • Python 百度搜索 API 快速入门与集成使用
  • Linux GCC/G++编译器:从源码到可执行文件的全流程解析
  • MySQL 与 MCP 协议集成实战:从环境搭建到 AI 数据交互
  • Web 版 IM 端到端加密:三种算法实现方案与实战对比
  • Linux 进程间通信:命名管道(FIFO)核心原理与实战
  • 基于 AgentFabric 微调 Qwen-7B 实现交互式智能体应用
  • Vibe Coding的感想:从自动补全到 Copilot 的技术与职业全景
  • 基于 Docker 本地部署 RSSHub 并通过内网穿透远程访问
  • JFreeChart 多元数据柱形图生成示例
  • ToDesk 发布 ToClaw:AI 可直接操作电脑
  • GitHub 仓库配置与推送:从 SSH 到冲突解决
  • OpenClaw Windows 10 本地 AI 智能体一键部署教程
  • 傅里叶级数与变换:从连续到离散的完整体系梳理
  • Linux 进程创建与终止:fork 与 exit 底层逻辑解析
  • Linux 下 GCC 编译入门与基础用法
  • OpenClaw 安装配置指南:Minimax/DeepSeek 模型及飞书机器人接入
  • 基于自然语言交互的 AI 爬虫工具实战解析
  • Spring AI 框架入门指南
  • 企业级 Web 影院订票系统:基于 SpringBoot 与 Vue 的实现

相关免费在线工具

  • 加密/解密文本

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