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

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

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

2026/7/26 7:10:45 阅读更多 →

最新新闻

RLHF技术在Harness调优中的应用与实战

RLHF技术在Harness调优中的应用与实战

1. 项目概述:RLHF与Harness调优的深度结合 在智能系统开发领域,Harness(测试工具链)的调优一直是个既关键又棘手的环节。传统方法依赖人工规则和静态参数配置,不仅效率低下,还难以应对复杂多变的测试场景。…

2026/7/26 13:16:02 阅读更多 →
UE4导航网格优化与动态调整实战:从原理到性能调优

UE4导航网格优化与动态调整实战:从原理到性能调优

1. 项目概述:导航网格在UE4中的核心地位与挑战 在UE4(Unreal Engine 4)项目开发中,无论是制作一款开放世界RPG,还是一个需要大量NPC交互的策略游戏,AI角色的自主移动能力都是沉浸感的关键。而这一切的基石&…

2026/7/26 13:16:02 阅读更多 →
一分钟学会系列-3.1示波器的使用

一分钟学会系列-3.1示波器的使用

摘要:本文系统讲解数字示波器的工作原理、探头选择、触发设置、显示模式、市电安全测量方法及FFT频谱分析等核心操作,以DS6064为例,涵盖从基础校准到高级应用的完整知识体系。 目录: 1、概述 2、示波器工作原理 3、示波器探头…

2026/7/26 13:16:02 阅读更多 →
TI CC13x2/CC26x2专有模式状态码解析与无线通信调试实战

TI CC13x2/CC26x2专有模式状态码解析与无线通信调试实战

1. 专有模式状态码:无线通信的“心跳”与“诊断书” 在嵌入式无线开发,尤其是基于TI CC13x2/CC26x2这类高度集成的无线MCU进行私有协议栈开发时,最让人头疼的往往不是协议设计本身,而是当通信失败时,你面对的只有一片沉…

2026/7/26 13:16:02 阅读更多 →
CATS Blender插件:从复杂3D模型到VRChat角色的终极自动化指南

CATS Blender插件:从复杂3D模型到VRChat角色的终极自动化指南

CATS Blender插件:从复杂3D模型到VRChat角色的终极自动化指南 【免费下载链接】cats-blender-plugin :smiley_cat: A tool designed to shorten steps needed to import and optimize models into VRChat. Compatible models are: MMD, XNALara, Mixamo, DAZ/Poser,…

2026/7/26 13:16:02 阅读更多 →
三分钟实现专业级人声分离:本地化AI音频处理完整指南

三分钟实现专业级人声分离:本地化AI音频处理完整指南

三分钟实现专业级人声分离:本地化AI音频处理完整指南 【免费下载链接】vocal-separate an extremely simple tool for separating vocals and background music, completely localized for web operation, using 2stems/4stems/5stems models 这是一个极简的人声和背…

2026/7/26 13:15:02 阅读更多 →

日新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/26 0:00:31 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/26 0:00:31 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/26 0:00:31 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/26 0:00:31 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/26 0:00:31 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/26 0:00:31 阅读更多 →

月新闻