数据结构篇(八)——二叉树
在计算机科学中二叉树Binary Tree是最基础也是最核心的数据结构之一。无论是数据库的索引B树、编译器的语法分析语法树、还是搜索引擎的排序堆排序背后都离不开二叉树的影子。简单来说二叉树是一种每个节点最多只有两个子节点的树形结构。这个最多两个的限制看似简单却衍生出了无数精妙的算法和数据结构——二叉搜索树、平衡二叉树、堆、哈夫曼树、红黑树……掌握二叉树就等于拿到了打开数据结构和算法大门的钥匙。本文将从零开始用C 语言带你逐步实现一个完整的二叉树涵盖定义、创建、遍历、查找、销毁等操作代码按照功能拆分为独立的模块方便理解和复用。目录一、基本概念1.二叉树的五种基本形态二、二叉树的性质1.完全二叉树和满二叉树的区分1. 满二叉树2. 完全二叉树三、二叉树的存储结构1. 顺序存储数组2. 链式存储指针四、代码模块实现1.创建节点2.插入节点构建二叉树3.前序遍历Preorder4.中序遍历Inorder5.后序遍历Postorder6.层序遍历Level Order7.获取树的节点个数8.获取树的深度高度9.查找节点10.销毁二叉树释放内存五、代码测试六、完整程序运行效果一、基本概念在进入代码之前先理清二叉树中的几个核心术语术语英文含义节点Node树中的基本单元存储数据和指向子节点的指针根节点Root树的最顶层节点没有父节点左/右孩子Left/Right Child一个节点的左/右子节点父节点Parent指向当前节点的上层节点叶子节点Leaf没有子节点的节点子树Subtree树中任何一个节点及其后代构成的局部树深度Depth从根节点到当前节点的边数高度Height从当前节点到最远叶子节点的边数层Level根节点在第 1 层其孩子在第 2 层以此类推节点的度Degree一个节点拥有的子节点个数1.二叉树的五种基本形态空二叉树 只有根节点 只有左子树 只有右子树 左右子树齐全 ∅ A A A A \ / / \ B B B C二、二叉树的性质1.第 i 层最多有 2^(i-1) 个节点i ≥ 1 2.深度为 k 的二叉树最多有 2^k - 1 个节点 3.叶子节点数 度为 2 的节点数 1记作 n₀ n₂ 1 4.完全二叉树除了最后一层其他层都满且最后一层的节点靠左排列 5.满二叉树所有层的节点数都达到最大值 6.任意二叉树度为 0 的叶子个数比度为 2 的节点个数多 1 应用 具有 2n 个结点的完全二叉树叶子节点个数为 n 假设 度为 0 → N0 个 度为 1 → N1 个 度为 2 → N2 个 N0 N21 → N2 N0-1 则 N0 N1 N0 -1 2n 完全二叉树中度为 1 的节点个数为 0 或 1 又因为有 2n 个节点 (偶数个) 2N0N1-12n N1 只能为 1 ∴ N0 n1.完全二叉树和满二叉树的区分1. 满二叉树除叶子结点度 0外其余所有节点同时拥有左孩子、右孩子每一层节点数量都达到该层最大容量没有空位。高度为 h 的满二叉树总节点数2^(h-1)(1) / \ (2) (3) / \ / \ (4) (5) (6) (7)2. 完全二叉树按从上到下、从左往右顺序填满节点 最后一层可以不满但是节点必须靠左紧密连续排布不允许出现右侧有节点、左侧空缺。(1) / \ (2) (3) / \ / (4) (5) (6)三、二叉树的存储结构二叉树有两种存储方式1. 顺序存储数组适用于完全二叉树。将节点按层序放入数组节点 i 的左孩子下标为2i1右孩子为2i2。A(0) / \ B(1) C(2) / \ \ D(3) E(4) F(5) 数组[A, B, C, D, E, F]缺点非完全二叉树会浪费大量空间。2. 链式存储指针每个节点包含三部分数据域 左孩子指针 右孩子指针。这是最常用的方式本文采用这种方案。结构定义如下// 模块1二叉树的节点结构定义 typedef struct TreeNode { int data; // 数据域这里用 int可替换为任意类型 struct TreeNode *left; // 左孩子指针 struct TreeNode *right;// 右孩子指针 } TreeNode;四、代码模块实现1.创建节点创建单个节点分配内存并初始化。TreeNode* createNode(int data) { TreeNode *newNode (TreeNode*)malloc(sizeof(TreeNode)); if (newNode NULL) { printf(内存分配失败\n); exit(1); } newNode-data data; newNode-left NULL; newNode-right NULL; return newNode; }2.插入节点构建二叉树/** * 按层序构建二叉树 * param arr 包含节点数据的数组-1 表示空节点 * param size 数组长度 * param index 当前处理的数组下标 * return 构建完成的树的根节点 */ TreeNode* buildTree(int arr[], int size, int index) { if (index size || arr[index] -1) { return NULL; } TreeNode *root createNode(arr[index]); // 递归构建左子树下标 2*index1 root-left buildTree(arr, size, 2 * index 1); // 递归构建右子树下标 2*index2 root-right buildTree(arr, size, 2 * index 2); return root; }示例数组 {1, 2, 3, 4, 5, -1, 6} 构建的二叉树1 / \ 2 3 / \ \ 4 5 63.前序遍历Preorder顺序根节点 → 左子树 → 右子树/** * 前序遍历二叉树递归版 * 顺序根 - 左 - 右 * param root 二叉树根节点 */ void preorderTraversal(TreeNode *root) { if (root NULL) { return; } printf(%d , root-data); // 1. 访问根节点 preorderTraversal(root-left); // 2. 遍历左子树 preorderTraversal(root-right); // 3. 遍历右子树 }4.中序遍历Inorder顺序左子树 → 根节点 → 右子树/** * 中序遍历二叉树递归版 * 顺序左 - 根 - 右 * param root 二叉树根节点 */ void inorderTraversal(TreeNode *root) { if (root NULL) { return; } inorderTraversal(root-left); // 1. 遍历左子树 printf(%d , root-data); // 2. 访问根节点 inorderTraversal(root-right); // 3. 遍历右子树 }5.后序遍历Postorder顺序左子树 → 右子树 → 根节点/** * 后序遍历二叉树递归版 * 顺序左 - 右 - 根 * param root 二叉树根节点 */ void postorderTraversal(TreeNode *root) { if (root NULL) { return; } postorderTraversal(root-left); // 1. 遍历左子树 postorderTraversal(root-right); // 2. 遍历右子树 printf(%d , root-data); // 3. 访问根节点 }三种递归遍历的记忆口诀前序根左右中序左根右后序左右根6.层序遍历Level Order顺序从上到下、从左到右逐层访问。需要借助队列来实现这里我们实现一个简单队列配合使用。// ---------- 辅助简单队列结构 ---------- #define MAX_QUEUE_SIZE 100 typedef struct Queue { TreeNode *data[MAX_QUEUE_SIZE]; int front; int rear; } Queue; void initQueue(Queue *q) { q-front 0; q-rear 0; } void enqueue(Queue *q, TreeNode *node) { if ((q-rear 1) % MAX_QUEUE_SIZE q-front) { printf(队列已满\n); return; } q-data[q-rear] node; q-rear (q-rear 1) % MAX_QUEUE_SIZE; } TreeNode* dequeue(Queue *q) { if (q-front q-rear) { return NULL; } TreeNode *node q-data[q-front]; q-front (q-front 1) % MAX_QUEUE_SIZE; return node; } int isQueueEmpty(Queue *q) { return q-front q-rear; } // ---------- 层序遍历 ---------- /** * 层序遍历二叉树借助队列 * 顺序逐层从左到右 * param root 二叉树根节点 */ void levelOrderTraversal(TreeNode *root) { if (root NULL) { return; } Queue q; initQueue(q); enqueue(q, root); while (!isQueueEmpty(q)) { TreeNode *current dequeue(q); printf(%d , current-data); if (current-left ! NULL) { enqueue(q, current-left); } if (current-right ! NULL) { enqueue(q, current-right); } } }7.获取树的节点个数/** * 计算二叉树中节点的个数 * 公式左子树节点数 右子树节点数 1根 * param root 二叉树根节点 * return 节点总数 */ int getNodeCount(TreeNode *root) { if (root NULL) { return 0; } return getNodeCount(root-left) getNodeCount(root-right) 1; }8.获取树的深度高度/** * 计算二叉树的高度深度 * 公式max(左子树高度, 右子树高度) 1 * param root 二叉树根节点 * return 树的高度 */ int getTreeHeight(TreeNode *root) { if (root NULL) { return 0; } int leftHeight getTreeHeight(root-left); int rightHeight getTreeHeight(root-right); return (leftHeight rightHeight ? leftHeight : rightHeight) 1; }9.查找节点/** * 在二叉树中查找值为 target 的节点 * param root 二叉树根节点 * param target 要查找的目标值 * return 找到返回指向该节点的指针否则返回 NULL */ TreeNode* searchNode(TreeNode *root, int target) { if (root NULL) { return NULL; } if (root-data target) { return root; } // 先在左子树找 TreeNode *found searchNode(root-left, target); if (found ! NULL) { return found; } // 左子树没找到再去右子树找 return searchNode(root-right, target); }10.销毁二叉树释放内存/** * 销毁整棵二叉树释放所有节点内存 * 使用后序遍历先释放子树再释放根 * param root 二叉树根节点二级指针释放后置 NULL */ void destroyTree(TreeNode **root) { if (*root NULL) { return; } destroyTree(((*root)-left)); // 1. 释放左子树 destroyTree(((*root)-right)); // 2. 释放右子树 free(*root); // 3. 释放当前节点 *root NULL; // 4. 指针置空防止野指针 }为什么用二级指针因为我们需要在函数内部修改调用方的root指针将其置为 NULL。如果只传一级指针函数内修改的是指针的副本调用方的指针仍是野指针。五、代码测试#include stdio.h #include stdlib.h // 在此处粘贴上述所有模块代码 ... int main() { // 用数组构建一棵二叉树 // 树结构 // 1 // / \ // 2 3 // / \ \ // 4 5 6 int arr[] {1, 2, 3, 4, 5, -1, 6}; int size sizeof(arr) / sizeof(arr[0]); TreeNode *root buildTree(arr, size, 0); printf( 二叉树的遍历 \n); printf(前序遍历); preorderTraversal(root); printf(\n); printf(中序遍历); inorderTraversal(root); printf(\n); printf(后序遍历); postorderTraversal(root); printf(\n); printf(层序遍历); levelOrderTraversal(root); printf(\n\n); printf( 树的基本信息 \n); printf(节点个数%d\n, getNodeCount(root)); printf(树的高度%d\n\n, getTreeHeight(root)); printf( 查找节点 \n); int target 5; TreeNode *found searchNode(root, target); if (found ! NULL) { printf(找到节点%d\n\n, found-data); } else { printf(未找到节点%d\n\n, target); } // 释放内存 destroyTree(root); if (root NULL) { printf(二叉树已成功销毁\n); } return 0; }六、完整程序运行效果 二叉树的遍历 前序遍历1 2 4 5 3 6 中序遍历4 2 5 1 3 6 后序遍历4 5 2 6 3 1 层序遍历1 2 3 4 5 6 树的基本信息 节点个数6 树的高度3 查找节点 找到节点5 二叉树已成功销毁总结本文梳理了二叉树基础理论与链式二叉树全套代码实现。遍历是二叉树核心熟练掌握本节内容可为后续学习高阶树形结构打下基础。

