[数据结构基础]链式二叉树的几个典型的基础oj问题

news2024/12/22 20:53:41

今年是农历腊月二十九,提前祝大家新春快乐。这是我壬寅虎年最后一篇文章,感谢大家的阅读。祝大家兔年吉祥,身体健康、阖家幸福、学业有成、事业如意、财源滚滚!

前置说明

本文中所有用到的二叉树及二叉树节点,都是由下面的代码定义创建的:

typedef int BTDataType;  //二叉树存储的数据类型

//二叉树节点
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;   //左子树
	struct BinaryTreeNode* right;  //右子树
}BTNode;  //将二叉树节点结构体类型重定义为BTNode

将二叉树中存储的数据的类型重定义为BTDataType,二叉树节点定义为结构体类型数据,结构体成员包括:存储的数据data、指向其左孩子节点的指针left、指向其右孩子节点的指针right,将二叉树节点结构体类型重定义为BTNode。

问题1. 统计二叉树节点个数

1.1 问题描述

设计函数int BinaryTreeSize(BTNode* root),统计一颗二叉树中有几个节点,其中函数的参数root为指向根节点的指针,函数二叉树返回节点个数。

如图1.1所示二叉树,节点个数为6。

图1.1 二叉树

1.2 解题思路

本题采用递归的思想来解决(见图1.2)

  1. 判断传入函数的根节点指针是否为NULL,如果是,则函数返回0。
  2. 如果根节点指针不是NULL,则将root的左孩子节点和右孩子节点作为参数传给函数BinaryTreeSize递归调用,函数返回1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right),即:左子树节点数 + 右子树节点数 + 1。
图1.2  统计二叉树节点个数思维图

1.3 函数代码

//统计二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (NULL == root)
		return 0;

	//计入root节点。然后再递归调用函数计算左右子树的节点个数
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

问题2. 统计二叉树中叶子节点的个数

2.1 问题描述

设计函数int BinaryTreeLeafSize(BTNode* root),统计一颗二叉树中叶子结点的个数。叶子节点就是左孩子节点和右孩子节点均为空的节点。

如图1.1所示的二叉树,其叶子节点为D、E、F,叶子节点个数为3。

2.2 解题思路

通过递归的思想解决问题(见图2.1)

  1. 判断传入函数的根节点root是否为NULL,如果是,则表示树为空,函数返回NULL。
  2. 在root不为空的前提下,判断root的左孩子节点和右孩子节点是否均为NULL,如果均为NULL,则证明root表示的节点为叶子节点,函数返回1。
  3. 若root既不是NULL也不是叶子节点,则统计以root为根节点的左右子树共有几个叶子节点,函数返回BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right)。
图2.1  统计二叉树中叶子节点个数思维图

2.3 函数代码

//统计二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (NULL == root)
		return 0;

	//如果root的左孩子节点和右孩子节点均为NULL,则root为叶子结点
	if (root->left == NULL && root->right == NULL)
		return 1;

	//root不是NULL也不是叶子节点
	//统计root的左子树中叶子节点个数和右子树中叶子节点个数并相加
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

问题3. 统计二叉树中第k层节点个数 

3.1 问题描述

以根节点为二叉树的第一层,设计函数int BinaryTreeLevelKSize(BTNode* root, int k),统计第k层有几个节点,如图3.1所示的二叉树,其第1、2、3层的节点个数为分别为1、2、3。

图3.1 二叉树

3.2 解题思路

统计二叉树中第k层节点数,可以转化为统计二叉树的左子树和右子树第k-1层的节点个数之和,并最终转换为计算第k-1层所有节点的孩子节点个数之和。通过递归的思想来解决问题(见图3.2)。

  1. 判断root是否为空,如果为空,则函数返回NULL。
  2. 判断root是否为第k层的节点(k==1是否成立),如果是,则函数返回1。
  3. 如果root既不为空也不是第k层的节点,则统计其左子树和右子树第k-1层节点的个数之和,即函数返回BinaryTreeLevelKSize(root->left, k-1) + BinaryTreeLevelKSize(root -> right, k-1)。
图3.2 统计第k层节点数思维图

3.3 函数代码

//统计二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return BinaryTreeLevelKSize(root->left, k - 1) 
		+ BinaryTreeLevelKSize(root->right, k - 1);
}

问题4. 查找二叉树中值为x的节点

4.1 问题描述

