数据结构之二叉树的精讲

news2024/9/20 14:56:51

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary_walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

👑💎💎👑💎💎👑 
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑    希望在看完我的此篇博客后可以对你有帮助哟

👑👑💎💎💎👑👑   此外,希望各位大佬们在看完后,可以互相支持,蟹蟹!
👑👑👑💎👑👑👑


对二叉树的基本概念性的理解,若有不明白的友友们,可以看一下前期写的关于堆与二叉树的精讲

链接在此:

有了大家对二叉树的基本理解接下来,对以下知识的理解应该是易如反掌!

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

 对于任何一个二叉树的节点来说:都是由值域,左孩子,右孩子构成,只不过对于某些节点而言左右孩子为空

1.1定义一个二叉树的结构体
typedef int BT_DataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left; //左孩子
	struct BinaryTreeNode* right;//右孩子
	BT_DataType val;  //值域

}BT;
1.2二叉树的链式结构

 为了方便对二叉树的理解,我暂时手动创建节点,进行连接

BT* BT_Create()
{
	BT* n1 = BuyNode( 1);
	BT* n2 = BuyNode( 2);
	BT* n3 = BuyNode( 3);
	BT* n4 = BuyNode( 4);
	BT* n5 = BuyNode(5);
	BT* n6 = BuyNode( 6);
	BT* n7 = BuyNode( 7);
	BT* n8= BuyNode( 8);
	BT* n9 = BuyNode( 9);
	BT* n10 = BuyNode( 10);


	n1->left = n2;
	n1->right = n3;
	n2->right = n4;
	n3->left = n5;
	n3->right = n6;
	n2->left = n7;
	n4->left = n8;
	return n1;
}
2.二叉树的遍历
2.1前序遍历(先序遍历)

先访问根节点,其次访问左子树,左后访问右子树,注意这是一个递归的定义。

见图如下:

 代码:
void Pre_order(BT* root)
{
	if (root == NULL)
	{
		printf("%c ", 'n');//返回n表示为空
		return;
	}
	printf("%d ", root->val);
	Pre_order(root->left);
	Pre_order(root->right);
}
递归展开图的解释:

2.2中序遍历

先访问左子树,在访问根节点最后访问右子树,依然是一个递归的定义

分析如下:
 代码:
void In_Order(BT* root)
{
	if (root == NULL)
	{
		printf("%c ", 'n');//返回n表示为空
		return;
	}
	In_Order(root->left);
	printf("%d ", root->val);
	In_Order(root->right);
}
2.3后续遍历

先访问左子树,再访问右子树,最后访问根节点,依然是递归定义

分析见下:

代码:
void Post_Order(BT* root)
{
	if (root == NULL)
	{
		printf("%c ", 'n');//返回n表示为空
		return;
	}
	Post_Order(root->left);
	Post_Order(root->right);
	printf("%d ", root->val);

}
3.与二叉树相关节点的求解
3.1求二叉树所有节点个数

可能有些老铁们会说:直接进行计数就可以了:只有是当前节点不为空就让计数器(size)加一,采用前序遍历的方法。没错也可以这样但是这样有些坑需要注意一下否则一不小心就掉进坑里了。

 

 这时候可能有老铁会说,直接定义一个全局变量不就OK了,注意当我们连续调用BT_Size()这个函数进行求个数的话会有问题滴!

 因为szie作为一个全局变量,第一调用确实为8没有错,但是当我们后续在进行调用的时候就需要把size 手动进行置零,(关于这个问题详解,感兴趣的友友们,可以看前期我写的博客:链接在此,自取,蟹蟹支持!)要不然只会在当前size = 8 的基础上进行相加,这也就是为什么最后会出现16,24的这样结果了,也就不以为奇了。

代码:
int size = 0;
int BT_Size(BT* root)
{
	//int size = 0;
	if (root == NULL)
		return size;
	size++;
	BT_Size(root->left);//对左子树进行个数的遍历
	BT_Size(root->right);//对右子树进行个数的遍历
	return size;
}

 运行结果:

3.2求二叉树叶节点个数

既然是让咱求叶节点个数,咱就需要知道什么是叶节点:度为0的节点(没有左孩子,也没有右孩子的节点)

