数据结构-二叉树力扣题

news2024/11/26 22:39:29

目录

1.相同的树

 2.二叉树中查找值为x的节点

3.单值二叉树

4.对称二叉树

 5.二叉树的前序遍历 

6.另一颗树的子树 

层序遍历:

7.二叉树遍历

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

 一个特殊的性质:


1.相同的树

题目链接:力扣(LeetCode)

思路:

这道题我们可以把它分为三个子问题,分别是:根与根、左子树与左子树、右子树与右子树之间的比较,而且最好是用前序的方式,即先比较根,要是根都不相等,后面的就不用比较了,而子树也可以在再分为根、左子树、右子树......直到不能分为止,所以每次递归调用:如果根的值不相等,就返回false,如果相等,就继续往下比较

注意:要分全为空、只有一个为空、全不为空三种情况处理。

代码如下:

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);
}

 2.二叉树中查找值为x的节点

这道题是延续我们上节内容

思路:

这道题思路很简单,也是分为:查找根、左子树、右子树三个子问题,先找根的值,如果相等就返回根节点,如果不等,继续找左子树,如果相等,返回左子树的节点,如果不相等,继续找右子树,如果相等,返回右子树节点,如果不等,则继续上面过程。

但是这道题我们要求返回的是节点,所以在某次递归中,如果找到了节点,就要先把它保存下来,然后再返回到这次递归的上一级,如果不保存,那节点极大可能返回不到函数外面。总而言之,递归是一级一级往上返回的,如果递归有返回值,每次递归都要确保能接收到下一次递归返回的值。

代码如下:

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

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail\n");
		return NULL;
	}
	node->left = NULL;
	node->right = NULL;
	node->data = x;
	return node;
}

BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;

	return node1;
}
//二叉树中查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
    return NULL;
}
int main()
{
	BTNode* root = CreatBinaryTree();
	BTNode* p = BinaryTreeFind(root, 3);
	printf("%d\n", p->data);
	return 0;
}

3.单值二叉树

题目链接:力扣(LeetCode)

思路:

 这道题,我们可以先比较根节点和左子树的值,然后比较根节点和右子树的值,当左子树不为空且它和根节点的值不相等时,返回false,当右子树不为空且它和根节点的值不相等时,返回false,当左右子树都和根节点相等时,继续递归,直到root==NULL,返回true,这时,true&&true=true,返回true。

代码如下:

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

4.对称二叉树

题目链接:力扣(LeetCode)

思路:

这道题默认有一个根节点,该节点不用比较所以我们可以比较根节点下的左子树和右子树,这就需要两个参数,我们可以把左子树的根节点和右子树的根节点作为左根和右根传给函数_isSymmetric(),然后把左根的左子树和右根的右子树比较,左根的右子树和右根的左子树比较,如果不等,返回false,如果相等,继续递归,直到全部为空,返回true,true&&true=true,返回true。

代码如下:

bool _isSymmetric(struct TreeNode*leftRoot,struct TreeNode*rightRoot)
    {
        //全部为空
        if(leftRoot==NULL&&rightRoot==NULL)
        {
            return true;
        }
        //只有一个为空
        if(leftRoot==NULL||rightRoot==NULL)
        {
            return false;
        }
        //全不为空
        if(leftRoot->val!=rightRoot->val)
        {
            return false;
        }
        return _isSymmetric(leftRoot->left,rightRoot->right)&&_isSymmetric(rightRoot->left,leftRoot->right);

    }
bool isSymmetric(struct TreeNode* root) {
    return _isSymmetric(root->left,root->right);
}

 5.二叉树的前序遍历 

题目链接:力扣(LeetCode)

思路:

这道题看起来很简单,只需要按照根、左子树、右子树的顺序依次访问即可,但是有很多坑:

1. 需要自己malloc数组,但是数组大小不知道,所以还要写一个函数用来计算二叉树大小

2. malloc数组之后就不能在原函数中写递归了,否则,每次递归都会malloc一个空间,所以又得专门写一个递归的函数。

3.二叉树节点的值要存储到数组中,必然要使用下标,但是下标在调用中不能传值,否则每次递归调用后,栈帧销毁,栈帧中形参i++后的值不能返回上一级递归,往数组中存放数据就会出错。

下面来看一个经典的错误:

int TreeSize(struct TreeNode* root)
 {
     return root==NULL?0:TreeSize(root->left)+TreeSize(root->right)+1;
 }
 void _preorderTraversal(struct TreeNode* root, int*a,int i)
 {
    if(root==NULL)
    {
        return;
    }
    a[i++]=root->val;
    _preorderTraversal(root->left,a,i);
    _preorderTraversal(root->right,a,i);
 }
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    *returnSize=TreeSize(root);
    int*a=(int*)malloc(*returnSize*sizeof(int));
    int i=0;
    _preorderTraversal(root, a,i);
    return a;
}

 可以看到,下面用例执行错误了:

