【数据结构】链式二叉树的实现和思路分析及二叉树OJ

news2024/11/16 12:55:55

【数据结构】链式二叉树的实现和思路分析及二叉树OJ

🔥个人主页大白的编程日记

🔥专栏数据结构


文章目录

  • 【数据结构】链式二叉树的实现和思路分析及二叉树OJ
    • 前言
    • 一.链式二叉树的定义及结构
    • 二.链式二叉树的遍历
      • 2.1前序遍历
      • 2.2中序遍历
      • 2.3后序遍历
      • 2.4层序遍历
      • 三.链式二叉树功能函数
      • 3.1节点个数
      • 3.2第k层节点的个数
      • 3.3查找值为x的节点
      • 3.4树的销毁
    • 四.二叉树OJ
      • 4.1二叉树遍历
      • 4.2左子叶之和
    • 后言

前言

哈喽,各位小伙伴大家好!上期讲的是用顺序表实现二叉树。今天咱们用链表的方式实现我们的二叉树。也就是链式结构。话不多说,咱们进入正题!向大厂冲锋!

一.链式二叉树的定义及结构

  • 树的定义
    我们链式二叉树用结构体定义。结构体内包含节点的数据。然后还有指向左右孩子节点的结构体指针
typedef int DataType;
typedef struct BinaryTreeNode
{
	DataType val;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
  • 节点的创建

节点的创建我们需要malloc一个结构体。检查节点是否开辟成功。然后将节点数据赋值为X即可。再将左右指针指向空。最后返回开辟好的节点。

BTNode* BuyNode(int x)//创建树的节点
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	node->a = x;
	node->left = node->right = NULL;
	return node;
}
  • 树的创建
    为了方便我们后面使用。我们先开辟一个树出来。
    我们只需要创建好节点。然后修改节点的指针使其成一棵树即可。
BTNode* CreatTree()//建树
{
	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;
}

这样一颗树二叉树就构建好了。

二.链式二叉树的遍历

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

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

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

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为
根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

任何一颗树都要分成根 左子树 右子树去按顺序遍历。
左右子树的访问又看成一颗树继续按照顺序去遍历。

2.1前序遍历

前序遍历访问顺序就是 根 左子树 右子树。
然后子树继续按照 根 左子树 右子树访问。
那我们先把一棵树的按照根 左子树 右子树拆分

  • 代码实现
    我们前序遍历一颗树时分为两种情况
    一:树为空。那就不用访问了直接return结束。
    二:树不为空。那就先访问根节点(这里我们直接打印)。然后还需要继续左右子树前序遍历,那我们就递归函数解决。