通过前面对二叉树的遍历咱们应该渐渐对递归要有些体悟了,当一个问题很大的时候,可以化大为小,化繁为简。这样岂不是很爽!

 分析见下:
 代码:
int BT_Leaf_Size(BT* root)
{
	if (root == NULL)
		return 0;
	if (root->left == NULL && root->right == NULL) // 判断是否为叶节点
		return 1;
	return  BT_Leaf_Size(root->left) + BT_Leaf_Size(root->right);
}
3.3求二叉树第H层节点个数

假设根节点所在层次就是第一层,依次下推,第H层的每个节点必然是第(H-1)层节点的左右孩子,这不就解决问题了嘛:求二叉树第H层节点个数转化为求二叉树第H-1层每个节点的左右孩子之和不就OK了。

 分析见下:

 代码:
int BT_LevelH_Size(BT* root, int h)
{
	if (root == NULL)
		return 0;
	if (h == 1)
		return 1;
return BT_LevelH_Size(root->left, h - 1)
	+ BT_LevelH_Size(root->right, h - 1) ;
}
4.二叉树高度的求解

对于一个二叉树而言,树的高度是左子树与右子树相比高度最大的一个再加1

还是如此,借助递归思想

子问题:左子树与右子树相比高度最大的一个再加1

结束条件:判断当前节点是否为空,其次当前节点是否为叶节点

 分析见下:

代码:
int BT_Height(BT* root)// 求树高度
{
	if (root == NULL)
		return 0;
	if (root->left == NULL && root->right == NULL)
		return 1;
	int left_h = BT_Height(root->left);
	int right_h = BT_Height(root->right);
	return left_h > right_h ? left_h + 1 : right_h + 1;
}
5.二叉树的销毁

注意这是链式二叉树,不能直接进行删除,更为直接的是采用后续遍历来进行销毁

代码:

void BT_Destroy(BT* root)
{
	if (root == NULL)
		return;
	BT_Destroy(root->left);
	BT_Destroy(root->right);
	free(root);
}
6.二叉树的层次遍历

 对于二叉树的层次遍历很显然就是一层一层进行遍历,在这里借助队的性质先进先出,来实现对二叉树的层次遍历

当队头元素出队的时候同时让队头元素的左右孩子节点也进队

 这里需要借助咱们之前写的队列的相关代码才可以玩哟!

 代码:
void Level_order(BT* root)
{
	Queque q;
	QuqueInit(&q);
	QuquePush(&q, root);
	while (!QuequeEmpty(&q))
	{
		BT* front = QuequeFront(&q);//取队头元素
		if (front)
			printf("%d ", front->val);
		QuquePop(&q);//删除队头元素
		//队头元素的左右孩子进队
		if (front)  
		{
			QuquePush(&q, front->left);
			QuquePush(&q, front->right);
		}
	}
	QuqueDestroy(&q);
}
7.借助二叉树前序遍历的结果实现对二叉树的构建

 分析: 还是分治的思想,层层递归,直到遇到空返回,把每一个节点看作一个新的根节点,只要当前节点不为空,就继续先序遍历

首先这是一个IO型的,所以完全需要自己手撕代码,

先把当前字符串的内容进行接收

其次利用递归:先序建立二叉树

子问题:先序建立    结束条件:遇到空,直接返回

最后利用递归写一个中序遍历

 辅助:需要我们每一个数据开辟节点

 定义一个二叉树的结构体:

 递归建立二叉树:

注意这里必须是 (*pi)++,不能是 *pi++。因为是递归调用每一次都需要进行栈帧建立,这样就不能做保证下标正确性,问题本质同二叉树求节点个数中的size

 完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef char BT_DataType; // 存储char类型数据
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BT_DataType val;

}BT;
BT* BuyNode( BT_DataType x)
{
	BT*  n1 = (BT*)malloc(sizeof(BT));
	if (n1 == NULL)
		return NULL;
	n1->val = x;
	n1->left = n1->right = NULL;
	return n1;
}
BT* CreateTree(char*pa,int* pi)
 {
    if(pa[*pi] == '#')
    {
        (*pi)++; // err *(pi)++
        return NULL;
    }
    BT*root = BuyNode(pa[*pi]);
    (*pi)++; // err *(pi)++
     root->left = CreateTree(pa,pi );
     root->right =CreateTree(pa,pi );
    return root;
 }
  void  In_Order(BT*root)
  {
    if(root == NULL)
    return ;
    In_Order(root->left);
    printf("%c ",root->val);
    In_Order(root->right);
  }