为什么呢?

画一下递归调用图就知道了:

所以以后在递归时如果需要传下标,最好传地址,用指针接收。 

正确代码如下:

int TreeSize(struct TreeNode* root)
 {
     return root==NULL?0:TreeSize(root->left)+TreeSize(root->right)+1;
 }
 void _preorderTraversal(struct TreeNode* root, int*a,int* pi)
 {
    if(root==NULL)
    {
        return;
    }
    a[(*pi)++]=root->val;
    _preorderTraversal(root->left,a,pi);
    _preorderTraversal(root->right,a,pi);
 }
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    *returnSize=TreeSize(root);
    int*a=(int*)malloc(*returnSize*sizeof(int));
    int i=0;
    _preorderTraversal(root, a,&i);
    return a;
}

6.另一颗树的子树 

思路:

这道题本质还是找两颗相同的树,只不过一个树必须得是另外一个树的子树,那我们可以复用上文中“相同的树”的代码,然后遍历找到第一棵树的所有子树与第二棵树比较,如果不同,继续遍历,只要左右子树中有一个子树与第二颗树相同就返回true。

题目链接:力扣(LeetCode)

代码如下:

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);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root==NULL)
    {
        return false;
    }
    if(isSameTree(root,subRoot))
    {
        return true;
    }
    return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}

在讲下一道题之前我们先来学习一下二叉树的层序遍历。

层序遍历:

二叉树的层序遍历是一层一层往下走的,我们可以用队列来实现它,用队列保存二叉树每一层的节点,先让根节点入队,保存并打印根节点的值,然后根节点出队,让它的左右孩子入队,相当于第一层出队的同时带第二层入队,一层带一层,直到队列中所有节点都为NULL,就说明二叉树遍历结束了。

层序需要队列的代码,这里只列出层序的核心代码,关于队列部分的代码,可以在栈与队列章节中找到: 数据结构-栈和队列(一)-CSDN博客

层序代码:

//层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->data);
		if (front->left)
			QueuePush(&q,front->left);
		if (front->right)
			QueuePush(&q,front->right);
	}
	printf("\n");
	DestoryQueue(&q);
}

注意,这里就是三层嵌套了,队列Queue中有头尾节点和队列大小size,头尾节点中有next指针和数据data,而数据data中存放的是二叉树的节点指针,所以我们在Queue.h中要把数据类型改成二叉树节点指针:

三层嵌套的关系如下图:

所以我们每次pop的都是队列的节点,而front是指向二叉树节点的指针,它没有被pop,所以可以找到二叉树节点的数据和它的左右孩子。

再补充一个内容,那就是二叉树的销毁:

二叉树销毁最好用后序,先销毁左右子树,最后销毁根节点,不建议使用前序,否则先销毁根节点,就找不到左右子树了,又要重新构建二叉树,比较麻烦。

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

7.二叉树遍历

题目链接:二叉树遍历_牛客题霸_牛客网

这道题其实包含了二叉树的创建和遍历, 相当于给一个字符串,要求把给字符串恢复成一个二叉树,然后输出它的中序遍历,那abc##de#g##f###,它是前序遍历的,我们就用前序把它恢复成二叉树:

创建二叉树时,也是先创建根节点,然后创建左子树、右子树。

注意:在传数组下标时,传地址,用指针接收。

代码如下:

#include <stdio.h>
#include<stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail\n");
		return NULL;
	}
	node->left = NULL;
	node->right = NULL;
	node->data = x;
	return node;
}
//创建二叉树
BTNode*CreateTree(char*a,int*pi)
{
    if(a[*pi]=='#')
    {
        (*pi)++;
        return NULL;
    }

    BTNode*root=BuyNode(a[*pi]);
    (*pi)++;

    root->left=CreateTree(a,pi);
    root->right=CreateTree(a,pi);
    return root;
}
//中序遍历
void InOrder(BTNode*root)
{
    if(root==NULL)
    {
        return;
    }
    InOrder(root->left);
    printf("%c ",root->data);
    InOrder(root->right);

}
int main() 
{
    char a[100];
    scanf("%s",a);
    int i=0;
    BTNode*root=CreateTree(a,&i);
    InOrder(root);
    printf("\n");
    return 0;
}

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

