小白的入门二叉树(C语言实现)

前言:

二叉树属于数据结构的一个重要组成部分,很多小白可能被其复杂的外表所吓退,但我要告诉你的是“世上无难事,只怕有心人”,我将认真的对待这篇博客,我相信只要大家敢于思考,肯定会有所收获的,当我们攀过一座山,回头看去,可能当初畏惧的大山也不过如此。

目录

前言:

一,树的基本知识

1树的概念

2,树相关概念

二,二叉树的基本知识

1,二叉树的概念

2,特殊的二叉树:

三,二叉树的思路及代码实现(边讲解边写代码)

1,二叉树实现方式思考

2,前提准备(头文件和二叉树基本结构)

3,如何创建二叉树

4,二叉树的前,中,后序遍历

5,层序遍历

6,二叉树的拓展——堆(堆排序,TOPk问题)

1,堆的概念

2,堆的代码实现思考

3,向下排序

4,向上排序

5,Topk问题

1,建堆

2,堆如何插入呢?

3,堆顶数据如何删除呢?


一,树的基本知识

1树的概念


树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根节点没有前驱结点
除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i 
<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继因此,树是递归定义的。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构

2,树相关概念

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;

二,二叉树的基本知识

1,二叉树的概念

一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

从上图可以看出:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的:

2,特殊的二叉树:


1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

三,二叉树的思路及代码实现(边讲解边写代码)

1,二叉树实现方式思考

想要用代码实现二叉树,我们首先要思考二叉树的结构特性,二叉树最多只有两个孩子,我们抓住这个关键特性,我们有两个实现方式:链表或者数组,我们只讲一种实现方式,链表实现二叉树,当然我讲完链表实现后,数组实现也不过是小菜一碟,现在正菜开始。

2,前提准备(头文件和二叉树基本结构)
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef char BTDataType;//BTDataType是char的别名,方便以后修改二叉树的数据类型typedef struct BinaryTreeNode
{BTDataType _data;              //二叉树的存储数据struct BinaryTreeNode* _left;  //左孩子struct BinaryTreeNode* _right; //右孩子
}BTNode;
3,如何创建二叉树

要求:给你一串字符,要求你根据字符前序遍历创建二叉树

字符:"ABD##E#H##CF##G##"

利用字符创建二叉树,一开始我们肯定是一头雾水,我们首先自己在纸上试试创建二叉树

不知道大家有没有发现规律,因为是前序遍历,因此是对根A进行赋值,然后是左孩子B,像C如果左孩子是NULL,就返回上一个根C对右孩子进行赋值,右孩子如果非空,就继续对右孩子的左孩子进行赋值,遇到空就返回对右孩子的右孩子赋值,直到全为空就返回到B。

那么这个思路我们该怎么实现呢,不知道大家有没有发现这个可以拆分为一个个类似的子问题,那么结果就呼之欲出了-----递归。接下来跟上代码继续讲解一遍思路


