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

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

综述由AI生成系统讲解了二叉树的数据结构基础,涵盖定义、术语、特殊类型及性质。重点阐述了二叉树的链式存储方式,并通过 C 语言代码实现了二叉树的构建、前中后序遍历、层序遍历、节点统计、高度计算、查找及完全二叉树判断功能,提供了完整的头文件与源文件代码示例。

魔法巫师发布于 2026/3/30更新于 2026/5/2430 浏览
数据结构:二叉树初阶与实现

前言

本文主要讲解二叉树的基础概念及其在 C 语言中的实现。

一、树是什么?

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

3. 代码实现

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

(1)创建头文件&源文件

在写复杂程序时要养成写多个头文件&源文件的好习惯,这样条理就很清晰也不会乱。

  • 创建了一个 BTNode.h 头文件,用于存放函数的声明和一些库函数的头文件。
  • 创建了一个 BTNode.c 源文件,用于放函数的定义 (二叉树的主体)。
  • 还有一个 Test.c 源文件,用于测试实现的二叉树的运行效果。
(2)定义二叉树

首先我们要定义一个二叉树。这里之前讲过,创建一个类似链表的结构,每个结点里面包含三个成员:节点中存储的数据、指向左孩子的指针、指向右孩子的指针。

代码演示:(内有注释,在头文件 BTNode.h 中写)

//重定义,方便修改类型
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)二叉树的层序遍历

这里看到小编特意把层序遍历与前三个遍历分开写,也可以看出层序的不一样了。也确实,层序遍历与前三种遍历差了很多。

首先,层序遍历是什么?层序层序,就是一层一层的遍历。比如下图的二叉树:该二叉树层序遍历的结果就是【1,2,3,4,5,6,7】。也就是一层一层遍历,这就是层序遍历。那又要该怎么写代码呢?

接着,代码思路是什么?我们怎么做到遍历完 1,2,3 后去遍历 4,5 呢?此时也无法直接通过 2 来找到 4,5 了。这时我们可以想到我们之前学过的东西——队列。我们可以在每次取出结点的同时,将该结点的左结点和右结点存储进队列中,直到队列为空。

例如: 开始时放入 1,此时队列【1】 取出 1,并放入 2、3,此时队列【2,3】 取出 2,并放入 4、5,此时队列【3,4,5】 取出 3,并放入 6、7,此时队列【4,5,6,7】 取出 4,此时队列【5,6,7】 取出 5,此时队列【6,7】 取出 6,此时队列【7】 取出 7,此时队列【】

在前面的学习中小编已经讲解过了队列了,这里就不赘述,想知道的可以去看看相关教程。

最后,代码实现。现在,知道了要用队列来实现,就可以开始来写代码了。我们创建一个队列,先把第一个根节点存入。接着在每次取出结点的同时,将该结点的左结点和右结点存储进队列中。以此循环直到队列为空。

代码演示:(不包含队列的实现)

// 层序遍历
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)二叉树节点个数的计算

当我们想要用代码来计数二叉树中的所有节点个数,该怎么办呢?其实这很简单,只需要熟悉递归就行了。当根节点为空时,就计 0。而当根节点为非空时,就 + 1 并递归计算其左右子树再。

代码演示:

// 二叉树节点个数
int BinaryTreeSize(BTNode* root) {
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1; //递归左右子树并 +1
}
(7)二叉树高度的计算

当我们想要用代码来计数二叉树的高度,该怎么办呢?首先我们要知道二叉树高度是什么,是树中结点的最大层次。而二叉树有左子树以及右子树,故左右子树高度更高的一边 + 1 就是树的高度。

所以,我们可以用递归来实现。当根节点为空时,返回 0。而当根节点为非空时,就返回左右子树高度更高的一边并 + 1(根节点本身)。

代码演示:

// 二叉树高度
int BinaryTreeHight(BTNode* root) {
    //当根节点为空时,返回 0
    if (root == NULL) {
        return 0;
    }
    return fmax(BinaryTreeHight(root->left), BinaryTreeHight(root->right)) + 1; //而当根节点为非空时,返回左右子树高度更高的一边并 + 1(根节点本身)
}
(8)二叉树第 k 层节点个数的计算

当我们想要用代码来计数二叉树第 k 层的节点个数,该怎么办呢?对于一个满二叉树很简单,套公式就行,但非完全二叉树咋办呢?这里依旧是递归:初始 k,若该层不是目标层次,就 k - 1 并递归左右子树。当根节点为空时,返回 0。当根节点为非空且此时 k == 1 时,返回 1(已经到达第 k 层)。

代码演示:

