数据结构学习分享之链式二叉树(一)

news2024/11/23 8:32:39

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:数据结构学习分享⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你了解更多数据结构的知识
  🔝🔝


在这里插入图片描述


1. 前言

在学习链式二叉树之前,大家一定要对函数栈帧的建立与销毁有一定的了解,因为链式二叉树这一块会涉及很多递归的问题,递归会不断建立栈帧,再不断销毁.理解了函数的栈帧的建立与销毁可以帮助我们理解二叉树的内容如果你对函数栈帧没有概念,请跳转函数栈帧的创建与销毁

其实普通的二叉树的增删查改没有什么意义,因为二叉树用来存储数据太复杂了,我们一般在它的基础上增加一些性质才有意义,比如说很经典点的搜索二叉树等

注:二叉树的内容开始真正上强度了!


2. 链式二叉树的结构

我们之前说过,特殊的二叉树,比如完全二叉树(堆)非常适合用数组的形式来表示,而普通的二叉树就非常适合用链式结构来表示:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//链式二叉树

typedef char BTDataType;

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

这里我们直接定义一个链式结构,里面有两个指针分别指向节点的左孩子和右孩子,data用来存储值


当然定义完二叉树的结构后,我们还应该将它初始化指向和初始化赋值以便于我们后面使用,这里我们定义一个下图一样的二叉树:

在这里插入图片描述


这里我们初始化节点的时候应该先将左右孩子指针都给置空,把所有节点初始化完成后再根据需要将节点链接起来:这里我们需要两个函数:

BTNode* BuyNode(BTDataType x)//这个函数用于为新节点开辟空间并且初始化
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	newnode->data = x;
	newnode->left = newnode->right = NULL;
	return newnode;
}

上面这个函数用于为节点开辟空间并且初始化内容

BTNode* CreatBinaryTree()//手动创建一共二叉树
{
	BTNode* nodeA = BuyNode('A');
	BTNode* nodeB = BuyNode('B');
	BTNode* nodeC = BuyNode('C');
	BTNode* nodeD = BuyNode('D');
	BTNode* nodeE = BuyNode('E');
	BTNode* nodeF = BuyNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;
	return nodeA;
}

而上面这个函数用于将初始化完成后的值修改指针指向,使他变成我们想要的二叉树.像叶子节点D,E,F在开辟空间时已经将左右孩子置空了,所以这里修改指针指向时不用再刻意置空了!


做好这些后,我们的二叉树结构就基本建立完成了,现在我们接着往下看.


3. 二叉树的遍历

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

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

我们一一来讲解:


3.1 二叉树前序遍历

我们用我们上面定义的二叉树来举例子:

在这里插入图片描述

前序遍历是先访问根节点,再访问左孩子最后有孩子,我们假设将所有的NULL(空节点)编个号,这样好理解:那么对于这个二叉树的前序遍历顺序应该是:A B D 2 3 1 C E 4 5 F 6 7.将所有空节点去掉后就得到: ABDCEF.


3.2 前序遍历代码实现及其解释

这个代码解释起来比较费劲,这里我先给出代码,再做解释:

void PreOrder(BTNode* root)//前序遍历二叉树,递归的方法(中左右)
{
	if (root == NULL)//节点为空就打印NULL后再返回
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);//要体现出我们在遍历,这里将值打印出来
	PreOrder(root->left);
	PreOrder(root->right);
}

这里我们使用了递归的手法,前序遍历时,我们将打印内容放在最前面,然后再去递归它的左右孩子,因为这个地方不好解释,所以我建议大家自己去画图理解,这里博主把我自己画的图分享给大家:主要分为两个部分,那就是递归往后走的部分和函数调用完返回的部分

在这里插入图片描述

你自己画出的递归展开图其实也就是一个二叉树的形状.我们在电脑上打印出来结果,可以看见和我们之前预想的结果是相同的:

在这里插入图片描述


3.2 二叉树中序遍历

和前序一样,我们先把中序情况的顺序写出来,再来实现代码:

2 D 3 B 1 A 4 E 5 C 6 F 7.将所有空指针去掉可以得到:DBAECF

我们再来写写中序遍历的代码:

void MidOrder(BTNode* root)//中序遍历二叉树,递归(左中右)
{
	if (root == NULL)
	{
		printf("NULL");
		return;
	}
	MidOrder(root->left);
	printf("%c ", root->data);
	MidOrder(root->right);
}