BTNode* TreeCreat(BTDataType* a, int* pi) {if (a[*pi] == '#') {	//如果为空就返回(*pi)++;			//即使为空也要继续下一个字母return NULL;		//提前结束这一层递归}BTNode* binary = (BTNode*)malloc(sizeof(BTNode));//分配空间binary->_data = a[*pi];			//将值放进data中(*pi)++;                        //读完,指向下一个字母binary->_left = TreeCreat(a, pi);	//左孩子进行赋值,直到遇空返回binary->_right  = TreeCreat(a, pi);//右孩子进行赋值,直到遇空返回return binary;    //返回创建的结点给上一层递归
}
BTNode* BinaryTreeCreate(BTDataType* a,int n, int* pi) {*pi = 0;					//用这个来记录读取的数据到哪里了return TreeCreat(a,pi);		//开始递归读值
}

4,二叉树的前,中,后序遍历

二叉树前,中,后遍历呢?大家先想想前序创建二叉树,有没有发现遍历只要按照创建时的思路就行了,那么话不多说·直接上前序代码

void BinaryTreePrevOrder(BTNode* root) {if (root == NULL) {   //老规矩,遇空返回printf("#");		return;}printf("%c", root->_data);		//按照前序顺序先打印根BinaryTreePrevOrder(root->_left); //开始进入左孩子,进入之后会打印左孩子,一直走到NULLBinaryTreePrevOrder(root->_right);//开始进入右孩子,进入之后会打印左孩子,一直走到NULL
}

中序代码

void BinaryTreeInOrder(BTNode* root) {if (root == NULL) {  //防止非法访问,直接判断是否为空printf("#");return;}BinaryTreeInOrder(root->_left);	//进入递归,先直到最左的叶结点printf("%c", root->_data);     //开始打印BinaryTreeInOrder(root->_right);//根和左孩子都遍历了开始进入右孩子并打印
}

相信聪明的你可以独自写出后序遍历了吧!!!

5,层序遍历

前中后遍历还有迹可寻,那层序遍历应该怎么入手呢?层序遍历的如果光按照树的特性是不太好入手的,但是我们想一下,假设打印根节点,再打印它的两个孩子,再打印根节点孩子的所以孩子,这个中间是不是有一个顺序关系,你有没有想到队列呢,这样的话是不是可以依次拿到数据呢?在里面还有一个重要的关系,就是一个父亲结点会有两个孩子,也就意味着入队列的速度是远大于出队列的速度的,想完这些我们就可以写代码了。想看队列的源代码你们可以看我的往期文章。

void BinaryTreeLevelOrder(BTNode* root) {Queue* q = (Queue*)malloc(sizeof(Queue));//创建队列QueueInit(q);							//初始化队列QueuePush(q, root);						//先将根入队列while (q->_front != q->_rear ) {        //这个时候队列头等于尾,意味着队列为空了if (q->_front->_data   != NULL) {	//入队列第一个非空元素的两个孩子QueuePush(q, q->_front->_data->_left);QueuePush(q, q->_front->_data->_right);printf("%c", q->_front->_data->_data);//打印队列第一个元素的休息}elseprintf("#");                          //为空也要打印QueuePop(q);							//出掉原先的第一个元素,第二个元素顶上}
}

6,二叉树的拓展——堆(堆排序,TOPk问题)

1,堆的概念

堆就是一个完全二叉树

大堆就是父亲节点一定大于孩子节点

小堆就是父亲节点小于孩子节点

2,堆的代码实现思考

堆是二叉树,我们同样可以用链表和数组实现,我们这里为了突出堆的特性和作用,我们这里用数组实现,在实现前我们有两个规则会帮助我们进行排序,大家先记住下标规律,规则的条件是,根节点在数组中的位置为1,孩子节点是按顺序存储的,先左孩子,后右孩子

左孩子下标=父节点下标/2+1

右孩子下标=父节点下标/2+2、

父节点下标=(孩子节点下标-1)/2      //因为int是没有小数的,1/2=0.所以左右节点没必要区分

3,向下排序

首先声明,堆排序不是绝对有序,而是父节点相比孩子节点是有序的,但兄弟节点和同一层节点不一定有序。

向下排序是如何排的呢?顾名思义,就是从根节点开始和孩子节点比较,如果大小不符合堆的特性,就交换位置,直到堆的叶结点,如果大小符号就直接结束,开始下一层排序,我们要利用父节点和孩子节点的位置才能进行比较和交换

接下来看一下小堆排序的图

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;int _size;  //堆的大小int _capacity;//堆的总容量
}Heap;
void Adjustdown(HPDataType* a, int n,int capcity) {int parent = n;			//参数传来排序的位置下标int child = parent * 2 + 1;			//找到孩子节点下标while (child < capcity) {          //防止数组越界if (child + 1 < capcity && a[parent] > a[child + 1] && a[child + 1] < a[child]) {swap(a + parent, a + child + 1);  //交换parent = child + 1;              //父子换为左孩子字节的下标,开始下一轮交换}else if (a[parent] > a[child]) {swap(a + parent, a + child);parent = child;					//父子换为右孩子字节的下标,开始下一轮交换} elsebreak;							//无需交换,直接结束child = parent * 2 + 1;             //更新孩子节点的位置下标}
}
4,向上排序

向上排序和向下排序相反,它是从子节点到父节点,到根节点就会强制终止,

void Adjustup(HPDataType* a, int n) {int parent = (n - 1) / 2;//通过参数找到父节点int child = n;			//找到孩子节点while (child > 0) {if (a[child] < a[parent])		swap(a + child, a + parent);elsebreak;			//如果不需要交换就停止,前提是堆是有序的child = parent;   //将子节点换位父节点,向上排序parent = (child - 1) / 2;		//父节点继续向上}
}

现在你有没有想为什么写向上排序和向下排序,其实向上排序和向下排序就像插入排序和冒泡排序的作用,只不过是用于堆的排序,但想要将一个毫无规律的堆排序是需要很多个向上排序和向下排序的,我们可以利用循坏来进行重复的排序。

5,Topk问题

堆排序的一个很重要的作用就是Topk问题,简而言之就是排行榜,相比与冒泡的o(n^2),堆排序的时间复杂度是nlogn。

1,建堆

首先要求Topk,我们需要一个容量为k的堆,这样我们才能进行排序,因此我们首先来建堆,并且将堆进行排序

void HeapCreate(Heap* hp, HPDataType* a, int n) {assert(hp);													//防止非法访问导致程序崩溃hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);		//开辟堆空间assert(hp->_a);												//判断是否开辟成功for (int i = 0; i < n; i++)hp->_a[i] = a[i];										//将数据全部输入堆,注意此时无序hp->_capacity = n;											//记录容量hp->_size = n;												//记录大小for(int i=(n-2)/2;i>=0;i--)									//找到第一个倒数的父亲节点,并且逐个向下排序直到根Adjustdown(hp->_a ,i,n);for (int i = 0; i < n; i++)printf("%d ", hp->_a[i]);								//打印方便观察
}
2,堆如何插入呢?

从数组的角度来看,我们可以先不管大小关系,直接将新数据放到末尾再来考虑顺序问题,将数据放到末尾之后排序就简单了,我们直接用一个向上排序就可以是堆再次有序了

void HeapPush(Heap* hp, HPDataType x) {if (hp->_capacity == hp->_size) {     //检查堆的剩余空间HPDataType* b;b= (HPDataType*)realloc(hp->_a ,sizeof(HPDataType) * (hp->_capacity + 3));  //一次开辟三个内存assert(b);						//判断是否开辟成功hp->_a = b;						//将开辟好的堆放入原位置hp->_capacity += 3;      //调整堆的容量记录}hp->_a[hp->_size] = x;					//放到数组末尾Adjustup(hp->_a, hp->_size);				//向上调整一次就有序了hp->_size++;
}
3,堆顶数据如何删除呢?

堆顶数据的删除看似是一个复杂的问题,我们要想清楚,如果直接把堆顶删除,然后之后的数据就依次往前推,但是会有一个问题,往前推一定是有序的吗?我们之前讲了,堆同层是不一定有序的,如果随意的往前覆盖,就会导致原先同层小的数据成为了父节点,此时会产生蝴蝶效应,我们无法判断有多少个无序了,因此我们要另辟蹊径,想一想如果我们把第一个时间和最后一个数据交换,然后把最后一个数据无视,那么是不是只要根节点的数据是无序的,那么此时我们只需要堆根进行一次向下排序不就有序了吗?

void HeapPop(Heap* hp) {assert(hp);   //防止非法访问assert(hp->_a);swap(hp->_a+0, hp->_a+hp->_size -1);//交换第一个和最后一个数据Adjustdown(hp->_a, 0, hp->_size-1);//直接进行一次向下排序完成hp->_size--;
}

博客耗时颇多,希望大家点赞加收藏,可以评论区交流

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

成都瀚网科技:抖音提供差异化​​亮点!

在抖音平台上&#xff0c;精选联盟是一个专门为优质品牌提供展示和推广机会的合作项目。对于斗店主来说&#xff0c;如何成功对接精选联盟并实现上市是一个重要目标。在这篇文章中&#xff0c;我们将分享一些豆点与精选联盟对接的方法&#xff0c;并提供上币指南。 1、提升店铺…

2023 蓝帽杯初赛web部分取证复现

前言&#xff1a;初赛进线下了&#xff0c;计划着在决赛前突击学习一下取证&#xff0c;但时间还是太紧 只看了很多内存取证和手机取证 计算机取证和服务器取证没掌握 ---( 不过复赛没考&#xff0c;也算狗运了) 目录 <1> web-LovePHP(file()函数侧信道攻击) <2&g…

基于微信小程序的美术馆预约平台设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

【LeetCode热题100】--53.最大子数组和

53.最大子数组和 使用动态规划&#xff1a; 状态定义&#xff1a;设动态规划列表dp&#xff0c;dp[i]代表以元素nums[i]为结尾的连续子数组最大和 转移方程&#xff1a;若dp[i-1]≤0,说明dp[i-1]对dp[i]产生负贡献&#xff0c;即dp[i-1]nums[i]还不如nums[i]本身大 初始状态&…

Redis GEO 类型与 API 结合,地理位置优化的绝佳实践

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

基于PHP语言研发的抖音矩阵系统源代码开发部署技术文档分享

一、概述 本技术文档旨在介绍抖音SEO矩阵系统源代码的开发部署流程&#xff0c;以便开发者能够高效地开发、测试和部署基于PHP语言的开源系统。通过本文档的指引&#xff0c;您将能够掌握抖音SEO矩阵系统的开发环境和部署方案&#xff0c;从而快速地构建出稳定、可靠的短视频S…

网络爬虫-----爬虫的分类及原理

目录 爬虫的分类 1.通用网络爬虫&#xff1a;搜索引擎的爬虫 2.聚焦网络爬虫&#xff1a;针对特定网页的爬虫 3.增量式网络爬虫 4.深层网络爬虫 通用爬虫与聚焦爬虫的原理 通用爬虫&#xff1a; 聚焦爬虫&#xff1a; 爬虫的分类 网络爬虫按照系统结构和实现技术&#…

竞赛选题 基于深度学习的植物识别算法 - cnn opencv python

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 MobileNetV2网络4 损失函数softmax 交叉熵4.1 softmax函数4.2 交叉熵损失函数 5 优化器SGD6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的植物识别算法 ** …

vue3硅谷甄选01 | 使用vite创建vue3项目及项目的配置 环境准备 ESLint配置 prettier配置 husky配置 项目集成

文章目录 使用vite创建vue3项目及项目的配置1.环境准备2.项目配置ESLint校验代码工具配置 - js代码检测工具1.安装ESLint到开发环境 devDependencies2.生成配置文件:.eslint.cjs**3.安装vue3环境代码校验插件**4. 修改.eslintrc.cjs配置文件5.生成ESLint忽略文件6.在package.js…

PIL或Pillow学习2

接着学习下Pillow常用方法&#xff1a; PIL_test1.py : 9, Pillow图像降噪处理由于成像设备、传输媒介等因素的影响&#xff0c;图像总会或多或少的存在一些不必要的干扰信息&#xff0c;我们将这些干扰信息统称为“噪声”&#xff0c; 比如数字图像中常见的“椒盐噪声”&…

Postman使用_接口导入导出

文章目录 Postman导入数据Collections导出数据Environments导出数据Postman导出所有数据 Postman导入数据 可以导入collections&#xff08;接口集&#xff09;、Environments&#xff08;环境配置&#xff09;通过分享的链接或导出的JSON文件导入数据&#xff08;还可以从第三…

Pixea Plus for Mac:极简图片浏览,高效图片管理

在处理和浏览图片时&#xff0c;我们往往需要一个得心应手的工具&#xff0c;尤其是当你的图片库包含了各种不同格式&#xff0c;例如JPEG、HEIC、psd、RAW、WEBP、PNG、GIF等等。今天&#xff0c;我们要推荐的&#xff0c;就是一款极简、高效的Mac图片浏览和管理工具——Pixea…

Crazy Excel:Excel中的泥石流

Crazy Excel又名&#xff1a;疯狂Excel。是一款PC端的Excel软件工具&#xff0c;该软件支持windows, mac os等主流操作系统。 正如其名&#xff0c;作者在设计之初就加入了一些疯狂的设计&#xff0c;目的是创作出更加好用有效的excel工具。 不管是专业还是小白&#xff0c;…

前后台分离开发 YAPI平台 前端工程化之Vue-cli

目录 YAPI介绍前端工程化之Vue-cli前端工程化简介前端工程化入门——Vue-cli环境准备Vue项目简介创建Vue项目vue项目目录结构介绍vue项目运行方法 Vue项目开发流程 前后台混合开发这种开发模式有如下缺点&#xff1a; 沟通成本高&#xff1a;后台人员发现前端有问题&#xff0…

【Redis】第5讲 Redis的下载并安装

下载Redis中文网https://www.redis.net.cn/ 百度网盘下载&#xff1a; 百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://p…

malloc与free

目录 前提须知&#xff1a; malloc&#xff1a; 大意&#xff1a; 头文件&#xff1a; 申请空间&#xff1a; 判断是否申请成功&#xff1a; 使用空间&#xff1a; 结果&#xff1a; 整体代码&#xff1a; malloc申请的空间怎么回收呢? 注意事项&#xff1a; free:…

VirtualBox Win7 虚拟机 共享文件夹设置

系统配置 VirtualBox虚拟机版本&#xff1a;6.1.46 主机Host&#xff1a;Win11 虚拟机&#xff1a;Win7-32位 添加虚拟光驱 为虚拟机添加虚拟光驱&#xff0c;光驱中导入VBoxGuestAdditions.iso文件。 该文件默认路径为&#xff1a; X:\Program Files\Oracle\VirtualBox\V…

Nmap安装和使用详解

Nmap安装和使用详解 Nmap概述功能概述运行方式 Nmap安装官方文档参考&#xff1a;Nmap参数详解目标说明主机发现端口扫描Nmap将目标主机端口分成6种状态&#xff1a;Nmap产生结果是基于机器的响应报文&#xff0c;而这些主机可能是不可信任的&#xff0c;会产生一些迷惑或者误导…

使用vue-cli搭建SPA项目及使用和路由及路由嵌套的使用

目录 一、介绍 ( 1 ) 概述 ( 2 ) 作用 二、项目搭建 SPA介绍 讲述 特点 优点 ( 1 ) 检查 ( 2 ) 安装 ( 3 ) 构建 ( 4 ) 启动 ( 5 ) 导入 三、路由及嵌套使用 ( 1 ) 路由 ( 2 ) 嵌套 给我们的收获 一、介绍 ( 1 ) 概述 vue-cli是一个基于Vue.js的脚…

uni-app 实现自定义按 A~Z 排序的通讯录(字母索引导航)

创建 convertPinyin.js 文件 convertPinyin.js 将下面的内容复制粘贴到其中 const pinyin (function() {let Pinyin function(ops) {this.initialize(ops);},options {checkPolyphone: false,charcase: "default"};Pinyin.fn Pinyin.prototype {init: functi…