二叉树:从基础结构到高级遍历技术

在这里插入图片描述
在这里插入图片描述

.

个人主页:晓风飞
专栏:数据结构|Linux|C语言
路漫漫其修远兮,吾将上下而求索


文章目录

  • 引言
  • 结构定义
  • 接口需求
  • 构建二叉树
  • 销毁二叉树
  • 计算节点和叶子的数量
    • 二叉树节点个数
    • 二叉树叶子节点个数
    • 二叉树第k层节点个数
  • 二叉树查找值为x的节点
  • 二叉树的遍历
    • 二叉树前序遍历
    • 二叉树中序遍历
    • 二叉树后序遍历
    • 二叉树层序遍历
  • 深度优先遍历(DFS)
  • 广度优先遍历(BFS)
  • 判断二叉树是否是完全二叉树
  • 实际应用
  • 完整代码
  • 结论


引言

在数据结构中,二叉树因其在搜索、排序和平衡等操作中的高效性而显得尤为重要。在这篇博客文章中,我们将深入探讨二叉树的概念,探索它们在C语言中的实现,并了解它们的实际应用。

结构定义

在C语言中,可以使用结构来表示二叉树,定义数据以及指向左右子节点的指针:

typedef char BTDataType;typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;

接口需求

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

构建二叉树

//通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{if (a[(*pi)] == '#'  || *pi > n)  //判断是否遍历完成或者遇到#,都返回NULL{(*pi)++;return NULL;}BTNode* root = (BTNode*)malloc(sizeof(BTNode)); // 创建新节点root->_data = a[*pi]; // 设置节点数据(*pi)++;root->_left = BinaryTreeCreate(a, n, pi); // 递归构建左子树root->_right = BinaryTreeCreate(a, n, pi); // 递归构建右子树return root;
}

销毁二叉树

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

计算节点和叶子的数量

二叉树节点个数

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

二叉树叶子节点个数

叶节点:位于树底部的节点,没有子节点,就像树上的叶子。
在这里插入图片描述

// 二叉树叶子节点个数
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层节点个数

在这里插入图片描述

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL || k < 1)return 0;if (k == 1)return 1;return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}

二叉树查找值为x的节点

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{// 如果当前节点为空,返回 NULL。这意味着我们已经到达树的末端或者树本身为空。if (root == NULL)return NULL;// 如果当前节点的值等于要查找的值 x,返回当前节点的指针。if (root->_data == x)printf("找到了它是:%c",root->_data);elseprintf("没有找到!", root->_data);return root;// 在左子树中递归查找值为 x 的节点。BTNode* leftResult = BinaryTreeFind(root->_left, x);// 如果在左子树中找到了这个值,返回该节点的指针。if (leftResult != NULL)return leftResult;// 如果左子树中没有找到,继续在右子树中递归查找。return BinaryTreeFind(root->_right, x);
}

二叉树的遍历

遍历是二叉树中的一个关键操作,允许我们按特定顺序访问每个节点:
在这里插入图片描述

前序遍历:遍历顺序是根节点,左子树,右子树。
中序遍历:遍历顺序是左子树,根节点,右子树。
后序遍历:遍历顺序是左子树,右子树,根节点。

二叉树前序遍历

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{if (root == NULL)return NULL;printf("%c ", root->_data);BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}

前序遍历首先访问根节点,然后递归地遍历左子树和右子树。这种遍历方法首先处理当前节点,然后处理子节点。

二叉树中序遍历

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL)return NULL;BinaryTreeInOrder(root->_left);printf("%c ", root->_data);BinaryTreeInOrder(root->_right);
}

中序遍历首先递归地遍历左子树,然后访问根节点,最后遍历右子树。这对于二叉搜索树来说,可以按顺序访问所有节点。

二叉树后序遍历

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{if (root == NULL)return NULL;BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);printf("%c ", root->_data);
}

后序遍历先递归地遍历左子树和右子树,最后访问根节点。这种遍历方法通常用于先处理子节点,然后再处理父节点的场景,如在二叉树中释放或删除节点。

二叉树层序遍历

