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

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

数据结构中的二叉树涵盖树的定义、术语及性质。重点介绍二叉树的链式存储实现,包括前序、中序、后序及层序遍历算法。同时提供节点个数、高度、第 k 层节点数计算、值查找及完全二叉树判断等核心操作的代码实现。内容基于 C 语言,使用队列辅助完成层序遍历。

ServerBase发布于 2026/2/8更新于 2026/7/2841 浏览
数据结构:二叉树初阶与实现

一、树是什么?

1. 树的定义

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

2. 一些常见术语

  • 结点的度:一个结点含有的子树的个数称为该结点的度。
  • 叶结点或终端结点:度为 0 的结点称为叶结点。
  • 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点。
  • 子结点:一个结点含有的子树的根结点称为该结点的子结点。
  • 树的高度或深度:树中结点的最大层次。

二、二叉树

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. 指向节点右孩子的指针

3. 代码实现

本文以创建一个 char 类型的二叉树为例。

(1)文件结构
  • BTNode.h:用于存放函数的声明和一些库函数的头文件。
  • BTNode.c:用于放函数的定义(二叉树的主体)。
  • Test.c:用于测试实现的二叉树的运行效果。
(2)定义二叉树
// 重定义,方便修改类型
typedef char BTDataType;

// 表示每个节点的类型
typedef struct BinaryTreeNode {
    BTDataType data; // 节点中存储的数据
    struct BinaryTreeNode* left; // 指向左孩子的指针
    struct BinaryTreeNode* right; // 指向右孩子的指针
} BTNode;

在定义二叉树的代码中,有两个需要注意的点:

  1. 本文是以 char 类型为例,但如果以后要将二叉树中的元素类型修改成 int 类型或是其他一个一个修改就很麻烦。所以我们重定义 char 类型为 BTDataType,并将接下来代码中的 char 类型全部写成 BTDataType。这是为了方便我们以后对类型进行修改,仅需将 char 改为其他类型即可。
  2. 在定义二叉树的同时重定义二叉树的变量名为 BTNode 方便以后使用。
(3)构建二叉树

通过一个前序遍历的数组 "ABD##E#H##CF##G##" 来构建了二叉树。要先有一个二叉树,才能对二叉树进行操作。

// 通过前序遍历的数组构建二叉树
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)二叉树遍历(前中后序)

要想对二叉树进行操作,肯定少不了遍历二叉树。一共有 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);
}
(5)二叉树的层序遍历

层序遍历是一层一层地遍历。需要使用队列来实现。

思路:每次取出结点的同时,将该结点的左结点和右结点存储进队列中,直到队列为空。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root) {
    Queue Q;
    QueueInit(&Q);
    if (root) {
        QueuePush(&Q, root);
    }
    while (!QueueEmpty(&Q)) {
        BTNode* root = QueueFront(&Q);
        printf("%c ", root->data);
        QueuePop(&Q);
        if (root->left) {
            QueuePush(&Q, root->left);
        }
        if (root->right) {
            QueuePush(&Q, root->right);
        }
    }
    QueueDestroy(&Q);
}
(6)二叉树节点个数的计算
// 二叉树节点个数
int BinaryTreeSize(BTNode* root) {
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
(7)二叉树高度的计算
// 二叉树高度
int BinaryTreeHight(BTNode* root) {
    if (root == NULL) {
        return 0;
    }
    return fmax(BinaryTreeHight(root->left), BinaryTreeHight(root->right)) + 1;
}
(8)二叉树第 k 层节点个数的计算
// 二叉树第 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);
}
(9)二叉树查找值为 x 的节点
// 二叉树查找值为 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;
}
(10)判断二叉树是否是完全二叉树

原理是层序遍历,只不过多加入了一层判断。当层序遍历到空结点时,若之后还有数据则不是完全二叉树。

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

四、二叉树实现完整代码

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

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

3. BTNode.h

#pragma once
#include "Queue.h"

typedef char BTDataType;

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

BTNode* BinaryTreeCreate(BTDataType* ch, int* pi);
void BinaryTreePrevOrder(BTNode* root);
void BinaryTreeInOrder(BTNode* root);
void BinaryTreePostOrder(BTNode* root);
int BinaryTreeSize(BTNode* root);
int BinaryTreeLeafSize(BTNode* root);
int BinaryTreeHight(BTNode* root);
void BinaryTreeDestory(BTNode** root);
int BinaryTreeLevelKSize(BTNode* root, int k);
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
int BinaryTreeComplete(BTNode* root);
void BinaryTreeLevelOrder(BTNode* root);

4. BTNode.c