设计函数BTNode* BinaryTreeFind(BTNode* root, BTDataType x),在一颗二叉树中查找值为x的节点。如果查找到了这样的一个节点,就返回指向这个节点的指针,如果没有找到,函数返回NULL。

4.2 解题思路

采用递归的思想,按照前序遍历的方法,依次将每一个节点的值与x比较,如果相同,就返回该节点的地址,具体步骤为:

  1. 判断root是否为NULL,如果是,则返回NULL。
  2. 判断根节点root的数据是否为x,如果是,返回root。
  3. 若干root既不是NULL,其节点值也不是x,将root->left作为第一个参数传给BinaryTreeFind函数递归调用,查找其左子树中是否存在x,如果存在,函数返回值为x的节点,函数不再走步骤4。
  4. 如果左子树中未找到x,那么再将root->right作为第一个参数传给函数BinaryTreeFind递归调用,查找右子树中是否存在x。如果存在,函数返回值为x节点的地址,如果不存在,则表明二叉树中没有x,函数返回NULL。
图4.1 二叉树遍历顺序示意图

4.3 函数代码

//查找二叉树中值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}

	//根节点值为x,直接返回根节点地址
	if (root->data == x)
	{
		return root;
	}

	//根节点值不为x,去左子树查找
	BTNode* LeftFind = BinaryTreeFind(root->left, x);
	if (LeftFind)
	{
		return LeftFind;
	}

	//根节点值不为x且左子树中没有x,查找右子树
	BTNode* RightFind = BinaryTreeFind(root->right, x);
	if (RightFind)
	{
		return RightFind;
	}

	return NULL;
}

问题5. 判断一颗二叉树是否为单值二叉树

5.1 问题描述

如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树,如图5.1,图中左边二叉树是单值二叉树,右边二叉树不是单值二叉树。设计函数bool isUnivalTree(struct TreeNode* root),判断一颗二叉树是否为单值二叉树。

图5.1 单值二叉树和非单值二叉树

5.2 解题思路 

本题采用递归的思想,对于作为参数传给函数的根节点,依次比较其左孩子节点的值和右孩子节点的值是否与根节点值相等,如果一颗二叉树所有非叶子节点的左右孩子节点的值都与节点本身值相等,则该二叉树为单值二叉树。

  1. 判断root是否为NULL,如果是,函数返回true。
  2. 如果root的左孩子节点root->left不为空且其值与root的值不同,函数返回false。
  3. 如果root的右孩子节点root->right不为空且其值与root的值不同,函数返回false。
  4. 如果函数走到这一步,说明root的左右孩子节点都存在且值都与root的值相同,则通过递归判断root的左子树和右子树是否为单值二叉树,如果root的左右子树都是单值二叉树,则整颗二叉树为单值二叉树,函数返回true,否则返回false。

5.3 函数代码

bool isUnivalTree(struct TreeNode* root)
{
    if(NULL == root)
    {
        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);
}

问题6. 判断两颗二叉树是否相同

6.1 问题描述

设计函bool isSameTree(struct TreeNode* p, struct TreeNode* q),判断两颗二叉树是否相同,p和q为两颗二叉树的根节点。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。图6.1和图6.2分别展示了两颗相同的二叉树和两颗不同的二叉树。

图6.1 两颗相同的二叉树
图6.2 两颗不同的二叉树

6.2 解题思路

按照前序遍历,依次比较两颗二叉树的每一个节点,两颗二叉树的结构和每个节点值均相同,则两个二叉树相同。这里采用递归的思想,先比较两颗二叉树的根节点是否相同,如果相同,在先后比较这两个二叉树的左子树和右子树是否相同,如果两颗二叉树的左右子树均相同,则这两颗二叉树相同。

  1. 判断p和q是否均为空,如果均为空,返回true。
  2. 判断两颗二叉树结构是否相同,即:判断p和q是否一个为空另一个不为空。如果一个为空另一个不为空,则两颗二叉树结构不同,函数返回false。
  3. 判断p节点和q节点值是否相同,如果不同,函数返回false。
  4. 比较两个数的左右子树是否相同,函数返回isSameTree(p->left, q->left) && isSameTree(p->right, q->right),即:两颗左右子树如果分别相同,则两个二叉树相同,函数返回true,否则两颗二叉树不同,函数返回false。

6.3 函数代码

bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
    //p和q都为空,返回真
    if(p == NULL && q == NULL)
    {
        return true;
    }

    //p和q一个为空一个不为空,返回假
    if(p == NULL || q == NULL)
    {
        return false;
    }

    //p和q都不为空,但值不一样,返回假
    if(p->val != q->val)
    {
        return false;
    }

    //递归调用,比较左右子树是否相同
    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

问题7. 判断一颗二叉树是否为对称二叉树

7.1 问题描述

给定二叉树根节点root,设计函数bool isSymmetric(struct TreeNode* root),判断二叉树是否为对称二叉树,图7.1展示了对称二叉树和非对称二叉树。

图7.1 对称二叉树和非对称二叉树

7.2 解题思路

如果一颗二叉树是对称二叉树,那么它应该满足下面的条件:

  • 两棵树的根节点值相同
  • 每棵树的右子树都与另一颗树的左子树镜像对称

综上,本题可以采用递归的思想,定义两个二叉树节点指针root1和root2,当root1向其左子树方向移动时,root2向其右子树方向移动,当root1向其右子树方向移动时,root2向其左子树方向移动。每次检查当前root1和root2节点的值是否相等,如果相等再判断左右子树是否对称。

为此,应当再定义一个子函数bool check_val(struct TreeNode* root1, struct TreeNode* root2),用于检查节点root1和root2节点值是否相等,然后检查root1的左子树与root2的右子树是否镜像对称,root1的右子树与root2的左子树是否镜像对称。

7.3 函数代码

//定义两个节点root1和root2
//root1左移,root2就右移;root1右移,root2就左移
//检查p和q的值是否相同
bool check_val(struct TreeNode* root1, struct TreeNode* root2)
{
    //两节点都为NULL,返回true
    if(root1 == NULL && root2 == NULL)
    {
        return true;
    }

    //一个节点是NULL另一个节点不是,不对称,返回false
    if(root1 == NULL || root2 == NULL)
    {
        return false;
    }

    //检查两节点值是否相同以及左右子树的镜像对称情况
    return (root1->val == root2->val) 
            && check_val(root1->left, root2->right)
            && check_val(root1->right, root2->left);
}

bool isSymmetric(struct TreeNode* root)
{
    if(NULL == root)
    {
        return true;
    }

    //检查左右子树是否镜像对称
    return check_val(root->left, root->right);  //值检查函数
}

问题8. 判断一颗二叉树是否为另一颗二叉树的子树

8.1 问题描述

给定两棵二叉树root和subRoot,设计函数bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot),判断二叉树root中是否存在子树subRoot,二叉树的一棵子树包括的某个节点和这个节点的所有后代节点。图8.1中二叉树subRoot为二叉树root的一颗子树。

图8.1 二叉树及其子树

8.2 解题思路

  • 思路:转化为子树是否相等的问题

通过前序遍历遍历二叉树的每一个节点,当root的值与subRoot的值相同时,调用函数isSameTree,判断以root节点为根节点的子树与subRoot是否为相同的二叉树,如果是相同的二叉树,函数返回true,如果不是相同的二叉树,继续比较二叉树root的剩余节点的值与subRoot的根节点值是否相同以寻找子树subRoot,直到二叉树root的所有节点均被遍历或找到子树subRoot为止。

8.3 函数代码

