🌳 链式二叉树 ------ 用指针构建的树形世界
✅ 专为初学者设计
✅ 图文比喻 + 递归思想讲解
✅ 包含完整 C 语言代码(头文件 + 实现 + 测试)
✅ 覆盖:创建、遍历、统计、查找、销毁、层序、完全二叉树判断等核心操作
第一章:为什么用"链"表示二叉树?
数组适合完全二叉树(如堆),但现实中的树往往"歪歪扭扭"------有些结点只有左孩子,有些只有右孩子,甚至中间空一大片。
这时候,链式结构就派上用场了!
链式二叉树的结点结构
每个结点包含三部分:
- 数据域:存值(比如 1、2、'A')
- 左指针:指向左孩子
- 右指针:指向右孩子
c
typedef int BTDataType; // 可根据需要改为 char、double 等
typedef struct BinaryTreeNode {
struct BinaryTreeNode* left; // 左孩子
struct BinaryTreeNode* right; // 右孩子
BTDataType val; // 数据
} BTNode;
💡 这就像每个人手里拿着两张"名片":一张给左娃,一张给右娃。
第二章:手动创建一棵二叉树(先跑起来!)
为了快速入门,我们不从输入建树开始 ,而是手动构造一棵树用于测试。
辅助函数:创建新结点
c
BTNode* BuyNode(BTDataType x) {
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (!node) {
perror("malloc failed");
exit(-1);
}
node->val = x;
node->left = node->right = NULL;
return node;
}
手动建树函数
我们构造如下这棵树:
c
1
/ \
2 4
/ / \
3 5 6
/
7
c
BTNode* CreateTree() {
BTNode* n1 = BuyNode(1);
BTNode* n2 = BuyNode(2);
BTNode* n3 = BuyNode(3);
BTNode* n4 = BuyNode(4);
BTNode* n5 = BuyNode(5);
BTNode* n6 = BuyNode(6);
BTNode* n7 = BuyNode(7);
n1->left = n2;
n1->right = n4;
n2->left = n3;
n4->left = n5;
n4->right = n6;
n5->left = n7;
return n1; // 返回根结点
}
✅ 这样我们就有了一个"玩具树",可以用来测试各种操作!
第三章:二叉树的遍历 ------ 走遍整棵树
由于二叉树是递归定义 的(根 + 左子树 + 右子树),所以遍历也天然适合用递归实现。
三种深度优先遍历
| 遍历方式 | 访问顺序 | 口诀 |
|---|---|---|
| 前序遍历 | 根 → 左 → 右 | "自己先干,再叫左右" |
| 中序遍历 | 左 → 根 → 右 | "左边干完,自己上,右边收尾" |
| 后序遍历 | 左 → 右 → 根 | "左右都干完,自己最后收工" |
完整代码实现(带 NULL 标记便于理解结构)
c
// 前序遍历:根 左 右
void PreOrder(BTNode* root) {
if (root == NULL) {
printf("N ");
return;
}
printf("%d ", root->val);
PreOrder(root->left);
PreOrder(root->right);
}
// 中序遍历:左 根 右
void InOrder(BTNode* root) {
if (root == NULL) {
printf("N ");
return;
}
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}
// 后序遍历:左 右 根
void PostOrder(BTNode* root) {
if (root == NULL) {
printf("N ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->val);
}
🔍 对于上面那棵树,输出结果为:
- 前序:
1 2 3 N N N 4 5 7 N N N 6 N N- 中序:
N 3 N 2 N 1 N 7 N 5 N 4 N 6 N- 后序:
N N 3 N N 2 N N N 7 N N 5 N N 6 4 1
💡 实际应用中,若不需要打印N,可去掉if (root == NULL)中的printf。
第四章:二叉树的常用操作(递归实现)
求结点总数
c
int BinaryTreeSize(BTNode* root) {
if (root == NULL) return 0;
return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
🧠 思想:当前结点(1个) + 左子树大小 + 右子树大小
求叶子结点数(度为0的结点)
c
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);
}
求第 k 层的结点个数
c
int BinaryTreeLevelKSize(BTNode* root, int k) {
if (root == NULL || k < 1) return 0;
if (k == 1) return 1; // 当前层就是第k层
// 否则去左右子树找第 (k-1) 层
return BinaryTreeLevelKSize(root->left, k - 1) +
BinaryTreeLevelKSize(root->right, k - 1);
}
求树的高度(深度)
c
int BinaryTreeDepth(BTNode* root) {
if (root == NULL) return 0;
int leftH = BinaryTreeDepth(root->left);
int rightH = BinaryTreeDepth(root->right);
return 1 + (leftH > rightH ? leftH : rightH);
}
⏱️ 时间复杂度:O(n),每个结点访问一次
查找值为 x 的结点
c
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
if (root == NULL) return NULL;
if (root->val == x) return root;
// 先在左子树找
BTNode* leftRet = BinaryTreeFind(root->left, x);
if (leftRet) return leftRet;
// 左边没找到,去右边找
BTNode* rightRet = BinaryTreeFind(root->right, x);
return rightRet;
}
✅ 返回的是结点地址,可用于后续操作(如修改值)
销毁整棵树(后序释放)
c
void BinaryTreeDestroy(BTNode** root) {
if (*root == NULL) return;
// 先销毁左右子树
BinaryTreeDestroy(&(*root)->left);
BinaryTreeDestroy(&(*root)->right);
// 再释放自己
free(*root);
*root = NULL; // 防止野指针
}
❗ 注意:传的是
BTNode**,这样才能把外部指针置为NULL
第五章:层序遍历(广度优先)------ 用队列实现
前中后序是"深度优先",而层序遍历 是"一层一层扫",需要用到队列。
队列辅助结构(简版)
假设已有队列实现(支持 QueueInit, QueuePush, QueuePop, QueueFront, QueueEmpty, QueueDestroy),且队列存储 BTNode* 类型。
📌 若你没有队列代码,文末会附上简易队列实现。
层序遍历代码
c
void LevelOrder(BTNode* root) {
if (root == NULL) return;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->val); // 访问当前结点
// 把非空孩子入队
if (front->left) QueuePush(&q, front->left);
if (front->right) QueuePush(&q, front->right);
}
QueueDestroy(&q);
}
🌟 输出示例:
1 2 4 3 5 6 7(按层从左到右)
第六章:判断是否为完全二叉树
完全二叉树定义 :
除最后一层外,其他层全满;最后一层结点靠左连续排列,不能有"空洞"。
判断思路(利用层序遍历)
- 按层序遍历,遇到 NULL 也入队;
- 一旦遇到第一个
NULL,后面所有结点都必须是 NULL; - 如果后面还有非空结点 → 不是完全二叉树。
代码实现
c
bool BinaryTreeComplete(BTNode* root) {
if (root == NULL) return true;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
// 步骤1:正常层序,直到遇到第一个 NULL
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL) break;
// 即使孩子是 NULL,也入队(关键!)
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
// 步骤2:检查剩余队列是否全是 NULL
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL) {
QueueDestroy(&q);
return false; // 发现非空 → 不是完全二叉树
}
}
QueueDestroy(&q);
return true;
}
✅ 对于我们构造的树(第5层只有7在最左),它是完全二叉树 吗?
答案:不是 !因为第4层的
n6(6)没有左孩子,但n5(5)有左孩子(7),导致最后一层不连续。
第七章:完整项目结构(可直接编译运行)
头文件 BinaryTree.h
c
// BinaryTree.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "Queue.h" // 需要队列支持层序遍历
typedef int BTDataType;
typedef struct BinaryTreeNode {
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType val;
} BTNode;
// 创建结点
BTNode* BuyNode(BTDataType x);
// 手动建树(用于测试)
BTNode* CreateTree();
// 遍历
void PreOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root);
void LevelOrder(BTNode* root);
// 统计类
int BinaryTreeSize(BTNode* root);
int BinaryTreeLeafSize(BTNode* root);
int BinaryTreeLevelKSize(BTNode* root, int k);
int BinaryTreeDepth(BTNode* root);
// 查找与销毁
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
void BinaryTreeDestroy(BTNode** root);
// 完全二叉树判断
bool BinaryTreeComplete(BTNode* root);
实现文件 BinaryTree.c(见上文各函数)
将上述所有函数放入此文件。
简易队列实现(Queue.h + Queue.c)
若你没有队列,可用以下简易版本(基于链表):
c
// Queue.h
#pragma once
#include "BinaryTree.h"
typedef BTNode* QDataType;
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
} QNode;
typedef struct Queue {
QNode* head;
QNode* tail;
} Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
bool QueueEmpty(Queue* pq);
c
// Queue.c
#include "Queue.h"
#include <assert.h>
void QueueInit(Queue* pq) {
pq->head = pq->tail = NULL;
}
void QueueDestroy(Queue* pq) {
QNode* cur = pq->head;
while (cur) {
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QDataType x) {
QNode* newnode = (QNode*)malloc(sizeof(QNode));
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL) {
pq->head = pq->tail = newnode;
} else {
pq->tail->next = newnode;
pq->tail = newnode;
}
}
void QueuePop(Queue* pq) {
assert(pq && !QueueEmpty(pq));
if (pq->head->next == NULL) {
free(pq->head);
pq->head = pq->tail = NULL;
} else {
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
}
}
QDataType QueueFront(Queue* pq) {
assert(pq && !QueueEmpty(pq));
return pq->head->data;
}
bool QueueEmpty(Queue* pq) {
return pq->head == NULL;
}
主函数测试 main.c
c
// main.c
#include "BinaryTree.h"
int main() {
BTNode* root = CreateTree();
printf("前序遍历: "); PreOrder(root); printf("\n");
printf("中序遍历: "); InOrder(root); printf("\n");
printf("后序遍历: "); PostOrder(root); printf("\n");
printf("层序遍历: "); LevelOrder(root); printf("\n");
printf("\n结点总数: %d\n", BinaryTreeSize(root));
printf("叶子结点数: %d\n", BinaryTreeLeafSize(root));
printf("第3层结点数: %d\n", BinaryTreeLevelKSize(root, 3));
printf("树的高度: %d\n", BinaryTreeDepth(root));
BTNode* found = BinaryTreeFind(root, 5);
if (found) printf("找到值为5的结点,地址: %p\n", (void*)found);
printf("是否为完全二叉树: %s\n",
BinaryTreeComplete(root) ? "是" : "否");
BinaryTreeDestroy(&root);
printf("树已销毁,root = %p\n", (void*)root);
return 0;
}
🎯 总结:链式二叉树核心要点
| 操作 | 方法 | 关键思想 |
|---|---|---|
| 创建结点 | BuyNode |
malloc + 初始化 |
| 遍历 | 递归(前/中/后) | 根据访问顺序调整 printf 位置 |
| 层序遍历 | 队列 | FIFO 模拟逐层访问 |
| 结点统计 | 递归累加 | 分治:1 + 左 + 右 |
| 查找 | 递归搜索 | 先左后右,找到即返回 |
| 销毁 | 后序释放 | 先子后父,避免内存泄漏 |
| 完全二叉树判断 | 层序+NULL标记 | 遇NULL后不能有非空 |
💡 给初学者的建议:
- 先画出你构造的树,再对照遍历结果;
- 递归函数多用"分治"思想:处理当前 + 递归左右;
- 层序遍历一定要掌握队列的使用;
- 完全二叉树判断是面试高频题!
这份笔记+代码,让你从零彻底掌握链式二叉树的核心操作!
加油,未来的数据结构高手!🌳✨