int main() 
{
    char a[100];
    scanf("%s",a);
    int i = 0;// 下标访问数组
   BT* root =  CreateTree(a,&i);
   In_Order(root);
}
8.判断一棵树是否为完全二叉树

 对于这个,可以借助层次遍历的思想来玩。只要是在出队的时候连续全部为空即为完全二叉树;否则就不是完全二叉树

 

代码:

bool BT_Complete(BT* root)
{
	Queque q;
	QuqueInit(&q);
	QuquePush(&q, root);
	while (!QuequeEmpty(&q))
	{
		BT* front = QuequeFront(&q);//取队头元素
		QuquePop(&q);//删除队头元素
		if (front == NULL)
			break;
		QuquePush(&q, front->left);
		QuquePush(&q, front->right);
	}
	//来到这只能有2种情况:队列为空  front == NULL
	while (!QuequeEmpty(&q))
	{
		//只能是front为空
		BT* front1 = QuequeFront(&q);//取队头元素
		if(front1)
			return false;  //非空 说明不是二叉树
		QuquePop(&q);
	}

	QuqueDestroy(&q);
	return true;
}
9:二叉树的查找

查找某个节点的值是否存在,若存在则返回该节点的地址,否则返回NULL

可以用先序来遍历

 错误示例:当我想查找3这个节点时候

 相信有不少老铁们一开始就会这样写吧,但是明明3这个节点存在为什么会没有找到呢?

其实通过我们调试发现这样写的逻辑是有Bug的,及时当要查找的节点存在时,也会造成把已找到的节点覆盖掉,其实这个查找逻辑的代码重在对return 返回的考察

正确代码:
BT* BT_Find(BT* root, BT_DataType x) // 3
{
	if (root == NULL)
		return  NULL;
	if (root->val == x)
		return root; 
	//先去左子树查找
	BT* left = BT_Find(root->left, x);
	if (left)
		return left;
	//说明左子树不存在,去右子树查找
	BT* right = BT_Find(root->right, x);
	if (right)
		return right;
	//来到这说明左右子树都不存在
	return NULL;
}

 运行结果:

10.二叉树相关OJ的练习
10.1 单值二叉树

 解题分析:

其实一个遍历就直接搞定了。拿双亲节点的值与孩子节点对应的值进行比较,若是不相等直接 return false

是不是看着比较简单,但是写起来是有坑滴

 

 运行结果:

其实走读一遍代码大概就找到问题所在了:return 语句返回是返回到调用当前函数的上一个函数里,并不是直接返回到最外层 

这个问题的本质同二叉树查找指定数据是一样的

OJ代码:
bool isUnivalTree(struct TreeNode* root) 
{
    if(root == NULL)
    return true;
    if(root->left)
    {
    if(root->val != root->left->val)
    return false;
    }
    if(root->right)
    {
    if(root->val != root->right->val)
    return false;
    }

    //递归左子树
    bool ret1 = isUnivalTree(root->left);
    //递归右子树
   bool ret2=  isUnivalTree(root->right);
    return ret1 && ret2;
}
10.2 判断2个二叉树是否相同

 解题分析:

采用遍历的方式,根节点与根节点进行对比,左孩子与左孩子对比,右孩子与右孩子对比,注意是对比val而不是对比节点所对应的地址

OJ代码:
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
 {
     if(p == NULL &&q == NULL)
     return true;

     if(p == NULL || q == NULL)
     return false;
    //来到这说明都不为空
    if(p->val  != q->val)
     return false;
    return  isSameTree(p->left,q->left) && isSameTree(p->right,q->right);//注意这里必须是 逻辑且的关系,不能是逻辑或
}

 OK,以上就是我今日要为大家分享的,希望各位都有得!对于二叉树这部分是数据结构初阶比较难的一部分了,初学的时候是不好理解,事事都有个过渡期,当然自己有空的时候也不要忘了敲敲代码!

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

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