相关新闻

OpenClaw智能体如何重构现代工作流与行业实践

OpenClaw智能体如何重构现代工作流与行业实践

1. 智能体革命:OpenClaw如何重构现代工作流2026年的职场正在经历一场前所未有的变革。作为一名深度参与多个行业智能化改造的技术顾问,我亲眼见证了OpenClaw这类数字员工框架如何彻底改变工作方式。不同于早期AI仅能完成单一任务,现在的智能体…

2026/7/26 7:10:38阅读更多 →
ROS 2 Jazzy 接入 A2M7 激光雷达实战:从电机不转、CH340 错码到 25 Hz 稳定 /scan

ROS 2 Jazzy 接入 A2M7 激光雷达实战:从电机不转、CH340 错码到 25 Hz 稳定 /scan

测试平台:Raspberry Pi CM4、Ubuntu 24.04、ROS 2 Jazzy、A2M7、CH340 USB-TTL本文记录一次真实排障过程。结论来自实机日志、连续帧统计和 rosbag 回放,不是根据“节点能启动”推断成功。一、最终解决到了什么程度这次接入最后取得了以下结果&#xff1…

2026/7/26 7:10:38阅读更多 →
PTA基础编程题目集 7-4 BCD解密(C语言实现)

PTA基础编程题目集 7-4 BCD解密(C语言实现)