是的没错!中序遍历就是在前序遍历的基础上将打印函数放在了中间位置,这很巧妙,但是你也得画图去理解,不能只是认为中序遍历就把printf放在中间! 有了前面前序遍历画图的抛砖引玉,这里的递归图就交给你们自己来画

这里还是运行结果看看能不能和我们的猜想对上

在这里插入图片描述


3.3 二叉树的后续遍历

还是老规矩,先将后续遍历的顺序给写出来: 2 3 D 1 B 4 5 E 6 7 F C A.将空指针去掉后得到: DBEFCA

和你猜想的一样,后续遍历的代码就是将printf放在最后[狗头]

void PostOrder(BTNode* root)//后续遍历二叉树(左右中)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

到这里如果你前两个遍历都画图理解了,相信这个遍历你不画图也能参透里面的奥义了!

结果毫无疑问是能够和我们的猜想对上的:

在这里插入图片描述


4. 二叉树节点个数

二叉树节点个数我们可能会认为很简单,只需要用前序把二叉树走一步,每遇见一个非空节点就加一就可以求出节点个数,很容易写出这样的代码:

int BinaryTreeSize(BTNode* root)
{
  if(root==NULL)
  {
    return;
  }
  int count=0;

  count++;//遍历多少次非空节点就将count加几次
  BinaryTreeSize(root->left);
  BinaryTreeSize(root->right);

但是这样写有一个明显的问题,那就是count是局部变量,变量每一次调用都会创建不同的栈帧空间,也就是说每一个递归函数中的count不能被保存起来,并且我们如果在count前面加上一个static关键字将count放在静态区也是不可取的,因为当我们第一次使用我们的求二叉树节点个数的函数是没有问题的,但是当我们下一次再使用这个函数时,count值不是从0开始,而是从6开始(二叉树有六个元素),第三次调用count就从12开始. 所以这里我们修改一下代码:

void BinaryTreeSize(BTNode* root, int* p)//二叉树节点的个数,这里的p是外面main定义的变量的地址
{
	if (root == NULL)
	{
		return;
	}
	++(*p);
	BinaryTreeSize(root->left,p);
	BinaryTreeSize(root->right, p);
}

这里我们在外部定义一个变量来存储二叉树节点的个数,然后我们把变量的地址传进来就可以很好的修正刚刚的问题,这里还有一种写法,这种写法应该首先掌握:

int BinaryTreeSize2(BTNode* root)//求二叉树节点个数,直接返回节点个数
{
	return root == NULL ? 0 :
		BinaryTreeSize2(root->left) 
		+ BinaryTreeSize2(root->right) + 1;
}

这里如果节点为空就返回0,不为空就接着往后递归,代码比较抽象,还是需要我们画图来理解:

在这里插入图片描述

你们应该自己画图理解一番.


5. 二叉树叶子节点的个数

叶子节点就是左右孩子都为空的节点,首先第一步我们要想到的是,如果数为空,那么我们应该返回0因为它一个节点都没有,第二点是节点的左孩子和右孩子都为空时,我们应该返回1.当我们走完这两种情况后,剩下的情况一定是节点即不为空,并且左右孩子都不全为空,这种情况我们就再往后递归左右子树就可以了

int BinaryTreeLeafSize(BTNode* root)//求二叉树叶子节点个数(左右孩子都为空的节点)
{
	if (root == NULL)//二叉树为空的情况和遇见空节点的情况
	{
		return 0;
	}

	if (root->left == NULL && root->right == NULL)//为叶子时
	{
		return 1;
	}

	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
	//既不是叶子也不是空就返回左右节点的叶子数
}

这里值得注意的是第一步,root==NULL时,这时其实有两种情况,一种是我们数本身就是空树,第二种是我们的节点是空节点,这两种情况都应该返回0,并且其实这两种情况可以合并为一种情况

这里的递归不难理解,所以就不给大家画递归展开图了(如果你感到困难,也应该自己画一幅图)


6. 二叉树第K层的节点个数

我们还是可以像刚刚一样分析,当数为空树时,我们应该返回0,而这里当K等于1时,也就是第一层,一定为1个节点,所以返回1.当这两种情况都不满足时,那么说明root这棵树的第k层节点在子树里面,这样就可以转换成求左右子树的k-1层的节点个数!

int BinaryTreeLeaveSize(BTNode* root, int k)//返回二叉树第k层节点数
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	//root既不是空,k也不为1时,说明第k层在root的字树中,转换为求左右子树的第k-1层的节点树
	return BinaryTreeLeaveSize(root->left, k - 1) + BinaryTreeLeaveSize(root->right, k - 1);
}