// 二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root) 
{if (root == NULL) return; // 如果树为空,则直接返回Queue q;QInit(&q); // 初始化队列QPush(&q, root); // 将根节点入队while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q); // 取得队首元素QPop(&q); // 将队首元素出队// 处理当前节点,例如打印节点的值printf("%c ", front->_data);if (front->_left != NULL) {QPush(&q, front->_left); // 将左子节点入队}if (front->_right != NULL) {QPush(&q, front->_right); // 将右子节点入队}}QDestroy(&q); // 销毁队列,释放资源
}

层序遍历使用队列来迭代地访问每一层的所有节点,从根节点开始,依次遍历每一层的左右子节点。这种遍历方法可以确保按照树的层次顺序访问每个节点,从上到下,从左到右。

在这里插入图片描述

深度优先遍历(DFS)

遍历方式:DFS 从根节点开始,沿着树的深度向下遍历,直到没有子节点可以访问,然
后回溯到上一个节点,继续遍历未探索的分支。这个过程一直重复,直到访问了所有可达的节点。

使用的数据结构:通常使用栈(Stack)来实现,这可以是递归实现的隐式栈(函数调用栈)或显式的数据结构栈。

特点:DFS 能够深入到树的底部,但可能会在树的某一分支上“走得太远”,尤其是在处理具有大量节点和深层次结构的树时。

应用场景:DFS 更适用于目标路径较深或树的结构不规则的情况。在图论中,DFS 常用于拓扑排序和检查图中的环。

以下是遍历顺序:
在这里插入图片描述

广度优先遍历(BFS)

遍历方式:BFS 从根节点开始,先遍历根节点的所有邻接节点,然后再遍历这些邻接节点的邻接节点,以此类推。BFS 逐层遍历树的节点,从根节点开始向外扩散。

使用的数据结构:通常使用队列(Queue)来实现。遍历过程中,先进入队列的节点先遍历,确保了节点是按照层级顺序访问的。

特点:BFS 能够快速访问到从根节点开始的每一层节点,但在宽度较大的树或图中,可能会消耗更多的内存。

应用场景:BFS 更适用于目标节点离根节点比较近或需要按层次遍历的情况。在图论中,BFS 常用于寻找最短路径。

总结来说,DFS 优先沿着树的深度遍历,直到遇到叶子节点,然后回溯并探索其他分支,而 BFS 优先遍历距离根节点最近的节点,逐层向外扩展。两者在解决不同类型的问题时各有优势。DFS 在树的深度较大时更有效,而 BFS 在寻找最短路径或树的宽度较大时更有优势。