题目描述摘要:本文介绍了一道基于 BCD 码误解的编程题。题目给出一个被错误当作二进制数转成十进制的 BCD 值(范围 0–153),要求程序将其还原为正确的十进制数。核心思路是将错误值的高 4 位和低 4 位分离,再按十进制位…

2026/7/26 7:10:38阅读更多 →
数组作为函数参数为什么要多传一个长度_Day10

数组作为函数参数为什么要多传一个长度_Day10

数组作为函数参数为什么要多传一个长度?——Day10 学习记录,从数组传参到日历打印 学完函数之后,我开始用函数封装各种功能。但很快就遇到了问题——把数组传给函数后,用 sizeof 计算长度,结果根本不是数组的大小。 后…

2026/7/26 8:26:54阅读更多 →
windows网络适配器驱动开发-开发 WiFiCx 客户端驱动程序(八)

windows网络适配器驱动开发-开发 WiFiCx 客户端驱动程序(八)

电源策略更改对于电源管理,客户端驱动程序使用 NETPOWERSETTINGS 对象 ,例如其他类型的 NetAdapterCx 客户端驱动程序。为了在系统处于工作状态(S0)状态时支持设备闲置,驱动程序调用 WdfDeviceAssignS0IdleSettings 并…

2026/7/26 8:26:54阅读更多 →
DPO技术如何优化AIGC图像生成审美表现