这里只要理解了root既不是空,并且k也不等于1,我们就转换这个问题求左右子树的k-1层的节点数,再画递归展开图理解一下:

假设我们设K=3.

在这里插入图片描述

这里的递归用照片其实也不太好看,在这个版块大家尽量自己画图,没有画图思路的可以看看我是怎么画的,看别人的图理解起来还是很难得


7. 总结

二叉树的内容还没有结束,由于二叉树这一章的内容较多,我将一些内容放在下一节讲,下一节包括:求二叉树的高度,查找值为x的节点,二叉树的层序遍历,判断二叉树是不是完全二叉树.敬请期待!

🔎 下期预告:链式二叉树详解(二) 🔍

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

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

相关文章

【Linux】shell编程—awk编辑器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、awk编辑器1.工作流程3.常用选项 二、awk的基础用法1.输出文件中的某一列2.根据特定条件筛选数据3.按照分隔符进行切割4.在匹配到特定字符串时执行操作5.BEGIN打…

chatgpt赋能Python-pythonwxpy

Python的wxpy模块&#xff1a;一款强大的微信机器人框架 在当今数字时代&#xff0c;微信已经成为了大家日常生活中不可缺少的应用。wxpy是一款使用Python语言的微信机器人框架&#xff0c;可以帮助用户实现诸如自动回复、消息提醒、定时发送消息等自动化操作。它的易用性、强…

(5)---STM32 的时钟系统

目录 1.时钟基本概念 时钟源常见振荡器 振荡电路 晶体振荡器 RC振荡器 2.G030时钟源 3.时钟树 4.STM32CubeMX时钟树配置 1.时钟基本概念 1&#xff09; 时钟是嵌入式系统的脉搏&#xff0c;处理器内核在时钟驱动下完成指令执行&#xff0c;状态变换等动作&#xff0c; 外设部件…

基于redis客户端缓存机制实现本地缓存

文章目录 前言一、本地缓存和分布式缓存二、redis客户端缓存机制1.客户端缓存实现原理普通模式广播模式重定向模式redirect 2.优势和误区3.客户端缓存机制请求流程 三、项目实战1.引入依赖2.redis连接属性配置3.开启客户端缓存4.使用本地缓存5.测试 总结 前言 采用缓存一直是我…

VMware ESXi 6.0 U3 Final - ESXi 6 系列最终版下载

VMware ESXi 6.0 U3 Final - ESXi 6 系列最终版下载 VMware ESXi 6 Standard 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-6/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org VersionRelease NameRelease …

RLHF中的PPO算法原理及其实现

RLHF中的PPO算法原理及其实现 ChatGPT是基于InstructGPT实现的多轮对话生成式大模型。ChatGPT主要涉及到的技术包括&#xff1a; 指令微调&#xff08;Instruction-tuning&#xff09;&#xff1b;因果推断&#xff08;Causal Language Modeling&#xff09;&#xff1b;人类…

从零开始Vue3+Element Plus后台管理系统(十五)——多语言国际化vue I18n

i18n国际化的内容比较多&#xff0c;写文章的时间也用得比较长&#xff0c;从上周五开始到本周一&#xff0c;断断续续完成了。 虽然实际工作中很多项目都不需要国际化&#xff0c;但是了解国际化的用法还是很有必要的。 i18n Vue I18n 是 Vue.js 的国际化插件。它可以轻松地…

PFC-FLAC3D Coupling Examples

目录 PFC-FLAC3D Coupling Examples Punch Indentation of a Bonded Material Sleeved Triaxial Test of a Bonded Material 命令流 结果 PFC-FLAC3D Coupling Examples Punch Indentation of a Bonded Material 这个例子展示了一个粘合颗粒模型&#xff08;BPM&#xff0…

项目经历该如何写?

大家好&#xff0c;我是帅地。 这不春招来了吗&#xff0c;帮训练营的帅友们修改了很多简&#xff0c;其中问题最多的就是项目经历 专业技能这块了&#xff0c;特别是项目经历这块&#xff0c;很多人写了一大堆描述功能描述&#xff0c;但是自己具体干了什么却没怎么写&#…

