跳到主要内容数据结构:二叉树初阶与链式实现 | 极客日志C算法
数据结构:二叉树初阶与链式实现
二叉树作为数据结构中的核心非线性结构,涵盖定义、性质及特殊形态。本文重点阐述链式存储实现,包括前中后序递归遍历、层序遍历队列实现,以及节点计数、高度计算、查找与完全性判断等关键算法。通过完整的 C 语言代码示例,展示从节点定义到功能测试的工程化落地过程,适合初学者构建扎实的数据结构基础。
微码行者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. 节点定义
与单链表类似,二叉树中的每一个节点有以下三个成员:
- 节点中存储的数据
- 指向节点左孩子的指针
- 指向节点右孩子的指针
为了便于类型修改,我们使用 typedef 重定义数据类型。
#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* 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;
}
二叉树高度计算
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)
#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);
#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;
}
本文涵盖了二叉树的核心概念与工程实现细节。通过上述代码,你可以直接编译运行并观察不同遍历方式及统计函数的效果。在实际开发中,理解这些底层逻辑有助于更好地设计算法和处理复杂的数据关系。
相关免费在线工具
- 加密/解密文本
使用加密算法(如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