思路:

 这道题就要使用到层序遍历了,我们说层序遍历是一层带一层的入队和出队的过程,而完全二叉树的每一层都是按顺序排放的,让它的每一层数据入队,然后出队,直到遇见空,如果是完全二叉树,那此时最后一个数据出队后,后面应该全为NULL,而非完全二叉树就不全是NULL了,如下图:

所以当我们遇到空时就跳出循环,然后判断后面的是否全为空,一旦有非空,那就不是完全二叉树。

 代码如下:

//判断二叉树是否是完全二叉树
bool BTreeComplete(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* front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			DestoryQueue(&q);
			return false;
		}
	}
	DestoryQueue(&q);
	return true;
}

二叉树的一个特殊的性质:

 对任何一棵二叉树, 如果度为0的叶结点个数为n0 , 度为2的分支结点个数为 n1,则有 n0=n1 +1

 简单推导一下:

对二叉树增加一个度为1的节点就一定会减少一个度为0的节点,同时增加的这个节点度也为0,此时度为0和度为2的节点个数相当于没变:

而增加一个度为2的节点就一定会减少一个度为1的,同时会增加一个度为0的节点:

所以二叉树中度为0的节点总是比度为2的节点个数多一个。 

下面我们来做道题:

在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2

答案是:A

假设度为0的节点有n0个,度为1的有n1个,度为2的有n2个,根据上文学过的性质,可得:

n0 + n1 + n0-1=2n,而此时要整除(叶子节点一定是整数个),n1必然是1,所以可得n0 = 2n/2 = n

好了,到今天为止,我们二叉树就告一段落了,其实二叉树还有很多问题,这些留到后面C++的时候学习,

下节内容会接着讲解排序,敬请期待。。。 

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

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

相关文章

基于操作系统讨论Java线程与进程、浅谈Go的线程与管程

文章目录 操作系统中的进程进程概念进程的状态 Java中的进程Java进程的概念Java进程的特性Java进程的状态Java进程与操作系统进程的通信 操作系统的进程和Java进程的区别联系操作系统进程Java 进程区别和联系 操作系统中的线程动机优点多核编程 Java中的线程定义&#xff1a;特…

ping命令使用示例解析

【一】ping命令简介 ping &#xff08;Packet Internet Groper&#xff09;是一种因特网包探索器&#xff0c;用于测试网络连接量的程序。ping的一般用途有&#xff1a; ①【测试网络物理链路是否正常】&#xff1a;通过将ICMP(Internet控制消息协议)回显数据包发送到网络终端&…

Unity中Shader图形流水线中的纹理

文章目录 前言一、图形流水线中的纹理1、我们的纹理一般用于&#xff1a;2、纹理的获取方式&#xff1a; 二、纹理的分类1、颜色纹理2、几何纹理 三、纹理管线四、纹理的作用1、纹理可以 替换 漫反射模型中的 漫反射系数Kd2、纹理还有的作用 前言 Unity中Shader图形流水线中的…

【入门篇】1.3 redis客户端之 jedis 高级使用示例

文章目录 0.前言1. 发布和订阅消息2. 事务操作3. 管道操作4. jedis 支持哨兵模式5. jedis 支持集群模式5. 参考链接 0.前言 Jedis是Redis的Java客户端&#xff0c;它支持所有的Redis原生命令&#xff0c;使用方便&#xff0c;且可以与Java项目无缝集成。 该库的最新版本支持Re…

C#创建并调用dll

文章目录 1.VS2019创建C#主程序2.编译主程序3.添加类库工程&#xff0c;并添加计算逻辑4.给主程序添加引用项5.重新编译主程序6.主程序添加测试逻辑 1.VS2019创建C#主程序 2.编译主程序 debug目录下生成exe&#xff1a; 3.添加类库工程&#xff0c;并添加计算逻辑 添加计算逻…

ROS 多机器人导航RVIZ环境的配置

文章目录 前言一、添加多个2D Pose Estimate/2D nav Goal二、RVIZ的其他配置1.RobotModel 总结 前言 前文ROS Turtlebot3多机器人编队导航仿真写了多机器人呢导航的代码实现&#xff0c;本文主要说明&#xff0c;RVIZ导航环境的配置&#xff0c;如何添加多个2D Pose Estimate/…

CODING DevOps产品认证笔记

1.敏捷&精益&瀑布概述 1.1 敏捷软件开发 第一章敏捷软件开发背景 背景&#xff1a;乌卡时代 易变性:当今世界的变化越来越多越来越快&#xff0c;越来越不可预测。不确定性:历史上的任何一个时代所带来的经验已经无法为当今世界的所有变化提供参照。复杂性:事物间的…