研发工程师玩转Kubernetes——使用Deployment进行多副本维护

多副本维护是指&#xff0c;对一组在任何时候都处于运行状态的 Pod 副本的稳定集合进行维护。说的直白点&#xff0c;就是保证某种的Pod数量会被自动维持——增加了该类Pod会自动删除多余的&#xff0c;减少了该类Pod会自动新增以弥补&#xff0c;以保证Pod数量不变。 Kubernet…

day37_Tomcat_Maven

今日内容 一、Maven 二、Tomcat 一、Maven 1.1 引言 项目管理问题 项目中jar包资源越来越多&#xff0c;jar包的管理越来越沉重。 繁琐 要为每个项目手动导入所需的jar&#xff0c;需要搜集全部jar 复杂 项目中的jar如果需要版本升级&#xff0c;就需要再重新搜集jar 冗余 相…

基于Spring-动态调整线程池阻塞队列长度

最近在做一个动态线程池的组件&#xff0c;遇到了关于阻塞队列长度刷新的问题,所以记录下来&#xff0c;很有意思 我们都知道常用线程池分为二类&#xff0c;Spring-ThreadPoolTaskExecutor和JDK-ThreadPoolExecutor的&#xff0c;当然了Spring也是基于JDK做一步封装&#xff0…

​数据库原理及应用上机(实验四 SQL连接查询)

✨作者&#xff1a;命运之光 ✨专栏&#xff1a;数据库原理及应用上机实验 目录 ✨一、实验目的和要求 ✨二、实验内容及步骤 ✨三&#xff0e;实验结果 ✨四、实验总结 &#x1f353;&#x1f353;前言&#xff1a; 数据库原理及应用上机实验报告的一个简单整理后期还会不…

Zerto 10.0 发布 - 勒索软件防护、灾难恢复和多云移动性的统一解决方案

Zerto 10.0 发布 - 勒索软件防护、灾难恢复和多云移动性的统一解决方案 请访问原文链接&#xff1a;https://sysin.org/blog/zerto-10/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 携手 ZERTO 提升勒索软件保护与灾难恢复水…

Python异常处理

1. 异常概述 在程序运行过程中&#xff0c;经常会遇到各种错误&#xff0c;这些错误称为“异常”。这些异常有的是由于开发者一时疏忽将关键字敲错导致的&#xff0c;这类错误多数产生的是SyntaxError:invalid syntax&#xff08;无效的语法&#xff09;&#xff0c;这将直接导…

JVM笔记

Java中对象一定分配在堆空间上吗&#xff1f;判断一个对象是否还活着GCgc频繁 Java中对象一定分配在堆空间上吗&#xff1f; 逃逸分析&#xff1a;分析对象动态作用域&#xff0c;当一个对象在方法中被定义后&#xff0c;它可能被外部方法所引用&#xff0c;例如作为调用参数传…

Redis6.2.5安装布隆过滤器BloomFilter

最近学习需要用到布隆过滤器&#xff0c;所以去RedisLabsModules下载RedisBloom插件&#xff0c;简单介绍一下安装的过程&#xff0c;首先需要先安装好Redis&#xff0c;建议使用Redis6以上版本&#xff0c;Redis安装教程查看https://smilenicky.blog.csdn.net/article/details…

什么是客户自助服务门户及其搭建方法

随着信息技术的快速发展&#xff0c;越来越多的企业开始转向以客户为中心的服务模式&#xff0c;而客户自助服务门户&#xff08;Customer Self-Service Portal&#xff09;则成为了重要的服务方式。它可以让客户在不需要人工干预的情况下&#xff0c;自行解决问题&#xff0c;…

chatgpt赋能Python-python_ai建模

用Python构建AI模型&#xff1a;一步步解析 随着人工智能技术的发展和普及&#xff0c;越来越多的企业开始寻找高效可靠的AI建模技术来提高业务水平和竞争力。Python作为一种强大的编程语言和开发工具&#xff0c;在AI建模领域也扮演着重要的角色。本文将介绍Python AI建模的基…

chatgpt赋能Python-python_ai下载

Python AI 下载&#xff1a;实现自动化数据处理的利器 介绍 Python作为一种脚本语言&#xff0c;凭借其简洁灵活的语法、强大的库支持和生态系统&#xff0c;成为了今天最流行的编程语言之一。在人工智能领域&#xff0c;Python也是最常用的语言之一&#xff0c;因为它的开发…