【数据结构】C语言实现二叉树(二叉树链式结构,结点个数,深度等)

【数据结构】C语言实现二叉树(二叉树链式结构,结点个数,深度等)

上一篇学习了堆的排序,Topk问题等,如果大家需要可以去观看:

【数据结构】C语言实现二叉树超详细入门(堆的实现,堆排序,TopK问题)-ZEEKLOG博客

目录

一.二叉树链式结构的实现

二.结点个数及高度等

1.二叉树结点个数

代码实现:

说明:

代码走读逻辑(清晰易懂):

2.二叉树叶子结点的个数

代码实现:

3.二叉树的高度

代码实现:

4.二叉树第k层结点的个数

代码实现:

5.二叉树查找值为x的结点

代码实现:

三.二叉树的创建及销毁

1.二叉树的销毁

2.通过前序遍历数组构建二叉树

3.判断二叉树是否是完全二叉树

前置声明:

代码实现:

Queue.h

Queue.c

Test.c

四.总结


一.二叉树链式结构的实现

       在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef char data1; typedef struct BinaryTreeNode { data1 n; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; BTNode* BuyNode(char x) { BTNode* newnode = (BTNode*)malloc(sizeof(BTNode*)); if (newnode == NULL) { perror("malloc fail"); exit(1); } newnode->n = x; newnode->left = NULL; newnode->right = NULL; return newnode; } //返回二叉树的根节点,这不是真正意义上创建的二叉树,而是测试用例 BTNode* CreatBinaryTree() { BTNode* nodeA = BuyNode('A'); BTNode* nodeB = BuyNode('B'); BTNode* nodeC = BuyNode('C'); BTNode* nodeD = BuyNode('D'); BTNode* nodeE = BuyNode('E'); BTNode* nodeF = BuyNode('F'); BTNode* nodeG = BuyNode('G'); BTNode* nodeH = BuyNode('H'); nodeA->left = nodeB; nodeB->left = nodeD; nodeB->right = nodeE; nodeE->right = nodeH; nodeA->right = nodeC; nodeC->left = nodeF; nodeC->right = nodeG; return nodeA; }

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

       1. 空树

       2. 非空:根结点,根结点的左子树、根结点的右子树组成的

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

二.结点个数及高度等

1.二叉树结点个数

若二叉树为空树,结点个数为0;若二叉树不为空树,结点个数为:左子树 + 右子树 + 1(根结点);

代码实现:

//二叉树结点个数 int BinaryTreeSize(BTNode* root) { //判断根是否为空 if (root == NULL) { return 0; } int leftsize = BinaryTreeSize(root->left); int rightsize = BinaryTreeSize(root->right); return leftsize + rightsize + 1; }

说明:

这里的 leftsize 存储的是左子树的个数,rightsize 存储的是右子树的个数。

比如:若递归到3这个结点,先走左子树,左子树为空;走右子树,右子树也为空;然后走return 0+0+1;返回到2的左子树这个函数,然后把1存储到 leftsize 中;接着走右子树,右子树为空;然后return 1+0+1 .......以此回溯,直到回溯到根结点1,然后走右子树.......最后return 2+3+1,即该树的结点为6。

代码走读逻辑(清晰易懂):

说明:

红色箭头为代码运行逻辑,红色数字为结点,蓝色为return 的运算过程,白色则存储的是左子树和右子树上结点的个数

2.二叉树叶子结点的个数

1.若二叉树为空树,则叶子节点的个数为0;

2.若二叉树不为空树,则叶子节点的个数为:左子树 + 右子树

说明:叶子节点是度为0的节点,所以该节点的左子树和右子树都为空时,则说明该节点为叶子节点,否则不为叶子节点。

              

代码实现:

//二叉树叶子结点的个数 int BinaryTreeLeafSize(BTNode* root) { if (root == NULL) { return 0; } if (root->left == NULL && root->right == NULL) { return 1; } int LeftLeafSize = BinaryTreeLeafSize(root->left); int RightLeafSize = BinaryTreeLeafSize(root->right); return LeftLeafSize + RightLeafSize; }

大家可以模仿二叉树结点的个数中的走读逻辑自己动手操作,能够加深印象

3.二叉树的高度

1.若二叉树为空树,则高度为0;

2.若二叉树不为空树,则高度为:max{左子树高度 ,右子树高度}  + 1 ;

                       

代码实现:

//二叉树的高度 int BinaryTreeHighSize(BTNode* root) { if (root == NULL) { return 0; } int lefthigh = BinaryTreeHighSize(root->left); int righthigh = BinaryTreeHighSize(root->right); return lefthigh > righthigh ? lefthigh + 1 : righthigh + 1; } 

4.二叉树第k层结点的个数

思路:

每次递归让k--,直到k==1的时候,return 1;

代码实现:

//二叉树第k层结点个数 int BinaryTreeLevelKSize(BTNode* root,int k) { if (root == NULL) { return 0; } if (k == 1) { return 1; } else { k--; } int LeftLeveKSize = BinaryTreeLevelKSize(root->left, k); int RightLeveKSize = BinaryTreeLevelKSize(root->right, k); return LeftLeveKSize + RightLeveKSize; }

5.二叉树查找值为x的结点

1.若二叉树为空树,则结点为空;

2.若二叉树不为空树,则返回该结点;

说明:若二叉树中有多个结点与x相等,则只返回第一个找到的结点,因为该函数的返回值只能有一个

代码实现:

//二叉树查找值为x的结点 //函数只能返回一个值不能返回多个,所以找到该数直接返回,后面若还有就不管了 BTNode* BinaryTreeFind(BTNode* root, data1 x) { if (root == NULL) { return NULL; } if (x == root->n) { return root; } BTNode* leftnode = BinaryTreeFind(root->left, x); if (leftnode != NULL) { return leftnode; } BTNode* rightnode = BinaryTreeFind(root->right, x); if (rightnode != NULL) { return rightnode; } //如果都没找到返回空 return NULL; } 

三.二叉树的创建及销毁

1.二叉树的销毁

//二叉树的销毁 void TreeDestory(BTNode* root) { if (root == NULL) { return; } TreeDestory(root->left); TreeDestory(root->right); free(root); }

2.通过前序遍历数组构建二叉树

#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include"Queue.h" typedef char data1; typedef struct BinaryTreeNode { data1 n; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; BTNode* BuyNode(char x) { BTNode* newnode = (BTNode*)malloc(sizeof(BTNode*)); if (newnode == NULL) { perror("malloc fail"); exit(1); } newnode->n = x; newnode->left = NULL; newnode->right = NULL; return newnode; } //返回二叉树的根节点,这不是真正意义上创建的二叉树,而是测试用例 BTNode* CreatBinaryTree() { BTNode* nodeA = BuyNode('A'); BTNode* nodeB = BuyNode('B'); BTNode* nodeC = BuyNode('C'); BTNode* nodeD = BuyNode('D'); BTNode* nodeE = BuyNode('E'); BTNode* nodeF = BuyNode('F'); BTNode* nodeG = BuyNode('G'); BTNode* nodeH = BuyNode('H'); nodeA->left = nodeB; nodeB->left = nodeD; nodeB->right = nodeE; nodeE->right = nodeH; nodeA->right = nodeC; nodeC->left = nodeF; nodeC->right = nodeG; return nodeA; } //通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* TreeCreate(data1* a, int* pi) { if (a[*pi] == '#') { (*pi)++; return NULL; } BTNode* root1 = (BTNode*)malloc(sizeof(BTNode)); if (root1 == NULL) { exit(1); } root1->n = a[(*pi)++]; root1->left = TreeCreate(a, pi); root1->right = TreeCreate(a, pi); return root1; } int main() { //构建二叉树 data1 arr[20] = { 'A', 'B', 'D', '#', '#', 'E', '#', 'H', '#', '#', 'C', 'F', '#', '#', 'G', '#', '#' }; int i = 0; BTNode* root1 = TreeCreate(arr, &i); //销毁二叉树 TreeDestory(root1); root1 = NULL; return 0; }

3.判断二叉树是否是完全二叉树

前置声明:

说明:若要判断是否是完全二叉树,则需要用到层序遍历;用到层序遍历,则需要用到队列;所以说这里比较复杂,我将把队列的相关代码粘贴到这里,若要详细理解队列的知识,则可以看我之前写的关于队列的知识点。



思路:逐层遍历,出来一个结点,将它的孩子带入队列,直到出来是空结点,这时跳出循环,遍历队列,若队列中存在非空结点,则说明不是完全二叉树。

代码实现:

Queue.h
#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> //链式结构:表示队列 //相当于前置声明,队列中存放的是二叉树中结点指针的地址 typedef struct BinaryTreeNode* data; typedef struct QListNode { data a; struct QListNode* next; }QNode;//队列中的结点 //由于队列是先进先出,所以插入的时候都需要找尾结点,不如直接定义两个指针,一个指向头结点一个指向尾结点, //但是传递参数需要传递两个指针,不如将两个指针定义在一个结构体中,只用传递结构体即可 //队列的结构 typedef struct Queue { QNode* head; QNode* ptial; int size;//累计队列中有几个结点 }Queue; //初始化队列 void QueueInit(Queue* q);//两个指针都指向头结点 //void QueueInit(QNode* head, QNode* pital);//创建头结点可以传递一级指针,没有头结点要传递二级指针 //队尾入队列 void QueuePush(Queue* q, data x); //对头出队列 void QueuePop(Queue* q); //获取队列头部元素 data QueueFront(Queue* q); //获取队列尾部元素 data QueueBack(Queue* q); //判断队列是否为空 bool QueueEmpty(Queue* q); //销毁队列 void QueueDestory(Queue* q); //队列中的元素个数 int QueueSize(Queue* q);
Queue.c
#include"Queue.h" //初始化队列 void QueueInit(Queue* q) { q->head = NULL; q->ptial = NULL; q->size = 0; } //队尾入队列 void QueuePush(Queue* q, data x) { assert(q); //创建新结点 QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("newnode fail"); exit(1); } newnode->next = NULL; newnode->a = x; if (q->ptial == NULL) { q->ptial = newnode; q->head = newnode; } else q->ptial->next = newnode; q->ptial = newnode; q->size++; } //对头出队列 void QueuePop(Queue* q) { //判断队列是否为空 assert(q); assert(q->head != NULL); ////存放第二个结点 //QNode* next = q->head->next->next; //free(q->head->next); //q->head->next = next; //q->size--; //没有将指针ptial置为空,成为野指针了 //一个结点 if (q->head->next == NULL) { free(q->head); q->head = q->ptial = NULL; } else { QNode* next = q->head->next; free(q->head); q->head = next; } q->size--; } //获取队列头部元素 data QueueFront(Queue* q) { assert(q); assert(q->head != NULL); return q->head->a; } //获取队列尾部元素 data QueueBack(Queue* q) { assert(q); assert(q->head != NULL); return q->ptial->a; } //判断队列是否为空 bool QueueEmpty(Queue* q) { assert(q); return q->size == 0; } //销毁队列 void QueueDestory(Queue* q) { assert(q); QNode* cur = q->head; while (cur) { QNode* next = cur->next; free(cur); cur = next; } q->head = q->ptial = NULL; q->size = 0; //QNode* cur = q->head; //while (cur) //{ // QNode* next = cur->next; // free(cur); // cur = next; //} //q->head = q->ptial = NULL; //q->size = 0; } //队列中的元素个数 int QueueSize(Queue* q) { assert(q); return q->size; }
Test.c
#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include"Queue.h" typedef char data1; typedef struct BinaryTreeNode { data1 n; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; BTNode* BuyNode(char x) { BTNode* newnode = (BTNode*)malloc(sizeof(BTNode*)); if (newnode == NULL) { perror("malloc fail"); exit(1); } newnode->n = x; newnode->left = NULL; newnode->right = NULL; return newnode; } //返回二叉树的根节点,这不是真正意义上创建的二叉树,而是测试用例 BTNode* CreatBinaryTree() { BTNode* nodeA = BuyNode('A'); BTNode* nodeB = BuyNode('B'); BTNode* nodeC = BuyNode('C'); BTNode* nodeD = BuyNode('D'); BTNode* nodeE = BuyNode('E'); BTNode* nodeF = BuyNode('F'); BTNode* nodeG = BuyNode('G'); BTNode* nodeH = BuyNode('H'); nodeA->left = nodeB; nodeB->left = nodeD; nodeB->right = nodeE; nodeE->right = nodeH; nodeA->right = nodeC; nodeC->left = nodeF; nodeC->right = nodeG; return nodeA; } //判断二叉树是否是完全二叉树 bool TreeComplete(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* node = QueueFront(&q); QueuePop(&q); if (node) { QueueDestory(&q); return false; } } QueueDestory(&q); return true; } int main() { //构建二叉树 data1 arr[20] = { 'A', 'B', 'D', '#', '#', 'E', '#', 'H', '#', '#', 'C', 'F', '#', '#', 'G', '#', '#' }; int i = 0; BTNode* root1 = TreeCreate(arr, &i); //判断是否是完全二叉树 if (TreeComplete(root1)) { printf("该树为完全二叉树\n"); } else printf("该树不为完全二叉树\n"); //销毁二叉树 TreeDestory(root1); root1 = NULL; return 0; }

四.总结

本文从二叉树链式结构实现、核心属性计算、树的创建销毁及完全二叉树判断四个维度,系统梳理了二叉树的核心操作与实现思路,为读者构建了清晰的知识框架。递归遍历是统计结点数、叶子数、树高等属性的核心方法,而层序遍历结合队列则是判断完全二叉树的关键,掌握这些逻辑能有效提升对二叉树的理解与应用能力。后续可结合中序、后序等遍历方式,以及线索二叉树、平衡二叉树等进阶结构,进一步深化二叉树相关知识的实践与拓展,为算法学习打下坚实基础。

     通过本节课的学习相信你一定有所收获,如果对你有帮助,欢迎 点赞、收藏、关注,后续持续更新数据结构与算法!

下一篇我们讲解二叉树的遍历,超详细!

Read more

Flutter 组件 spry 适配鸿蒙 HarmonyOS 实战:轻量化 Web 框架,构建高性能端侧微服务与 Middleware 治理架构

Flutter 组件 spry 适配鸿蒙 HarmonyOS 实战:轻量化 Web 框架,构建高性能端侧微服务与 Middleware 治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 spry 适配鸿蒙 HarmonyOS 实战:轻量化 Web 框架,构建高性能端侧微服务与 Middleware 治理架构 前言 在鸿蒙(OpenHarmony)生态迈向全场景分布式协同、涉及设备端侧 API 暴露、轻量化资源服务镜像及严苛的跨端 RPC 通信背景下,如何实现一套既能保持极低内存足迹(Footprint)、又能提供类似后端(Node.js/Koa)般丝滑开发体验且具备全异步处理能力的“端侧 Web 基座”,已成为决定应用分布式自治能力与全栈同构效率的关键。在鸿蒙设备这类强调 AOT 极致效能与背景任务严格限制的环境下,如果应用依然采用重量级的 HTTP 服务端,由于由于进程级的上下文切换开销,极易由于由于“算力溢出”导致鸿蒙应用在作为服务端响应时发生明显的电量损耗。 我们需要一种能够解耦路由逻辑、支持

By Ne0inhk
前端八股文面经大全:字节前端一面(2026-2-1)·面经深度解析

前端八股文面经大全:字节前端一面(2026-2-1)·面经深度解析

前言 大家好,我是木斯佳。 在这个春节假期,当大家都在谈论返乡、团圆与休息时,作为一名技术人,我的思考却不由自主地转向了行业的「冬」与「春」。 相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的“增删改查”岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。 正值春节,也是复盘与规划的好时机。结合ZEEKLOG这次「春节代码贺新年」活动所提倡的“用技术视角记录春节、复盘成长”,我决定在这个假期持续更新专栏,帮助年后参加春招的同学。 这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。 我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。 温馨提示:市面上的面经鱼龙混杂,

By Ne0inhk

3D效果:HTML5 WebGL结合AI实现智能3D场景渲染

3D效果:HTML5 WebGL结合AI实现智能3D场景渲染 📝 本章学习目标:本章聚焦高级主题,帮助读者掌握工程化开发能力。通过本章学习,你将全面掌握"3D效果:HTML5 WebGL结合AI实现智能3D场景渲染"这一核心主题。 一、引言:为什么这个话题如此重要 在前端技术快速发展的今天,3D效果:HTML5 WebGL结合AI实现智能3D场景渲染已经成为每个前端开发者必须掌握的核心技能。HTML5作为现代Web开发的基石,与AI技术的深度融合正在重新定义前端开发的边界和可能性。 1.1 背景与意义 💡 核心认知:HTML5与AI的结合,让前端开发从"静态展示"进化为"智能交互"。这种变革不仅提升了用户体验,更开辟了前端开发的新范式。 从2020年TensorFlow.js的成熟,到如今AI辅助开发工具的普及,前端开发正在经历一场智能化革命。据统计,超过70%的前端项目已经开始尝试集成AI能力,AI辅助前端开发工具的市场规模已突破十亿美元。 1.2 本章结构概览 为了帮助读者系统性地掌握本章内容,

By Ne0inhk

lectrue10 排序和聚合算法

查询计划:到目前为止,我们主要讨论的是访问方法(即如何通过索引或扫描找到数据)。现在,我们需要讨论如何真正执行这些查询。         数据库系统会将SQL语句编译成查询计划,查询计划是一个由算子组成的树状结构。我们将在后续的查询执行课程中详细讲解这部分内容。         对于面向磁盘的数据库系统,我们将利用缓冲池来实现那些在处理过程中需要溢出到磁盘的算法,我们的核心目标是尽可能减少算法的I/O次数。         排序:由于在关系模型中,表中的元组没有特定的顺序,因此 DBMS 需要对数据进行排序。排序(潜在地)被用于 ORDER BY、GROUP BY、JOIN 和 DISTINCT 算子。如果待排序的数据能全部装入内存,DBMS 可以使用标准排序算法(如快速排序)。如果数据量太大无法装入内存,则需要使用外部排序(如归并排序),该算法能够根据需要将数据溢出到磁盘,并且相比随机 I/O,它更倾向于使用顺序 I/O。         如果查询包含带有LIMIT的ORDER BY,DBMS 只需扫描一次数据即可找到前 N 个元素。

By Ne0inhk