Uniapp连接iBeacon设备——实现无线定位与互动体验(实现篇)

export default { data() { return { iBeaconDevices: [], // 存储搜索到的iBeacon设备 deviceId: [], data: [], url: getApp().globalData.url, innerAudioContext: n…

【MATLAB源码-第79期】基于蚯蚓优化算法(EOA)的栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 蚯蚓优化算法&#xff08;Earthworm Optimisation Algorithm, EOA&#xff09;是一种启发式算法&#xff0c;灵感来源于蚯蚓在自然界中的行为模式。蚯蚓优化算法主要模仿了蚯蚓在寻找食物和逃避天敌时的行为策略。以下是蚯蚓…

Python Numpy.einsum

、 见 https://zhuanlan.zhihu.com/p/27739282

程序员如何悄悄拔尖,然后惊艳四座?

谁最初还不是个血气方刚、胸怀抱负的有志青年了&#xff1f; 渐渐的&#xff0c;被岁月磨平棱角&#xff0c;被生活冲淡理想&#xff0c;如今的你还有几分似从前&#xff1f; 你是否&#xff0c;不安现状、焦虑未来、忙忙碌碌没有方向&#xff1f; 不甘平庸?不想摸鱼&#…

论文技巧2

目录 1 找基准模型2 找模块小论文的三个实验怎么做对比试验Sota的挑选对⽐论⽂结果的获取3 消融实验什么是消融实验怎么做消融实验4 实例分析怎么做实例分析小论文必备三张图1 找基准模型 2 找模块 小论文的三个实验 怎么做对比试验

桂院校园导航 | 云上高校导航 云开发项目 二次开发教程 1.3

Gitee代码仓库&#xff1a;桂院校园导航小程序 GitHub代码仓库&#xff1a;GLU-Campus-Guide 演示视频 中国大学生计算机设计大赛-移动应用与开发-云上高校导航 升级日志 1.3 优化了小程序的数据存储方式&#xff0c;对部分页面进行了调整&#xff0c;调整了功能和代码。 引…

sscanf提取相应字符到数组

代码如下 #include<stdio.h> #include<string.h>int main(int argc, char const *argv[]) {char buf[128] {0};int m1 0, m2 0;int s1 0, s2 0;char lrc[128] "";sscanf("[02:16.33][04:11.44]我想大声宣布对你恋恋不舍","[%*1d%d…

【Pytorch笔记】7.torch.nn (Convolution Layers)

我们常用torch.nn来封装网络&#xff0c;torch.nn为我们封装好了很多神经网络中不同的层&#xff0c;如卷积层、池化层、归一化层等。我们会把这些层像是串成一个牛肉串一样串起来&#xff0c;形成网络。 先从最简单的&#xff0c;都有哪些层开始学起。 Convolution Layers -…

YOLO目标检测——谢韦尔钢材缺陷检测数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;钢材质量控制、钢材缺陷检测数据集说明&#xff1a;谢韦尔钢材缺陷检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(jso…

交换机如何配置BGP协议

环境&#xff1a; 华为交换机 华三交换机 问题描述&#xff1a; 交换机如何配置BGP协议 解决方案&#xff1a; 华三交换机上配置案例 1.配置BGP协议&#xff0c;可以按照以下步骤进行&#xff1a; 登录交换机&#xff1a;使用SSH、Telnet或控制台等方式登录到华三交换…

SpringBoot整合Redis使用基于注解的缓存

环境准备 注解 EnableCaching CacheConfig CacheConfig 提供了一种在类级别共享公共缓存相关设置的机制。 | 参数 | 作用 | | | — | — | — | | cacheNames | 使用在类上的默认缓存名称 | | | keyGenerator | 用于类的默认KeyGenerator的bean名称 | | | cacheManager | 自定…

Linux内核中的内存管理剖析,干货收藏!

内存管理的主要工作就是对物理内存进行组织&#xff0c;然后对物理内存的分配和回收。但是Linux引入了虚拟地址的概念。 虚拟地址的作用 如果用户进程直接操作物理地址会有以下的坏处&#xff1a; 用户进程可以直接操作内核对应的内存&#xff0c;破坏内核运行。 用户进程也…

【小黑送书—第七期】>>程序员To架构师?一起来看架构师系列书籍推荐

相信大家都对未来的职业发展有着憧憬和规划&#xff0c;要做架构师、要做技术总监、要做CTO。对于如何实现自己的职业规划也都信心满满&#xff0c;努力工作、好好学习、不断提升自己。 相信成为一名优秀的架构师是很多程序员的目标&#xff0c;架构师的工作包罗万象&#xff…