#include "BTNode.h"

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* root = QueueFront(&Q);
        printf("%c ", root->data);
        QueuePop(&Q);
        if (root->left) {
            QueuePush(&Q, root->left);
        }
        if (root->right) {
            QueuePush(&Q, root->right);
        }
    }
    QueueDestroy(&Q);
}

void BinaryTreeDestory(BTNode** root) {
    if (*root == NULL) {
        return;
    }
    BinaryTreeDestory(&(*root)->left);
    BinaryTreeDestory(&(*root)->right);
    free(*root);
    *root = NULL;
}

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

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

int BinaryTreeHight(BTNode* root) {
    if (root == NULL) {
        return 0;
    }
    return fmax(BinaryTreeHight(root->left), BinaryTreeHight(root->right)) + 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);
}

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* root = QueueFront(&Q);
        QueuePop(&Q);
        if (root == NULL) {
            break;
        }
        QueuePush(&Q, root->left);
        QueuePush(&Q, root->right);
    }
    while (!QueueEmpty(&Q)) {
        BTNode* root = QueueFront(&Q);
        QueuePop(&Q);
        if (root != NULL) {
            QueueDestroy(&Q);
            return 0;
        }
    }
    QueueDestroy(&Q);
    return 1;
}

5. Test.c

#include "BTNode.h"

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

    int ret1 = BinaryTreeSize(T);
    printf("二叉树节点个数:%d\n", ret1);

    int ret2 = BinaryTreeLeafSize(T);
    printf("二叉树叶子节点个数:%d\n", ret2);

    int ret3 = BinaryTreeHight(T);
    printf("二叉树高度:%d\n", ret3);

    int ret4 = BinaryTreeLevelKSize(T, 3);
    printf("二叉树第 k 层节点个数:%d\n", ret4);

    BTNode* ret5 = BinaryTreeFind(T, 'G');
    if (ret5 == NULL) {
        printf("没有找到\n");
    } else {
        printf("存在%c\n", ret5->data);
    }

    int ret6 = BinaryTreeComplete(T);
    if (ret6) {
        printf("是完全二叉树\n");
    } else {
        printf("不是完全二叉树\n");
    }

    printf("\n");
    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. 1. 树的定义
  3. 2. 一些常见术语
  4. 二、二叉树
  5. 1. 二叉树是什么?
  6. 2. 二叉树的组成
  7. 3. 特殊的二叉树
  8. 4. 二叉树的顺序存储(完全二叉树)
  9. 5. 二叉树的一些性质
  10. 三、二叉树的实现(重点)
  11. 1. 二叉树的链式存储(非完全二叉树)
  12. 2. 实现思路
  13. 3. 代码实现
  14. (1)文件结构
  15. (2)定义二叉树
  16. (3)构建二叉树
  17. (4)二叉树遍历(前中后序)
  18. (5)二叉树的层序遍历
  19. (6)二叉树节点个数的计算
  20. (7)二叉树高度的计算
  21. (8)二叉树第 k 层节点个数的计算
  22. (9)二叉树查找值为 x 的节点
  23. (10)判断二叉树是否是完全二叉树
  24. 四、二叉树实现完整代码
  25. 1. Queue.h
  26. 2. Queue.c
  27. 3. BTNode.h
  28. 4. BTNode.c
  29. 5. Test.c
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 医疗送药机器人“空间拓扑优化+动态算法决策+多级容错控制”三重链式编程技术解析与应用
  • C/C++ 入门学习指南
  • SSTI 模板注入漏洞实战:从基础到绕过技巧
  • 在 Android 设备上利用 Termux 安装 llama.cpp 并启动 WebUI
  • 鸿蒙金融理财全栈项目:运维监控、性能优化与安全加固
  • VSCode Copilot 登录失败排查与解决方案
  • Python for 循环详解与实战案例
  • PyArrow:Apache Arrow 的 Python 绑定及高效内存数据交换
  • DeepSeek-V3.2 深度拆解:开源模型架构与推理能力解析
  • Python 开发实战:集成本地 DocsGPT 构建智能 IDE
  • 城市场景下无人机三维路径规划的 NMOPSO 多目标优化算法
  • OpenAI发布GPT-5.3 Instant:幻觉率降低26.8%,2026全球AI模型排行榜
  • 35 道常见前端 Vue 面试题解析
  • ComfyUI-Workflows-ZHO 工作流使用指南
  • PyTorch 文本引导图像生成与 Stable Diffusion 实践
  • Python 基于 LangGraph 搭建带记忆与人工干预的搜索机器人
  • Java JDK 25 官网下载、安装及环境变量配置指南
  • EasyPostman:开源免费的 Postman 替代方案,支持国产化操作系统
  • 鸿蒙纯血版侧载第三方应用实操指南
  • Visual Studio 2022 AI 编码工具 Windsurf 功能与安装指南

相关免费在线工具

  • 加密/解密文本

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