isSameTree函数的代码见6.3节。

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
{
    //根和子根均为空,返回true
    if(!root && !subRoot)
    {
        return true;
    }

    //两者之一为空返回假
    if(!root || !subRoot)
    {
        return false;
    }

    //树和子树根节点值相同,检查两树是否相同
    if(root->val == subRoot->val)
    {
        bool ret = isSameTree(root, subRoot);
        if(ret)
        {
            return ret;
        }
    }

    //检查左子树和右子树是否与subRoot相同
    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

问题9. 二叉树的销毁

9.1 问题描述

设计函数void BinaryTreeDestory(BTNode** root),释放创建二叉树时,为二叉树每一个节点动态申请的内存空间,并将指向根节点的指针置为NULL。

9.2 解题思路

通过后序遍历释放依次释放一颗二叉树的左子树节点、右子树节点和根节点,最后通过对二级指针root的解引用,将指向根节点的指针置为NULL。

这里采用后序遍历销毁二叉树时为了避免根节点释放后无法找到其左孩子节点和右孩子节点的问题。

9.3 函数代码

//二叉树销毁函数
void BinaryTreeDestory(BTNode** root)
{
	if (NULL == *root)
		return;

	//销毁左子树
	BinaryTreeDestory(&(*root)->left);
	//销毁右子树
	BinaryTreeDestory(&(*root)->right);
	//销毁根节点
	free(*root);

	*root = NULL;
}

问题10. 判断一颗二叉树是否为完全二叉树

10.1 问题描述

给定一个二叉树根节点root,设计函数bool BinaryTreeComplete(BTNode* root),判断该二叉树是否为完全二叉树,如果是函数返回true,否则函数返回false。

完全二叉树概念:对于一颗深度为h的二叉树,其前h-1层节点个数均达到最大值,第h层节点个数可能没有达到最大值,但从左到右是连续的。

10.2 解题思路

判断完全二叉树的解题思路与层序遍历类似,均需要借助队列来实现目的。

  1. 创建队列,让根节点入队列。
  2. 当队列不为空时,提取队列队首数据。如果队首为NULL,则检查队列中是否还存在不为NULL的数据,如果存在,那么该二叉树不是完全二叉树如果不存在,则是完全二叉树。
  3. 如果队首不是NULL,则让队首数据节点的左孩子节点和右孩子节点先后入队列。注意:对于层序遍历,孩子节点不为空才入队列,对于判断完全二叉树的问题,无论孩子节点是否为NULL,都要入队列。
图10.1 判断完全二叉树的代码逻辑

10.3 函数代码

bool BinaryTreeComplete(BTNode* root)
{
	if (root == NULL)
		return true;

	Queue q;  //创建队列
	QueueInit(&q);  //初始化队列
	QueuePush(&q, root);  //根节点入队列

	while (!QueueEmpty(&q))  //队列不为空
	{
		BTNode* front = QueueFront(&q);  //提取队首数据
		QueuePop(&q);  //删除队首数据

		if (front == NULL)
		{
			//队首为空,终止循环
			break;
		}
		else
		{
			//front的左右孩子节点无论是否为NULL,均入队列
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}

	//队首遇到空,判断队列中是否还存在不为NULL的数据
	//1.如果存在,表明不是完全二叉树
	//2.如果不存在,表明是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

 

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

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

相关文章

Fabric中的txid exists问题

Fabric 默认配置中tls证书有效期为一年,相信挖了不少的坑,我前段时间写了篇文章介绍了下解决的思路,但是自己真解决起来还是没解决问题,这种分布式企业架构太复杂。 最近有遇到一个奇怪的问题,小伙伴写的存证数据&…

plt自定义水平线和垂直线、水平区域和垂直区域

一、添加x轴y轴垂直辅助线 1、函数 axvline函数:绘制垂直线。axhline函数:绘制水平线。 2、参数 plt.axvline(x0, ymin0, ymax1, c"g", ls"--", lw2, labelNone)。axhline类似 x:垂直线在x轴上的位置。浮点数&#xf…

AcWing1227.分巧克力——学习笔记

目录 题目 代码 AC结果 思路: 一、设置全局变量 二、获取数据 三、当前大小可得到的巧克力数是否满足每个小朋友至少分到一块 四、二分法找每个小朋友可得到的最大大小 题目 1227. 分巧克力 - AcWing题库https://www.acwing.com/problem/content/descripti…

Python---函数相关知识

专栏:python 个人主页:HaiFan. 专栏简介:本专栏主要更新一些python的基础知识,也会实现一些小游戏和通讯录,学时管理系统之类的,有兴趣的朋友可以关注一下。 函数前言函数的使用函数的语法格式函数的参数函…

Python脚本集成SQLite3数据库

文章目录一、Sqlite3数据库的基本使用1.数据库是什么2.数据库分类二、正式开始使用SQLite3(Python)1.基础sql命令2.Python连接SQLite3①查询②增加③更新一、Sqlite3数据库的基本使用 1.数据库是什么 借用百科上的话,数据库是“按照数据结构…

yolo v8 解决了 v5 的问题嘛?

文章大纲 yolo v8 简介网络结构yolo v8 准确率的提升yolo v8 的速度提升参考文献与学习路径yolo v8 简介 官网: https://ultralytics.com/yolov8https://github.com/triple-Mu/YOLOv8-TensorRT详细介绍: https://learnopencv.com/ultralytics-yolov8/网络结构 yolo v8 准确率…

I.MX6ULL裸机开发笔记3:SDK烧录镜像

目录 一、获取NXP官方SDK 二、Linux安装SDK 三、SDK中相关工具 四、烧录工具 一、获取NXP官方SDK 官方网站MCIMX6ULL-EVK_i.MX6ULL评估套件_NXP 半导体 二、Linux安装SDK ./XXX.run 这里如果提示sudo ./XXX.run 找不到命令,就可以用ls -l查看当前文件夹文件的…

VVDocumenter-Xcode github README.md 中英文翻译

最近在学习 Xcode 的使用,查到有一款用于生成注释文档的插件:VVDocumenter-Xcode 进入其 github 页面之后看 README,看到两句话: 1. Goodbye World: 再不更新了? 2. 从 Xcode 8 之后Apple官方提供了插件做…

Python爬虫---爬虫介绍,实战案例

目录标题1、爬虫介绍1.1 爬虫的合法性1.2 网络爬虫的尺寸1.3 robots.txt协议1.4 http&https协议1.5 requests模块1.5.1 request库的异常2、实战案例2.1 百度页面2.2 爬取京东商品页面2.3 爬取亚马逊商品页面-更改headers2.4 百度/360搜索关键词提交-params2.5 网络图片的爬…

Nacos 报Statement cancelled due to timeout or client request

1. 问题:nacos 启动报错,启动失败,全部报错下面贴出。 2. 结论:排查结果为服务器带宽不够,mysql 查询的数量量太大,传输时间损坏在网络io上! 3. 下面开始回溯事故起因: 前期config…

2022年一年级入学小结

2022年即将过去,在这里,简单回顾一下Richard同学进入小学一学期后的成长经历和小结。先说说学校生活Richard很喜欢目前的学校,喜爱给他授课的每一位老师,也和老师和同学们相处得不错,自诩自己现在的”粉丝“蛮多。从我…

C++设计模式(6)——适配器模式

亦称: 封装器模式、Wrapper、Adapter 意图 适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。 问题 假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美…

9.框架SpringMVC

一、基本概念 Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。Spring MVC 使用 MVC 架构模式的思想,将 Web 应用进行职责解构,把一个复杂的 Web 应用划分成模型(Model&#xff09…

HashMap的使用:put、remove和get方法原理

关联项目需求进行FeatureAB上报的时候,我们使用HashSet的add方法存key值,如果key已存在,则add失败,返回false,如果key不存在,add成功,返回true。看源码中HashSet的add(E e)方法实现:…

【Git】IDEA 集成 GitHub

8、IDEA 集成 GitHub 8.1、设置 GitHub 账号 如果出现 401 等情况连接不上的,是因为网络原因,可以使用以下方式连接: 然后去 GitHub 账户上设置 token。 点击生成 token。 复制红框中的字符串到 idea 中。 点击登录。 8.2、分享工程到 GitHu…

【甄选靶场】Vulnhub百个项目渗透——项目五十五:SP-LEOPOLD v1.2(beef联动msf,脏牛提权)

Vulnhub百个项目渗透 Vulnhub百个项目渗透——项目五十五:SP-LEOPOLD v1.2(beef联动msf,脏牛提权) 🔥系列专栏:Vulnhub百个项目渗透 🎉欢迎关注🔎点赞👍收藏⭐️留言&am…

ZooKeeper-集群搭建

5)ZooKeeper 集群搭建 5.1)Zookeeper集群介绍 Leader选举: •Serverid:服务器ID 比如有三台服务器,编号分别是1,2,3。 编号越大在选择算法中的权重越大。 •Zxid:数据ID 服务器中存放的最大数据ID.值越大说明数据 越新&…

剑指offer—day1.用两个栈实现队列、包含min函数的栈

1.用两个栈实现队列 本题来源:力扣 剑指 Offer 09. 用两个栈实现队列 - 力扣(LeetCode)https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/题目描述 用两个栈实现一个队列。队列的声明如下,请实现它的两…

IP-guard如何映射到外网登录访问管理

终端安全管理(endpoint security management)是一种保护网络安全的策略式方法,它需要终端设备在得到访问网络资源的许可之前遵从特定的标准。随着企业信息化发展,终端安全管理系统需求不断扩大,相关系统软件被广泛应用。 IPguard即IP-guard&a…

线段树(重要!多加理解懒惰标记!)

基础概念: 线段(区间)[L,R] 所对应的线段树是由区间 [L,R] 及其子区间构成的二叉树(如下图所示) 线段树具有的特性: (1)线段树的叶结点为只有一个元素的区间,因此长度为…