以下是遍历顺序
在这里插入图片描述


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

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root) {if (root == NULL) {return 1; // 空树认为是完全二叉树}Queue q;QInit(&q);QPush(&q, root);int level = 0; // 记录层级bool mustBeLeaf = false; // 标记是否必须为叶子节点while (!QueueEmpty(&q)) {int size = QueueSize(&q); // 当前层的节点数level++;for (int i = 0; i < size; i++) {BTNode* front = QueueFront(&q);QPop(&q);// 如果标记为必须是叶子节点,但当前节点不是叶子节点,则不是完全二叉树if (mustBeLeaf && (front->_left != NULL || front->_right != NULL)) {return 0;}// 如果当前节点有右孩子但没有左孩子,则不是完全二叉树if (front->_left == NULL && front->_right != NULL) {return 0;}if (front->_left != NULL) {QPush(&q, front->_left);}else {// 如果左孩子为空,则后面的节点都必须是叶子节点mustBeLeaf = true;}if (front->_right != NULL) {QPush(&q, front->_right);}else {// 如果右孩子为空,则后面的节点都必须是叶子节点mustBeLeaf = true;}}}QDestroy(&q);return 1; // 遍历结束没有发现违反完全二叉树的规则,返回1
}

这个函数用来判断一个二叉树是否是完全二叉树。它通过层序遍历来实现,检查每个节点是否符合完全二叉树的性质:如果某个节点的右子节点存在,而左子节点不存在,则不是完全二叉树;如果某个节点的左子节点或右子节点不存在,则该节点后的所有节点必须是叶子节点。

实际应用

二叉树不仅仅是理论构造,它们在现实世界中有着广泛的应用:

数据库系统:在索引中使用,实现高效的数据检索。
文件系统:用于组织文件的层级结构。
网络路由算法:帮助优化数据包的传输路径。

完整代码

可以来我的github参观参观,看完整代码
路径点击这里–>数据结构二叉树的实现

结论

通过深入了解和实现二叉树,我们不仅能够提高我们的编程技能,还能更好地理解这些结构在现代计算中的重要性。无论是在学术研究还是实际应用中,二叉树都是一个值得掌握的关键概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/625811.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

立白科技集团:研发安全推动数字化蜕变,日化业务再上新高度

立白科技集团成立于1994年&#xff0c;是我国日化行业的领军企业&#xff0c;致力于成为一家“品牌引领、数字经营、富有创新、富有活力”的智慧服务型企业。从2018年开始&#xff0c;立白科技集团加速数字化转型&#xff0c;打造数据和业务中台&#xff0c;并建立toB和toC平台…

修改和调试 onnx 模型

1. onnx 底层实现原理 1.1 onnx 的存储格式 ONNX 在底层是用 Protobuf 定义的。Protobuf&#xff0c;全称 Protocol Buffer&#xff0c;是 Google 提出的一套表示和序列化数据的机制。使用 Protobuf 时&#xff0c;用户需要先写一份数据定义文件&#xff0c;再根据这份定义文…

【C语言】指针知识点笔记(2)

目录 一、野指针 二、assert断言 三、指针的使用和传址调用 四、数组名的理解 五、使用指针访问数组 一、野指针 二、assert断言 三、指针的使用和传址调用 四、数组名的理解 五、使用指针访问数组

Vue响应式系统(二)

Vue响应式系统(一) 六、嵌套的effect与effect栈。 什么场景会用到effect嵌套呢&#xff1f;听我娓娓道来。 就用Vue.js来说吧&#xff0c;Vue.js的渲染函数就是在effect中执行的&#xff1a; /*Foo组件*/ const Foo {render() {return /*.....*/} }// effect中执行Foo组件中…

kubectl与 jq的另外一些用法

背景&#xff1a; 在日常运维工作中&#xff0c;我们需要管理和操作大量的配置文件&#xff0c;这在使用 Kubernetes 集群管理应用时尤为常见。Kubernetes 提供了一个名为 ConfigMap 的资源对象&#xff0c;它用于存储应用的配置信息。有时&#xff0c;我们需要查找哪些 Confi…

基于SSM的驾校信息管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue、HTML 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是…

人机协同中存在一个独特的时空体系

一、在人机协同中存在一个独特的时空体系 在人机这个独特的时空体系中&#xff0c;人和机器之间的时间和空间的交织和共同作用。 在时间维度上&#xff0c;人机协同体系中的人和机器具有不同的时间节奏和速度。人类有限的生命周期和有时候需要休息的需求使得他们的工作时间和生…

机器人制作开源方案 | 智能循迹避障小车

作者&#xff1a;刘元青、邹海峰、付志伟、秦怀远、牛文进 单位&#xff1a;哈尔滨信息工程学院 指导老师&#xff1a;姚清元 智能小车是移动式机器人的重要组成部分&#xff0c;而移动机器人不仅能够在经济、国防、教育、文化和生活中起到越来越大的作用&#xff0c;也是研究…

Codeforces Round 114 (Div. 1) C. Wizards and Numbers(思维题 辗转相除+博弈 巴什博弈)

题目 t(t<1e4)组询问&#xff0c;每次询问(a,b)&#xff08;0<a,b<1e18&#xff09;&#xff0c; 不妨a<b&#xff08;a>b时需要交换两个数考虑&#xff09; ①令b减去a的k次方&#xff08;k>1&#xff09;&#xff0c;要求减完之后b非负 ②令bb%a 当a和…

java处理16进制字符串的一些方法和基础知识

前言&#xff1a;本篇文章是对于基础数据的处理的一些简单经验总结里边包含了一些基础的数据储存和数据转化的一些知识&#xff0c;同样也包含有部分快捷的数据处理方法。主要用于个人知识的一个记录和方便进行对应的数据转换和处理。 1、bit,字节和字的关系 1.1 bit和字节的…

2024全新开发API接口调用管理系统网站源码 附教程

2024全新开发API接口调用管理系统网站源码 附教程 用layui框架写的 个人感觉很简洁 方便使用和二次开发

腾讯云MPS为出海媒体企业助力

在如今互联网发达的时代&#xff0c;一个视频通过网络发布即可供给全球用户进行观看。其中视频媒体企业便其中的领头先锋&#xff0c;为了让创作者们以及全球各大用户的视频进行快速推广&#xff0c;出海则是不二之选。但是因为各地区域的不同&#xff0c;带宽的不同与网络的限…

深信服技术认证“SCSA-S”划重点:逻辑漏洞

为帮助大家更加系统化地学习网络安全知识&#xff0c;以及更高效地通过深信服安全服务认证工程师考核&#xff0c;深信服特别推出“SCSA-S认证备考秘笈”共十期内容&#xff0c;“考试重点”内容框架&#xff0c;帮助大家快速get重点知识~ 划重点来啦 *点击图片放大展示 深信服…

桌面显示器type-c接口方案

在当今时代&#xff0c;TYPE-C接口桌面显示器已经成为了我们生活和工作中不可或缺的重要设备之一。与传统显示器相比&#xff0c;新型的TYPE-C接口桌面显示器具有更多的功能和优势&#xff0c;其中最显著的特点就是支持视频传输和充电功能。 首先&#xff0c;TYPE-C接口桌面显示…

[ACM 学习] 最长上升子序列

LIS&#xff08;最长上升子序列&#xff09;的三种经典求法 - 一只不咕鸟 - 博客园 (cnblogs.com) 理解一下第三种方法&#xff08;贪心二分查找&#xff09; 因为构建的是上升子序列&#xff0c;所以是可以用二分查找找到最大的小于当前 A[i] 的在子序列中的 F[j]&#xff0…

关于steam游戏搬砖,想给大家的几点忠告

关于CSGO游戏搬砖&#xff0c;想给大家的几点忠告&#xff1a; 1、新出的箱子&#xff0c;里面开出的皮肤短时间内会溢价&#xff0c;价格虚高&#xff0c;后期会呈逐渐下跌趋势&#xff0c;这就是我们不让大家碰新品的原因&#xff0c;哪怕利润再高也不建议购入或者囤货&…

Linux Mii management/mdio子系统分析之五 PHY状态机分析及其与net_device的关联

&#xff08;转载&#xff09;原文链接&#xff1a;https://blog.csdn.net/u014044624/article/details/123303714 前面几章基本上完成了mdio模块驱动模型的分析&#xff0c;本篇文章主要讲述phy device的状态机以及phy device与net_device的关联。Phy device主要是对phy的抽象…

C语言:自定义类型——结构体

一、什么叫做结构体 C语⾔已经提供了内置类型&#xff0c;如&#xff1a;char、short、int、long、float、double等&#xff0c;但是只有这些内置类型还是不够的&#xff0c;假设我想描述学⽣&#xff0c;描述⼀本书&#xff0c;这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要 …

SMD NTC Thermistor NTC热敏电阻产品基本参数定义

热敏电阻器&#xff08;Thermistor&#xff09;是一种电阻值对温度极为灵敏的半导体元件&#xff0c;温度系数可分为Positive Temperature Coefficient 正温度系数热敏电阻又称PTC热敏电阻和Negative Temperature Coefficient 负温度系数热敏电阻又称NTC热敏电阻. NTC热敏电…

Seata分布式事务

文章目录 Seata分布式事务1.分布式事务问题1.1.本地事务1.2.分布式事务 2.理论基础2.1.CAP定理2.1.1.一致性2.1.2.可用性2.1.3.分区容错2.1.4.矛盾 2.2.BASE理论2.3.解决分布式事务的思路 3.初识Seata3.1.Seata的架构3.2.部署TC服务3.2.1.下载3.2.2.解压3.2.3.修改配置3.2.4.在…