相关文章

关键对话_

关键对话 https://www.bilibili.com/video/BV1Vh4y1E7sY 关键对话&#xff0c;是那些让你觉得很艰难&#xff0c;同时又非常重要的谈话 关键对话三个特征 1、对话双方的观点有很大差距&#xff0c; 比如说你和你父母观点差异很大&#xff0c;父母觉得到了一定年龄就该结婚啊…

2024年5月,软考防作弊系统强制收卷功能可能要上线了!

省流版&#xff1a; 1.网上报名支持PC、平板、移动端 2.忘记报名不要慌&#xff0c;可以补报名 3.信息处理技术员应用技术科目可以自动阅卷 4.防作弊系统升级&#xff0c;强制收卷 5.报名数据可以直接导出成图表 最近网上流传出了一份内部文件&#xff1a;《工业和信息化部教育…

光伏气象站的发展

TH-FGF9光伏气象站是一种集成了光伏发电系统和气象监测设备的新型设施。它利用光伏效应将太阳能转化为电能&#xff0c;同时配备了一系列气象传感器&#xff0c;实时监测和记录环境温度、湿度、风速、风向、降雨量等关键气象参数。光伏气象站不仅能够提供清洁能源&#xff0c;还…

C++——内存管理(new和delete)详解

目录 C/C内存管理 案例&#xff1a;变量在内存中到底会在哪&#xff1f; New和delete Operator new和operator delete函数 New和delete的原理 对内置类型 对自定义类型 定位new New/delete和malloc/free的区别 C/C内存管理 C/C内存管理分布图&#xff1a;&#xff08;从…

1028. 从先序遍历还原二叉树(三种方法:栈+递归+集合)

文章目录 1028. 从先序遍历还原二叉树&#xff08;三种方法&#xff1a;栈递归集合&#xff09;一、栈 while迭代1.思路2.代码 二、递归法1.思路2.代码 三、集合存储1.思路2.代码 1028. 从先序遍历还原二叉树&#xff08;三种方法&#xff1a;栈递归集合&#xff09; 一、栈 wh…

机器学习 -- 梯度下降算法加深

梯度下降算法 在机器学习中&#xff0c;梯度下降算法常用于最小化代价函数&#xff08;或损失函数&#xff09;&#xff0c;以此来优化模型的参数。代价函数衡量的是模型预测值与实际值之间的差异。通过最小化这个函数&#xff0c;我们可以找到模型预测最准确的参数。 代价函…

永磁同步电机无感FOC(龙伯格观测器)算法技术总结-仿真篇

文章目录 1、观测器的引入2、β轴向下的电机观测器数学模型3、β轴向下的转子点角度及速度观测4、Simulink仿真模型搭建4.1模型总览4.2 Luenberger观测器模块4.2.1 I_alpha观测4.2.2 I_beta观测4.2.3 e_alpha、e_beta观测4.2.4 锁相环 4.3 速度设定4.4 速度观测结果4.5 电角度观…

模板初阶的补充和string一些函数的用法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 模板初阶的补充 一、C语言中的字符串 二、标准库中的string类 2.1 string类(了解) 2.2 string类的常用接口说明&#xff08;注意下面我只讲解最常用的接口&…

【Leetcode每日一题】二分查找 - 寻找旋转排序数组中的最小值(难度⭐⭐)(22)

1. 题目解析 Leetcode链接&#xff1a;153. 寻找旋转排序数组中的最小值 这个题目乍一看很长很复杂&#xff0c;又是旋转数组又是最小值的 但是仔细想想&#xff0c;结合题目给的示例&#xff0c;不难看出可以用二分的方法来解决 核心在于找到给定数组里面的最小值 2. 算法原…

Linux编程 2.3 文件和目录-文件权限

1文件权限 #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> int lstat(const char *path,struct stat *buf)&#xff1b; 返回&#xff1a;成功返回0&#xff0c;失败返回-1 功能&#xff1a;获取文件属性 参数&#xff1a; const char *pa…