// 二叉树第 k 层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k) {
    //当根节点为空时,返回 0
    if (root == NULL) {
        return 0;
    }
    //当根节点为非空且此时 k == 1 时,返回 1(已经到达第 k 层)
    if (k == 1) {
        return 1;
    }
    return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1); //若该层不是目标层次,就 k - 1 并递归左右子树
}
(9)二叉树查找值为 x 的节点

当我们想要用代码来查找值为 x 的节点,该怎么办呢?这里依旧是递归:当根节点为空时,返回空。当根节点的值为 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;
}
(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);
            //返回 0(假)
            return 0;
        }
    }
    //最后不要忘记了销毁队列
    QueueDestroy(&Q);
    //最后返回 1(真)
    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);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回 0
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; //全部初始化置 0 / NULL
}

// 队尾入队列
void QueuePush(Queue* q, QDataType data) {
    assert(q); //断言空指针
    QNode* tmp = (QNode*)malloc(sizeof(QNode)); //直接开辟一个节点的空间
    if (tmp == NULL) //加一个 if 语句 防止增容失败
    {
        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;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回 0
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; //全部初始化置 0 / NULL
}

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);
// 二叉树第 k 层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为 x 的节点
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) {
    //当根节点为空时,返回 0
    if (root == NULL) {
        return 0;
    }
    return fmax(BinaryTreeHight(root->left), BinaryTreeHight(root->right)) + 1;
}

// 二叉树第 k 层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k) {
    //当根节点为空时,返回 0
    if (root == NULL) {
        return 0;
    }
    //当根节点为非空且此时 k == 1 时,返回 1(已经到达第 k 层)
    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;
}

// 判断二叉树是否是完全二叉树
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);
            //返回 0(假)
            return 0;
        }
    }
    //最后不要忘记了销毁队列
    QueueDestroy(&Q);
    //最后返回 1(真)
    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);
    // 二叉树第 k 层节点个数
    int ret4 = BinaryTreeLevelKSize(T, 3);
    printf("二叉树第 k 层节点个数:%d\n", ret4);
    // 二叉树查找值为 x 的节点
    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\n");
    // 二叉树中序遍历
    printf("中序:");
    BinaryTreeInOrder(T);
    printf("\n\n");
    // 二叉树后序遍历
    printf("后序:");
    BinaryTreePostOrder(T);
    printf("\n\n");
    // 层序遍历
    printf("层序:");
    BinaryTreeLevelOrder(T);
    printf("\n\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. (1)创建头文件&源文件
  16. (2)定义二叉树
  17. (3)构建二叉树
  18. (4)二叉树遍历(前中后序)
  19. (5)二叉树的层序遍历
  20. (6)二叉树节点个数的计算
  21. (7)二叉树高度的计算
  22. (8)二叉树第 k 层节点个数的计算
  23. (9)二叉树查找值为 x 的节点
  24. (10)判断二叉树是否是完全二叉树
  25. 四、二叉树实现完整代码
  26. 1. Queue.h
  27. 2. Queue.c
  28. 3. BTNode.h
  29. 4. BTNode.c
  30. 5. Test.c
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Java 正则表达式基础与实战:元字符、限定符及 Email 验证
  • 前端开发基础:HTML、CSS 与 JavaScript 入门
  • 混合知识库搭建:本地 Docker 部署 Neo4j 图数据库与 Milvus 向量库
  • Python Pandas Timestamp 常用属性与方法详解
  • 基于C#的OPC转Web API服务器框架源码,集成IoT与Modbus及PLC协议
  • Xilinx 7 系列 FPGA 数据手册核心特性与选型指南
  • Python Google Search API 集成实战与无依赖方案
  • C++ 内存管理:malloc 原理与实现
  • AI 开发中的风险与治理:安全、可控性与责任边界
  • AIOps 实践:基于 Dify+LangBot 实现飞书智能体对话机器人
  • C++ 算法刷题:气球排列、迷宫搜索与主持人调度
  • Spring Cloud + Nacos 微服务从 0 到 1 搭建实战
  • Python 中 == 与 is 操作符的本质区别与应用实践
  • 字符串算法基础:暴力搜索、KMP 与编辑距离
  • Openclaw 连接本地 Ollama 及 Qwen WebUI 无响应排查
  • C++ 类和对象基础详解
  • Python Pandas 核心数据结构与操作实战指南
  • DeepSeek-R1 模型集成与 OpenAI SDK 调用实践
  • C++ 单链表概念与结构详解
  • Web 服务基石:Nginx 从安装到高级配置实战

相关免费在线工具

  • 加密/解密文本

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