数据结构篇(八)——二叉树
在计算机科学中二叉树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/9/26 4:27:51 阅读更多 →
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/10/4 21:36:11 阅读更多 →
PTA基础编程题目集 7-4 BCD解密(C语言实现)

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

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

2026/10/5 11:18:52 阅读更多 →

最新新闻

隔离内网AI Agent落地方案:从模型选型到并发压测全指南

隔离内网AI Agent落地方案:从模型选型到并发压测全指南

把 AI Agent 推进隔离内网的时候,我最直观的感受是:网上那些 Agent 演示项目,到了内网几乎没有一个能直接跑起来。这不是代码写得不行,而是它们默认的世界里什么都有——模型权重从 HuggingFace 拉、Python 依赖从 PyPI 装、搜索工…

2026/10/5 14:40:16 阅读更多 →
本地部署大模型:Token自由与数据主权的成本交叉点

本地部署大模型:Token自由与数据主权的成本交叉点

1. 从一张显卡账单说起:为什么企业开始重新算这笔账去年底帮一家做工业质检的客户做技术选型,他们的场景很典型:每天要处理大约两万张缺陷样本图,每张图都要过一遍多模态模型做描述生成和分类打标。一开始走的是公有云API&#xf…

2026/10/5 14:40:16 阅读更多 →
Windows下TensorFlow GPU版安装指南:CUDA与cuDNN版本匹配全解析

Windows下TensorFlow GPU版安装指南:CUDA与cuDNN版本匹配全解析

1. 写在动手之前:TensorFlow GPU版本没那么玄,坑全在版本匹配TensorFlow装GPU版本,十个新手九个在环境上翻车。这活儿本身不复杂,但坑全藏在版本匹配里:显卡驱动、CUDA、cuDNN、Python、TensorFlow本体,五个…

2026/10/5 14:40:16 阅读更多 →
Windows安装TensorFlow GPU版全攻略:CUDA/cuDNN版本匹配与报错排查

Windows安装TensorFlow GPU版全攻略:CUDA/cuDNN版本匹配与报错排查

Windows上装TensorFlow GPU版,说实话不算难,但坑是真的多。很多朋友卡在最后一步,pip install成功,import的时候直接报错,日志里全是什么cudart64_110.dll、cublas64_11.dll找不到,一看就是CUDA和cuDNN版本…

2026/10/5 14:40:15 阅读更多 →
Python登录接口实战:从密码加密到Session/Token登录态保持

Python登录接口实战:从密码加密到Session/Token登录态保持

做登录接口,算是Python后端入门里最典型、也最容易被低估的一个练习。项目名里带着“携程登陆”,说明不少人是想拿真实网站当靶子练手,这个思路没错,但我的建议是:先别急着去模拟别人家的登录,先自己用Pyth…

2026/10/5 14:40:15 阅读更多 →
零售数仓实战:促销敏感度与评论敏感度建模全解析

零售数仓实战:促销敏感度与评论敏感度建模全解析

做了不少零售行业的数仓项目,说实话,像“促销敏感度”和“评论敏感度”这类需求,几乎每个做电商或品牌方数据团队都会接到。老板们通常不会直接说“我要建个模型”,而是扔过来几个很现实的问题:为什么这波满减发出去&a…

2026/10/5 14:39:14 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 5:06:42 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →