【数据结构】二叉树的相关操作以及OJ题目

news2024/10/7 10:22:58

文章目录

  • 1. 二叉树
  • 2.二叉树的遍历
    • 2.1前序遍历
    • 2.2中序遍历
    • 2.3后序遍历
    • 2.4层序遍历
  • 3.树的节点个数
  • 4.树的高度
  • 5.叶子节点的个数
  • 6.第k层节点的个数
  • 7.查找x所在的节点
  • 8.树的销毁
  • 9.相关题目
    • 9.1相同的树
    • 9.2单值二叉树
    • 9.3对称二叉树
    • 9.4二叉树的构建
    • 9.5翻转二叉树
    • 9.6另一颗树的子树
  • 10.判断二叉树是否是完全二叉树

在这里插入图片描述

1. 二叉树

当一个树不是满二叉树或完全二叉树时,它是不适合使用数组存储的,它应该使用链式结构来存储。
再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

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

在这里插入图片描述

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

typedef int TNDataType;
typedef struct TreeNode
{
	TNDataType val;
	struct TreeNode* left;
	struct TreeNode* right;
}TreeNode;

2.二叉树的遍历

所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历——访问根结点的操作发生在遍历其左右子树之前。 (根左右)
  2. 中序遍历——访问根结点的操作发生在遍历其左右子树之中(间)。(左根右)
  3. 后序遍历——访问根结点的操作发生在遍历其左右子树之后。(左右根)

2.1前序遍历

在这里插入图片描述
前序遍历:先访问树的根,然后访问左子树(左子树又被分为根、左子树、右子树,直到左子树不能拆分),然后再访问右子树(右子树又被分为根、左子树、右子树,直到右子树不能拆分)

先遍历根(有根一直根),然后左(有左一直左),最后右,拆分了以后就从头来

遍历结果	1 2 3 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL
谁的NULL:      3    3    2        5    5      6    6
void PreOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->val);//先遍历根

	PreOrder(root->left);//然后遍历左子树

	PreOrder(root->right);//最后遍历右子树
}

2.2中序遍历

在这里插入图片描述
中序遍历:有左子树就一直访问左子树,直到左子树不能再拆分,然后遍历根,最后遍历右子树(右子树又拆分为根、左子树、右子树。也得先访问左子树)

有左一直左,然后根,最后右,拆分了以后就从头来

遍历结果: NULL 3 NULL 2 NULL 1 NULL 5 NULL 4 NULL 6 NULL
谁的NULL: 3       3      2      5      5      6     6
void InOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);//先遍历左子树

	printf("%d ", root->val);//再遍历根

	InOrder(root->right);//最后遍历右子树
}

2.3后序遍历

在这里插入图片描述
后序遍历:先遍历左子树(左子树又拆分为:根、左子树、右子树),然后遍历右子树(右子树又拆分为:根、左子树、右子树),最后遍历根。
有左一直左,然后右,最后根,拆分了以后就从头来

遍历结果: NULL NULL 3 NULL 2 NULL NULL 5 NULL NULL 6 4 1
谁的NULL:  3    3      2      5    5      6    6  
//后序遍历
void PostOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	PostOrder(root->left);//先遍历左子树

	PostOrder(root->right);//再遍历右子树

	printf("%d ", root->val);//最后访问根
}

2.4层序遍历

二叉树的层序遍历需要借助队列实现。
首先需要将根入队列,然后再出队列,出队列时,需要将自己的孩子带进队列之中。由于栈先进先出的特点,只有上一层全部出完了才会出当前层,故可以实现层序遍历。

需要注意的是:链表的数值域设置为指向树节点的指针

//树的结构
typedef int TNDataType;
typedef struct TreeNode
{
	TNDataType val;
	struct TreeNode* left;
	struct TreeNode* right;
}TreeNode;


//链表的节点
typedef TreeNode* QNodeDataType;//将链表的数值域设置为指向树节点的指针
typedef struct QueueNode
{
	QNodeDataType data;
	struct QueueNode* next;
}QNode;
//层序遍历
void TreeLevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		//出数据
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			printf("%d ", front->val);
			//出的数据将其孩子带进队列中(带下一层)
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else
		{
			printf("N ");
		}
	}
	printf("\n");
	QueueDestroy(&q);
}

3.树的节点个数

方法:当前节点只加自己,然后再加上左子树和右子树中节点的个数

int TreeSize(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + TreeSize(root->left) + TreeSize(root->right);
}

4.树的高度

当前层加上子树中高度大的一个

int  TreeHeight(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int left = TreeHeight(root->left);
	int right = TreeHeight(root->right);
	return  left > right ? left + 1 : right + 1;
}

5.叶子节点的个数

是叶子,返回1;不是叶子,继续遍历左右子树,空树返回0
在这里插入图片描述

int BinaryTreeLeafSize(TreeNode* root)
{
	//空树,返回空
	if (root == NULL)
	{
		return 0;
	}
	//叶子,返回1
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//继续遍历左右子树
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

6.第k层节点的个数

不是第k层就继续遍历左右子树,空树返回0;第k层就返回1。
假设K等于3
在这里插入图片描述

int TreeKLevel(TreeNode* root, int k)
{
	//空树
	if (root == NULL)
	{
		return 0;
	}
	//到第k层
	if (k == 1)
	{
		return 1;
	}
	//未到第k层,继续在左右子树中寻找
	//层数减少
	return TreeKLevel(root->left,k-1) + TreeKLevel(root->right,k-1);
}

7.查找x所在的节点

当前节点为空数,返回NULL,当前节点是x,返回x的地址;当前节点不是x,先在左子树中找,左子树找不到,再到右子树中找。

TreeNode* TreeFind(TreeNode* root, int x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->val == x)
	{
		return root;
	}
	//先在左子树中找
	TreeNode* left = TreeFind(root->left,x);
	if (left)//找到了返回
	{
		return left;
	}
	//左子树找不到。那就在右子树中找
	TreeNode* right = TreeFind(root->right, x);
	if (right)
	{
		return right;//找到了,返回
	}
	//左右都找不到,整棵树都找不到
	return NULL;
}

在这里插入图片描述

8.树的销毁

//树的销毁,采用后序遍历销毁
void TreeDestroy(TreeNode* root)
{
	if (root == NULL)
		return;

	TreeDestroy(root->left);
	TreeDestroy(root->right);

	free(root);
}

9.相关题目

9.1相同的树

在这里插入图片描述
题目链接
先比较两树的根是否相等,若相等,在比较子树是都对应相等;若不相等,直接返回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(q->right,p->right);
}

9.2单值二叉树

在这里插入图片描述
题目链接
若当前节点的值不等于根的值,直接false;若当前节点为NULL,则返回true,当前节点不是空,则检查它左树与右数的值等不等于根的值。

bool UnivalTree(struct TreeNode* root ,int val)
{
    if(root == NULL)
    {
        return true;
    }
    if(root->val != val)
    {
        return false;
    }

    return UnivalTree(root->left,val) && UnivalTree(root->right,val);
}

bool isUnivalTree(struct TreeNode* root) {
    if(root == NULL)
    {
        return true;
    }
    int val = root->val;

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

9.3对称二叉树

在这里插入图片描述
题目链接

bool check(struct TreeNode* left,struct TreeNode* right)
{
    if(left == NULL && right == NULL)
    {
        return true;
    }

    if(left == NULL || right == NULL)
    {
        return false;
    }
    //当前节点相等,遍历左右子树
    return left->val == right->val
        && check(left->left,right->right)
        && check(left->right,right->left);
}

bool isSymmetric(struct TreeNode* root) {
    if(root == NULL)
    {
        return true;
    }
    return check(root->left,root->right);
}

9.4二叉树的构建

在这里插入图片描述

题目链接

//前序遍历,构建二叉树
TreeNode* CreatTree(char* a, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	//先构建根
	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	root->val = a[(*pi)++];
	//然后构建左右子树
	root->left = CreatTree(a, pi);
	root->right = CreatTree(a, pi);
	return root;
}

9.5翻转二叉树

在这里插入图片描述
题目链接
思路:将根的子树依次进行交换
在这里插入图片描述

struct TreeNode* invertTree(struct TreeNode* root) {
    if(root == NULL)
        return NULL;

    //交换当前树的左右子树
    struct TreeNode* tmp = root->left;
    root->left = root->right;
    root->right = tmp;

    //递归交换左右子树
    invertTree(root->left);
    invertTree(root->right);

    return root;
}

9.6另一颗树的子树

在这里插入图片描述
题目链接
其实这一题就是9.1的变种,本质上还是找两棵相同的树。

  • 若当前根节点与另一棵树相等,那就从当前节点开始,依次比较子树,检查是否相等。
  • 若子树为空或与另一棵树不相等,则返回false.
bool isSameTree(struct TreeNode* root, struct TreeNode* subRoot)
{
    if(root == NULL && subRoot == NULL)
        return true;
    if(root == NULL || subRoot == NULL)
        return false;
    if(root->val != subRoot->val)
        return false;
    return isSameTree(root->left,subRoot->left)
        && isSameTree(root->right,subRoot->right);
}

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

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

思路:与层序遍历类似,依然使用入队出队的方式。‘
如果当前出队元素为NULL,则队中的剩余元素必须全部为空,否则就不是一棵完全二叉树。

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		//若当前出队元素不是空,则该元素出队,将它的孩子进队
		if (front)
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else
		{
			//当前出的元素是空,停止出
			break;
		}
	}
	//继续出,检查有无非空节点
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL)
		{
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

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

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

相关文章

Learn OpenGL 17 立方体贴图

立方体贴图 我们已经使用2D纹理很长时间了,但除此之外仍有更多的纹理类型等着我们探索。在本节中,我们将讨论的是将多个纹理组合起来映射到一张纹理上的一种纹理类型:立方体贴图(Cube Map)。 简单来说,立方体贴图就是一个包含了…

Java基础夯实——八股文【2024面试题案例代码】

1、Java当中的基本数据类型 Java中常见的数据类型及其对应的字节长度和取值范围如下: byte:1字节,取值范围为-128到127。short:2字节,取值范围为-32,768到32,767。int:4字节,取值范围为-2,147…

【Greenhills】GHS-MULTI IDE-Ubuntu纯命令系统部署license文件

【更多软件使用问题请点击亿道电子官方网站查询】 1、 文档目标 记录在Ubuntu纯命令系统中部署license文件的步骤。 2、 问题场景 客户服务器为Linux纯命令行的环境,客户也无其他服务器可以部署,需在纯命令行上尝试安装。 3、软硬件环境 1&#xff09…

【Linux系统编程】进程程序替换

介绍: 进程程序替换是指将一个进程中正在运行的程序替换为另一个全新的程序的过程,但替换不是创建新进程,只是将对应程序的代码和数据进行替换。具体来说,这个替换过程涉及将磁盘中的新程序加载到内存结构中,并重新建立…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:FlowItem)

瀑布流组件的子组件,用来展示瀑布流具体item。 说明: 该组件从API Version 9开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。仅支持作为Waterflow组件的子组件使用。 子组件 支持单个子组件。 接口 FlowItem() 使…

【RK android6.0 实现假关机真开机效果】

RK android6.0 实现假关机真开机效果 需求描述解决方法 郑重声明:本人原创博文,都是实战,均经过实际项目验证出货的 转载请标明出处:攻城狮2015 Platform: Rockchip CPU:rk3368 OS:Android 7.1.2 Kernel: 3.10 需求描述 由于硬件设计,使用错误…

Qt实现简单的五子棋程序

Qt五子棋小程序 Qt五子棋演示及源码链接登陆界面单机模式联机模式联网模式参考 Qt五子棋 参考大佬中国象棋程序,使用Qt实现了一个简单的五子棋小程序,包含了单机、联机以及联网三种模式;单机模式下实现了简易的AI;联机模式为PtoP…

Verilog case/casez/casex的区别

casez/casex语句虽然EDA工具也可以综合出来,但是注意,casez/casex综合出来的电路和case语句综合出来的电路可能是不同的,一定要慎用。而且综合工具也会告诉你casez/casex中的“?”"x""z"的comparison is always false&a…

提速增效!Figma插件推荐,助你事半功倍!

随着设计工具的更换,设计师不再局限于传统软件的重复操作,而是越来越追求能够提高设计效率的插件。从Photoshop到Sketch,再到最受欢迎的Figma,插件层出不穷。Figma是一种基于浏览器和团队合作的设计工具,可以在任何平台…

HackTheBox Blackfield

[1] 靶机信息状态退役难度HardIP/地址https://app.hackthebox.com/machines/Blackfield价格需要订阅 14$ /20 $ /月 端口扫描 └──╼ #nmap -p- --min-rate1000 -T4 10.129.229.17 Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-14 14:14 GMT Nmap scan report for…

【iOS】ARC学习

文章目录 前言一、autorelease实现二、苹果的实现三、内存管理的思考方式__strong修饰符取得非自己生成并持有的对象__strong 修饰符的变量之间可以相互赋值类的成员变量也可以使用strong修饰 __weak修饰符循环引用 __unsafe_unretained修饰符什么时候使用__unsafe_unretained …

JVM中对象创建过程

在JVM中对象的创建,我们从一个new指令开始: 这个过程大概图示如下: 虚拟机收到new指令触发。 类加载检查:如果类没有被类加载器加载,则执行类加载流程(将class信息加载到JVM的运行时数据区的过程&#xff…

【SRE系列之docker容器】--dockerfile镜像优化

dockerfile镜像优化 1.1 镜像优化方法 系统镜像采用ubuntu或者alpine,会比centos少1G左右编写业务镜像时从官网拉取镜像,其余配置根据业务需求再配置编写dockerfile时把不用的安装包卸载或者删除尽量减少run命令的使用(一个run命令&#xf…

《手把手教你》系列技巧篇(三十九)-java+ selenium自动化测试-JavaScript的调用执行-上篇(详解教程)

1.简介 在做web自动化时,有些情况selenium的api无法完成,需要通过第三方手段比如js来完成实现,比如去改变某些元素对象的属性或者进行一些特殊的操作,本文将来讲解怎样来调用JavaScript完成特殊操作。 2.用法 创建一个执行 JS 的…

公众号关闭自定义菜单

1、登录公众号 https://mp.weixin.qq.com/ 2、找到侧边导航-》新的功能 3、已开通-》自定义菜单 4、点击停用

Cesium:绘制一个 3DTiles 对象的外包盒顶点

作者:CSDN @ _乐多_ 本文将介绍如何使用 Cesium 引擎根据模型的中心坐标,半轴信息,绘制一个 3DTiles 对象的外包盒顶点。 外包盒是一个定向包围盒(Oriented Bounding Box),它由一个中心点(center)和一个包含半轴(halfAxes)组成。半轴由一个3x3的矩阵表示,这个矩阵…

产品数据管理系统哪家好?产品数据管理系统厂商

产品数据管理系统(PDM)的选择取决于企业的具体需求、规模、行业以及预算。市场上有很多优秀的PDM供应商,每一家都有其独特的优势和特点。以下是一些在市场上广受好评的PDM供应商,供您参考: 彩虹PLM系统:彩虹…

java垃圾回收-三色标记法

三色标记法 引言什么是三色标记法白色灰色黑色 三色标记过程三色标记带来的问题多标问题漏标问题 如何弥补漏标问题增量更新原始快照总结 引言 在CMS,G1这种并发的垃圾收集器收集对象时,假如一个对象A被GC线程标记为不可达对象,但是用户线程又把A对象做…

GEE:基于变异系数法(CV)进行遥感生态指数(RSEI)波动分析

作者:CSDN @ _养乐多_ 本文将在 Google Earth Engine(GEE)平台上复现论文《基于遥感生态指数的青藏公路典型路段路域生态环境质量评估与分析》中使用变异系数法对遥感生态指数(RSEI)进行波动分析的方法和代码。 其公式如下所示, 结果如下所示, 文章目录 一、核心函数二…