跳到主要内容
数据结构:二叉树基础与 C 语言实现 | 极客日志
C 算法
数据结构:二叉树基础与 C 语言实现 二叉树作为非线性数据结构的重要分支,分为满二叉树和完全二叉树等类型。详细讲解了二叉树的定义、性质及链式存储实现方法。通过 C 语言代码演示了前序、中序、后序遍历以及层序遍历的逻辑,并实现了节点计数、高度计算、查找及完全二叉树判断等功能。内容涵盖队列辅助的层序遍历逻辑,适合初学者系统掌握二叉树核心操作。
星辰大海 发布于 2026/3/25 更新于 2026/4/25 1 浏览前言
在掌握了顺序表、链表、栈以及队列等基础数据结构后,我们继续深入非线性结构的学习。今天我们来聊聊——二叉树。
一、树是什么?
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,则一棵非空二叉树的第 i 层上最多有 2^(i-1) 个结点。
若规定根结点的层数为 1,则深度为 h 的二叉树的最大结点数是 2^h - 1。
对任何一棵二叉树,如果度为 0 的叶结点个数为 n₀,度为 2 的分支结点个数为 n₂,则有 n₀ = n₂ + 1。
若规定根结点的层数为 1,具有 n 个结点的满二叉树的深度,h = log₂(n + 1)。
对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从 0 开始编号:
若父亲在数组中下标为 i,则该父亲的左孩子下标为 2 * i + 1;右孩子下标为 2 * i + 2。
若孩子在数组中下标为 i,则该孩子的父亲下标为 (i - 1) / 2。
注意:前提是这些下标所对应的值存在!!!**
以上的性质都是基于数学推导的基本常识,感兴趣的可以自己尝试推导一下。
三、二叉树的实现(重点)
1. 二叉树的链式存储(非完全二叉树) 前面我们说到存储完全二叉树可以用数组,是因为完全二叉树的编号是连续的。而非完全二叉树却不是如此,若强行用数组来存储会浪费大量的空间 。
那么除了数组,我们还可以用什么来储存二叉树呢?我们会想到链式存储 。
链式存储 顾名思义,就是用链表来表示一颗二叉树,用链来指示元素的逻辑关系。通常的方法是创建一链表,使每个结点由三个域组成:数据域和左右指针域 。左右指针分别用来给出该结点左孩子和右孩子所在结点的地址。
2. 实现思路 知道了用链式存储,现在就可以开始实现代码了。与单链表的实现类似,二叉树中的每一个节点有以下三个成员:
节点中存储的数据
指向节点左孩子的指针
指向节点右孩子的指针
3. 代码实现
(1)文件结构 在写复杂程序时,建议养成写多个头文件和源文件的好习惯,这样条理清晰也不会乱。
BTNode.h :用于存放函数声明和一些库函数的头文件。
BTNode.c :用于存放函数的定义(二叉树的主体)。
Test.c :用于测试实现的二叉树的运行效果。
(2)定义二叉树 首先我们要定义一个二叉树。这里之前讲过,创建一个类似链表的结构,每个结点里面包含三个成员:节点中存储的数据、指向左孩子的指针、指向右孩子的指针。
typedef char BTDataType;
typedef struct BinaryTreeNode {
BTDataType data;
struct BinaryTreeNode * left ;
struct BinaryTreeNode * right ;
} BTNode;
本文是以 char 类型为例,但如果以后要将二叉树中的元素类型修改成 int 类型或是其他类型,一个一个修改就很麻烦。所以我们重定义 char 类型为 BTDataType,并将接下来代码中的 char 类型全部写成 BTDataType。这是为了方便我们以后对类型进行修改,仅需将 char 改为其他类型即可。
在定义二叉树的同时重定义二叉树的变量名为 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】
...以此类推
在前面的学习中已经讲解过了队列了,这里就不赘述。知道了要用队列来实现,就可以开始来写代码了。我们创建一个队列,先把第一个根节点存入,接着在每次取出结点的同时,将该结点的左结点和右结点存储进队列中,以此循环直到队列为空。
代码演示(不包含队列的实现):
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 ;
}
(7)二叉树高度的计算 当我们想要用代码来计数二叉树的高度,该怎么办呢?首先我们要知道二叉树高度是什么,是树中结点的最大层次。而二叉树有左子树以及右子树,故左右子树高度更高的一边 + 1 就是树的高度。
所以,我们可以用递归来实现。当根节点为空时,返回 0;而当根节点为非空时,就返回左右子树高度更高的一边并 + 1(根节点本身)。
int BinaryTreeHight (BTNode* root) {
if (root == NULL ) {
return 0 ;
}
return fmax(BinaryTreeHight(root->left), BinaryTreeHight(root->right)) + 1 ;
}
(8)二叉树第 k 层节点个数的计算 当我们想要用代码来计数二叉树第 k 层的节点个数,该怎么办呢?对于一个满二叉树很简单,套公式就行,但非完全二叉树咋办呢?这里依旧是递归:初始 k,若该层不是目标层次,就 k - 1 并递归左右子树。当根节点为空时,返回 0;当根节点为非空且此时 k == 1 时,返回 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 );
}
(9)二叉树查找值为 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;
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\n" );
printf ("中序:" );
BinaryTreeInOrder(T);
printf ("\n\n" );
printf ("后序:" );
BinaryTreePostOrder(T);
printf ("\n\n" );
printf ("层序:" );
BinaryTreeLevelOrder(T);
printf ("\n\n" );
BinaryTreeDestory(&T);
return 0 ;
}
结语 本文涵盖了二叉树的核心概念与 C 语言实战实现。希望这些内容能帮助你更好地掌握这一重要的数据结构。如果在阅读过程中有任何疑问,欢迎交流探讨。
相关免费在线工具 加密/解密文本 使用加密算法(如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