DPO技术如何优化AIGC图像生成审美表现

1. 项目概述:DPO技术如何重塑AIGC审美维度最近在调试Stable Diffusion模型时发现一个有趣现象:同样的提示词,经过DPO(Direct Preference Optimization)调优后的模型产出图像,在构图和色彩协调性上明显优于传…

2026/7/26 8:26:54阅读更多 →
大语言模型集成前必问的6个关键问题:从业务场景到技术落地的系统评估

大语言模型集成前必问的6个关键问题:从业务场景到技术落地的系统评估

在技术团队考虑引入大语言模型(LLM)时,很多决策者容易被其强大的生成能力吸引,却忽略了前期评估的重要性。盲目集成LLM可能导致项目偏离实际需求、资源浪费甚至技术债堆积。本文基于多个落地案例,梳理出六个关键评估问…

2026/7/26 8:26:54阅读更多 →
用“舞台换景”讲清 Docker 的 Restart 与 Recreate

用“舞台换景”讲清 Docker 的 Restart 与 Recreate

最近更新一个 Docker 服务时,我遇到了一个问题。 镜像拉取成功,容器也重新启动了,整个过程没有任何报错: docker compose pull app docker compose restart app可打开页面一看,显示的仍然是 V1。 第一反应通常是&#…

2026/7/26 8:26:54阅读更多 →
Compute Shader核心原理与GPU并行计算优化实战指南

Compute Shader核心原理与GPU并行计算优化实战指南

1. 项目概述:为什么ComputeShader是图形学性能的“核武器”?在图形渲染管线里,我们习惯了顶点着色器、片元着色器的流水线作业。但当你需要处理海量粒子模拟、复杂物理碰撞、实时体素化或者全局光照的预计算时,传统的渲染管线就显…

2026/7/26 8:24:49阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/26 0:01:28阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/26 0:01:28阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/26 0:01:28阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/26 0:01:28阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/26 0:01:28阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/26 0:01:28阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/25 23:03:25阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/25 19:03:04阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/25 19:03:04阅读更多 →