void PrevOrder(BTNode* p)//前序遍历
{
	if (p == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", p->a);
	PrevOrder(p->left);
	PrevOrder(p->right);
}
  • 逻辑分析
    逻辑上我们就是将一颗树的前序遍历分为根的访问和左子树的遍历和右子树。
    左右子树的遍历又看成一棵树的前序遍历。所以我们递归左右子树即可。



  • 逻辑过程

我们都知道函数的调用需要在栈上开辟栈帧。
但是需要注意的是左子树开辟的栈帧函数调用结束销毁后
仍然存在内存中,调用右子树开辟的栈帧是重复利用左子树的栈帧。
所以函数的栈帧会重复利用。

2.2中序遍历

以此类推,中序就先递归左子树 再访问根 再递归右子树即可。

void InorOrder(BTNode* p)//中序遍历
{
	if (p == NULL)
	{
		printf("N ");
		return;
	}
	InorOrder(p->left);
	printf("%d ", p->a);
	InorOrder(p->right);
}

2.3后序遍历

以此类推,中序就先访问根 递归左子树 再递归右子树即可。

void PostOrder(BTNode* p)//后序遍历
{
	if (p == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(p->left);
	PostOrder(p->right);
	printf("%d ", p->a);
}
  • 验证

2.4层序遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

  • 思路分析

上一层带下一层即可完成层序遍历。

  • 代码实现
void TreeLevelOrder(BTNode* p)//层序遍历
{
	Queue pq;
	QueueInit(&pq);//初始化队列
	if(p)
	   QueuePush(&pq, p);//第一层入队列
	while (!QueueEmpty(&pq))//队列不为空
	{
		BTNode* head = QueueFron(&pq);//取出队头数据
		QueuePop(&pq);//出队列
		printf("%d ", head->a);//访问
		if (head->left)
		{
			QueuePush(&pq, head->left);//左孩子入队列
		}
		if (head->right)
		{
			QueuePush(&pq, head->right);//右孩子入队列
		}
	}
	QueueDestroy(&pq);//销毁队列
}

三.链式二叉树功能函数

我们链式二叉树的实现不只是实现遍历而已。
我们还需要对二叉树实现求节点个数,树的高度等等。

3.1节点个数

  • 遍历计数

我们很容易想到走一个遍历然后不为空用size++记录个数即可。
这确实是一种方法。那具体代码如何实现呢?

int TreeSize(BTNode* p)//树的节点个数
{
	int size;
	if (p == NULL)
	{
		return 0;
	}
	size++;
	TreeSize(p->left);
	TreeSize(p->right);
	return  size;
}

这样写对吗?不对因为size是局部变量。每次函数调用size都会置为0.
这样就不能把节点个数累加起来。size之会累加第一次。

那我们是不是把他static改成静态让他的生命周期是全局的
这样每次size++都是同一个size就可以了呢?

int TreeSize(BTNode* p)//树的节点个数
{
	static int size=0;
	if (p == NULL)
	{
		return 0;
	}
	size++;
	TreeSize(p->left);
	TreeSize(p->right);
	return  size;
}


我们发现结果确实是6。那我在调用一次呢?

我们发现第二次调用是12。为什么呢?因为局部静态变量只会初始化一次。
所以第二次调用6+6就是12.

int size;
int TreeSize(BTNode* p)//树的节点个数
{
	if (p == NULL)
	{
		return 0;
	}
	size++;
	TreeSize(p->left);
	TreeSize(p->right);
	return  size;
}

那我们只能用全局的size然后每次函数调用前都要手动置0.

  • 分治递归
    我们可以用递归的思想把大问题拆分成小问题解决。
int TreeSize(BTNode* p)//树的节点个数
{
	if (p == NULL)
	{
		return 0;
	}
	return  TreeSize(p->left)+TreeSize(p->right)+1;
}

3.2第k层节点的个数

现在我们要求树的第k层节点的个数。我们该怎么求呢?
还是用递归的思想。

把问题转化为下一层第k-1层的递归求解即可。

int TreeLevelKSize(BTNode* p, int k)//第k层的节点
{
	if (p == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return TreeLevelKSize(p->left, k - 1)+TreeLevelKSize(p->right, k - 1);
}

3.3查找值为x的节点

现在我们要查找值为x的节点。一棵树可能有多个节点值为x。我们就返回找到的第一个节点即可。

利用递归分治的思想。将一棵树x节点的查找分为根节点 左子树 右子树的查找即可。

  • 代码实现
BTNode* TreeFind(BTNode* p, int x)//查找值为k的节点
{
	if (p == NULL)
	{
		return NULL;
	}
	if (p->a == x)
	{
		return p;
	}
	BTNode* ret1 = TreeFind(p->left, x);
	BTNode* ret2 = TreeFind(p->right, x);
	return ret1 == NULL ? ret2 : ret1;
}

我们这样写对吗?
其实也算对。但是这样有小问题就是不管左子树存不存在x节点。都会去再查找右子树。这样就效率不太高。

BTNode* TreeFind(BTNode* p, int x)//查找值为k的节点
{
	if (p == NULL)
	{
		return NULL;
	}
	if (p->a == x)
	{
		return p;
	}
	BTNode* ret1 = TreeFind(p->left, x);
	if (ret1 != NULL)
	{
		return ret1;
	}
	BTNode* ret2 = TreeFind(p->right, x);
	if (ret2 != NULL)
	{
		return ret2;
	}
	return NULL;
}

所以我们最好对左子树的返回值检查一下。
如果不为空说明找到。直接return返回节点即可。

3.4树的销毁

那树如何销毁呢?

把树的销毁看成根的销毁 左右子树的销毁。
左右子树又是树的销毁 递归即可。

void TreeDestroy(BTNode* p)//树的销毁
{
	if (p == NULL)
	{
		return ;
	}
	TreeDestroy(p->left);//销毁左子树
	TreeDestroy(p->right);//销毁右子树
	free(p);//销毁根节点
}
  • 判断完全二叉树
    现在我们要判断一棵树是否时完全二叉树如何判断呢?

    我们只需要走一个层序遍历。然后出队列时孩子节点入队列即可。
  • 代码实现
bool FullTree(BTNode* p)//判断是否满二叉树
{
	Queue pq;
	QueueInit(&pq);
	if (p)
		QueuePush(&pq, p);
	while (!QueueEmpty(&pq))//入队列
	{
		BTNode* head = QueueFron(&pq);
		QueuePop(&pq);//出队列
		if (head == NULL)
		{
			break;
		}
		QueuePush(&pq, head->left);//左孩子入队列
		QueuePush(&pq, head->right);//右孩子入队列
	}
	while (!QueueEmpty(&pq))
	{
		BTNode* head = QueueFron(&pq);
		QueuePop(&pq);
		if (head)//找是否有非空节点
		{
			QueueDestroy(&pq);
			return false;
		}
	}
	QueueDestroy(&pq);
	return true;
}

四.二叉树OJ

4.1二叉树遍历

  • 题目
    二叉树遍历
  • 思路分析

这里我们还是用递归的方式。
根据前序遍历的思想构建树。然后走中序遍历即可。

  • 代码实现
#include <stdio.h>
typedef char DataType;
typedef struct BinaryTreeNode {
    DataType a;
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
} BTNode;
void InorOrder(BTNode* p)//中序遍历
{
    if (p == NULL)
    {
        return;
    }
    InorOrder(p->left);
    printf("%c ", p->a);
    InorOrder(p->right);
}
BTNode* CreatTree(char* p, int* i) //构建树
{ //前序遍历
    if (p[*i] == '#')//不为空
    {
        (*i)++;
        return NULL;
    }
    BTNode* ret = (BTNode*)malloc(sizeof(BTNode));//创建一个节点
    if (ret == NULL)
    {
        perror("malloc fali");
        exit(1);
    }
    ret->a = p[(*i)++];//赋值
    ret->left = CreatTree(p, i);//左节点
    ret->right = CreatTree(p, i);//右节点
    return ret;//返回节点

}
int main() {
    char s[100];
    scanf("%s", &s);
    int i = 0;
    BTNode* ret = CreatTree(s, &i);//构建二叉树
    InorOrder(ret);//中序遍历
    return 0;
}

4.2左子叶之和

  • 题目
    左子叶之和

  • 思路分析
    在这里插入图片描述
    这里我们还是用递归的思想将树的左子叶之和分为
    左子树左子叶+右子树左子叶之和即可。

  • 代码实现

int sumOfLeftLeaves(struct TreeNode* root){
    if(root==NULL)
    {
        return 0;
    }
    if(root->left&&(root->left->left==NULL&&root->left->right==NULL))
    {
        return root->right==NULL?root->left->val:sumOfLeftLeaves(root->right)+root->left->val;
    }
    return sumOfLeftLeaves(root->left)+sumOfLeftLeaves(root->right);
}

后言

这就是链式二叉树的实现以及二叉树OJ。这是数据结构中比较难也是重点内容。
大家一定要好好消化。今天就分享到这。感谢各位的耐心垂阅!咱们下期见!拜拜~

在这里插入图片描述

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

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

相关文章

《LeetCode热题100》---<双指针篇四道②>

本篇博客讲解LeetCode热题100道双指针篇中的 第三道&#xff1a;三数之和&#xff08;中等&#xff09; 第四道&#xff1a;接雨水&#xff08;困难&#xff09; 第三道&#xff1a;三数之和&#xff08;中等&#xff09; 法一&#xff1a;暴力枚举&#xff08;三重循环&#x…

Java学习Day18:基础篇8

多态 基本信息&#xff1a; 应用场景&#xff1a; 可以把子类对象赋值给父类对象&#xff0c;实现多态从而使用同一种方法&#xff1b; 多态中调用成员的特点 1.调用成员变量都看左边 调用成员变量:编译看左边&#xff0c;运行也看左边 编译看左边: javac编译代码的时候&a…

【BSV生态亮点】体育进入区块链时代:波兰奥委会与Zetly建立战略伙伴关系

​​发表时间&#xff1a;2024年7月18日 波兰奥运委员会和Zetly宣布正式建立战略伙伴关系。这一合作是波兰将现代技术、数字资产和创新解决方案整合到波兰体育中的重要一步&#xff0c;将把波兰体育产业带入数字时代。 Zetly是一个非常活跃的数字平台&#xff0c;它将体育与区…

3.6.锚框

锚框 ​ 13.4. 锚框 — 动手学深度学习 2.0.0 documentation (d2l.ai) ​ 一类目标检测算法是基于锚框的&#xff0c;步骤如下&#xff1a; ​ 使用多个被称为锚框的区域(边缘框)&#xff0c;预测每个锚框里是否含有关注的物体&#xff0c;如果有&#xff0c;则预测从这个锚…

双指针-【3,4,5,6,7,8】

第三题&#xff1a;快乐数 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/happy-number/算法思想&#xff1a; 1.每个…

jdk版本管理利器-sdkman

1.什么是sdkman&#xff1f; sdkman是一个轻量级、支持多平台的开源开发工具管理器&#xff0c;可以通过它安装任意主流发行版本&#xff08;例如OpenJDK、Kona、GraalVM等等&#xff09;的任意版本的JDK。通过下面的命令可以轻易安装sdkman: 2.安装 curl -s "https://…

IFM易福门LR3000LR3300液位传感器操作说明

IFM易福门LR3000LR3300液位传感器操作说明

使用ssh-remote连接远程vscode运行yolo项目时的一点坑

使用ssh-remote连接远程vscode运行yolo项目时的一点坑 1.坑1 因为我是直接下载的release包&#xff0c;然后运行 pip install -e .来下载依赖的&#xff0c;那么这个时候需要使用YOLO时都需要在下载的release文件的目录下的py文件才能生效 比方说我下载的yolov8(ultralytic…

SpringBoot 快速上手

1. 环境准备 ⾃检Idea版本: 社区版: 2021.1 -2022.1.4 专业版: ⽆要求 如果个⼈电脑安装的idea不在这个范围, 需要卸载重新安装. Idea 卸载参考: https://blog.csdn.net/qq_19072921/article/details/126408402 (⼀定要删除注册表) 2. Maven 2.1 什么是Maven 官⽅…

Stable Diffusion 模型的安装和使用

一、SD模型简介 Stable Diffusion的绘图风格主要由模型来控制&#xff0c;基础模型主要由二次元图片训练获得&#xff0c;所以在不安装其他模型的情况下&#xff0c;只能生成二次元风格的图像。 模型能够有效地控制生成的画风和内容。 常用的模型网站有&#xff1a; Stable D…

Taro 框架中使用iconfont 阿里巴巴矢量图标 class版

想必大家都知道这个图标库的 图标还是很多的‘ 大家应该都也用过 最进开发 Taro nut-ui 的H5 移动端的时候 我发现一些图标我在组件库中找不到 我看到了nut-ui 中还有另一种使用方法 这个叫做 自定一图标 、 我来用实际操作 代码 来介绍一下 这个怎么使用 我总结文章 …

docker环境安装kafka/Flink/clickhouse镜像

1、安装Kafka服务 1、将一下三个tar文件复制到ubuntu指定目录下 2、进入到/home/cl/app目录&#xff0c;使用docker命令加载tar镜像文件 # cd /home/cl/app # docker load -i kafka.tar # docker load -i kafka-manager.tar # docker load -i kafka-zookeeper.tar3、查看d…

MySQL基础练习题11-换座位

题目&#xff1a;交换每两个连续的学生的座位号。如果学生的数量是奇数&#xff0c;则最后一个学生的id不交换。按 id 升序 返回结果表。 准备数据 分析数据 方法一&#xff1a;利用power函数对id进行交换&#xff0c;得出的答案只有0或1 第一步&#xff1a;用power()函数将…

HDBaseT远距离无压缩传输系统源头厂家

HDBaseT双绞线延长器是一款集成HDBaseT的远距离高清信号无压缩、无延时传输器&#xff0c;HDMI信号从接收端输出&#xff0c; 信号分辨率高达4Kx2K可以通过单根CAT5/CAT6网线将信号长距离传输高清无压缩音视频信号&#xff0c; 采用单根网线最远可传输70/100米&#xff0c; …

Linux 安装 nacos 2.4.0

参看&#xff1a;Nacos 快速开始 2.4.0 版本是Nacos2.X的又一个功能性版本&#xff0c;此版本的更新主要在 Nacos 的安全性、扩展功能和改进用户体验方面的持续努力&#xff0c;为用户提供了更安全、更灵活的服务管理平台。版本主要的功能如下&#xff1a; 增强安全性&#xf…

Duix AI 太上瘾,让我熬夜体验的AI女友

✨点击这里✨&#xff1a;&#x1f680;原文链接&#xff1a;&#xff08;更好排版、视频播放、社群交流、最新AI开源项目、AI工具分享都在这个公众号&#xff01;&#xff09; Duix AI 太上瘾&#xff0c;让我熬夜体验的AI女友 开启 Duix AI 女友的奇妙之旅_ Hi&#xff0c;这…

C++:类和对象2

1.类的默认成员函数 默认成员函数就是用户没有显示实现编译器会自动生成的成员函数称为默认成员函数。一个类&#xff0c;我们在不写的情况下编译器会默认生成6个默认成员函数&#xff0c;分别是构造函数&#xff0c;析构函数&#xff0c;拷贝构造函数&#xff0c;拷贝赋值运算…

前缀和与差分大总结!!!C++

学了忘忘了学o(╥﹏╥)o 题源acwing 讲解前缀和一维&#xff0c;用于序列二维&#xff0c;用于矩阵 讲解差分什么是差分数组&#xff1f;一维差分数组二维差分数组 题目一&#xff1a;前缀和题目二&#xff1a;子矩阵的和题目三&#xff1a;差分题目四&#xff1a;差分矩阵 讲…

案例分享-国外轻松感UI设计赏析

国外UI设计倾向于采用简洁的布局、清晰的排版和直观的交互方式&#xff0c;减少用户的认知负担&#xff0c;从而营造出轻松的使用体验。这种设计风格让用户能够快速找到所需信息&#xff0c;降低操作难度&#xff0c;提升整体满意度。 在注重美观的同时&#xff0c;更加重视用户…

便携移动工作站,端侧 AI 大模型设备折腾笔记:ROG 幻 X 和 4090 扩展坞

为了本地测试和开发更丝滑&#xff0c;最近入手了一套新设备 ROG 幻 X Z13 和 ROG XG Mobile 4090 扩展坞。 基于这套设备&#xff0c;我搭了一套 Windows x WSL2 x CUDA 的开发环境。分享一下折腾记录&#xff0c;或许对有类似需求的你也有帮助。 写在前面 最近因为各种事情…