白酒:传统酿造工艺与现代科技相结合的创新实践

在云仓酒庄豪迈白酒的生产过程中&#xff0c;传统酿造工艺与现代科技的结合是推动产业发展的重要动力。云仓酒庄作为一家注重创新与实践的酒庄&#xff0c;在这方面进行了许多有益的探索和尝试。 首先&#xff0c;传统酿造工艺是云仓酒庄豪迈白酒的灵魂。在长期的生产实践中&am…

软件说明书怎么写?终于有人一次性说清楚了!

每次写软件说明书&#xff0c;你是不是总是毫无头绪&#xff0c;不知道从何下手&#xff1f;到各网站找资料&#xff0c;不仅格式不规范&#xff0c;甚至可能遗漏关键内容&#xff01;挨一顿批不说&#xff0c;还浪费大把时间。别着急&#xff0c;编写软件说明书&#xff0c;关…

智达方通总经理蔡志宏先生受邀参与哈工大京津冀校友发展研讨会,共话科技创新与发展新篇章

近日&#xff0c;哈尔滨工业大学京津冀校友发展研讨会在北京富力万达嘉华酒店隆重举办。本次校友研讨会以“以科技创新推动产业创新”为主题&#xff0c;汇聚京津冀地区广大哈工大校友的智慧和力量&#xff0c;共同探讨如何利用科技创新助力产业升级和企业发展。北京智达方通科…

IT廉连看——Uniapp——配置文件pages

IT廉连看——Uniapp——配置文件pages [IT廉连看] 本堂课主要为大家介绍pages.json这个配置文件 一、打开官网查看pages.json可以配置哪些属性。 下面边写边讲解 新建一个home页面理解一下这句话。 以下一些页面的通用配置 通用设置里我们可以对导航栏和状态栏进行一些设…

androidapp的开发流程,王者笔记

昨天去面了一家公司&#xff0c;价值观有受到冲击。 面试官技术方面没的说&#xff0c;他可能是个完美主义的人&#xff0c;无论什么事情到了他那里好像都有解决的方案&#xff0c;我被说的无所适从&#xff0c;感觉他很厉害。 但我不能认可的是&#xff0c;面试官觉得加班是…

LabVIEW水下温盐深数据一体化采集与分析

LabVIEW水下温盐深数据一体化采集与分析 开发一个基于LabVIEW的水下温盐深数据一体化采集与分析系统&#xff0c;实现海洋环境监测的自动化和精确化。通过集成温度、盐度和深度传感器&#xff0c;结合USB数据采集卡&#xff0c;利用LabVIEW软件开发的图形化界面&#xff0c;实…

ChatGPT-4 AI 绘图魔力释放

最近刚开通了 ChatGPT4&#xff0c;正好要设计一个网站图标&#xff0c;想测试一下它AI绘图的能力&#xff0c;让它根据文字描述生成一个想象中的图标 &#xff08;PS&#xff1a;如果想体验 GPT4 文生图&#xff0c;可以看这个教程 如何升级 ChatGPT 4.0&#xff09; 第1次交…

大数据毕业设计之前端04:管理系统为什么要自己实现图标组件

关键字&#xff1a;BuildAdmin、Icon、图标、Vue、ElementUI 前言 说到图标&#xff0c;在BuildAdmin中用到的地方很多。比如上一篇中的折叠图标&#xff0c;还有菜单栏图标、导航菜单栏图标等。常见的图标有&#xff1a;ElementUI图标、font-awesome、iconfont阿里图标以及本…

UE5 C++ 发射子弹发射(Projectile)

一.相关蓝图的练习&#xff0c;在我之前的文章中射击子弹案例-CSDN博客 本篇使用C实现 1.创建C类 MyBullet,在MyBullet.h中包含相关头文件 #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/StaticMeshComponent.…

接口测试开始前做什么?

在进行接口测试之前&#xff0c;测试工程师需要进行一系列的准备工作&#xff0c;以确保测试的顺利进行。以下是接口测试开始之前的准备工作&#xff0c;并附有示例说明&#xff1a; 1. 了解项目和接口文档&#xff1a; 在开始测试之前&#xff0c;测试工程师需要仔细阅读项目…