【数据结构】二叉树的模拟实现

前言:前面我们学习了堆的模拟实现,今天我们来进一步学习二叉树,当然了内容肯定是越来越难的,各位我们一起努力!

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:数据结构 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


什么是树

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。光看文字可能大家理解不了,我们看下图。在这里插入图片描述


树的相关概念

  1. 节点的度:一个节点含有的子树的个数称为该节点的度。 如上图:A的为3。
  2. 叶节点或终端节点:度为0的节点称为叶节点; 如上图:F、G、H、I…等节点为叶节点。
  3. 非终端节点或分支节点:度不为0的节点; 如上图:B、C、D…等节点为分支节点.
  4. 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。 如上图:A是B的父节点。
  5. 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点。 如上图:B是A的孩子节点。
  6. 兄弟节点:具有相同父节点的节点互称为兄弟节点。 如上图:B、C是兄弟节点。
  7. 树的度:一棵树中,最大的节点的度称为树的度。 如上图:树的度为5。
  8. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
  9. 树的高度或深度:树中节点的最大层次。如上图:树的高度为4。
  10. 堂兄弟节点:双亲在同一层的节点互为堂兄弟。如上图:G、G互为兄弟节点 。
  11. 节点的祖先:从根到该节点所经分支上的所有节点。如上图:A是所有节点的祖先。
  12. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。
  13. 森林:由m(m>0)棵互不相交的树的集合称为森林。

什么是二叉树

二叉树是一种常见的树型数据结构,由若干个节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树有以下特点(如图所示):

  1. 每个节点最多有两个子节点,且子节点的位置是固定的。
  2. 左子节点在父节点的左边,右子节点在父节点的右边。
  3. 二叉树的子树也是二叉树。
  4. 二叉树的每个节点都有一个值,可以是任意类型的数据。
    在这里插入图片描述

讲完了二叉树的基本性质,我们开始模拟实现二叉树吧!

二叉树的基本功能

  1. 前序遍历–根、左、右 – 深度优先遍历
  2. 计算树节点。
  3. 中序遍历–左、根、右
  4. 计算叶子节点
  5. 树的高度
  6. 求第k层结点的个数
  7. 二叉树查找值为x的结点
  8. 通过前序遍历的数组构建二叉树。
  9. 销毁二叉树
  10. 层序遍历 – 广度优先遍历
  11. 判断二叉树是否是完全二叉树

二叉树的初始化(手搓二叉树)

思路导读:由于通过数组创建二叉树比较难,我们先放在博客的后面在去讲解,但是我们又需要二叉树怎么办,那就手搓一个。
代码演示:

typedef int BTDataType;

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

}TreeNode;

TreeNode* BuyNode(int x)
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);
	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}


TreeNode* CreateTree()//创建二叉树
{
	TreeNode* node1 = BuyNode(1);
	TreeNode* node2 = BuyNode(30);
	TreeNode* node3 = BuyNode(2);
	TreeNode* node4 = BuyNode(71);
	TreeNode* node5 = BuyNode(99);
	TreeNode* node6 = BuyNode(11);
	TreeNode* node7 = BuyNode(21);

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

创建好后的树的结构如下图所示
在这里插入图片描述


二叉树的前序遍历

思路导读:二叉树的前序遍历指先遍历二叉树的根,左子树,在遍历右子树。我们可以先画个图来模拟一下它遍历的顺序如下图:
在这里插入图片描述
既然图都画出来了,我们肯定思路都大致清晰了,那用什么方式去遍历呢?循环还是什么?今天我们要讲的方式是递归解决二叉树的遍历,这种是目前比较简单的方式。
代码实现:对于刚刚接触二叉树的友友们可能还不能理解这个递归的方式,没事可以看下图的递归展开图帮助你进一步理解

void PrevOrder(TreeNode* root)//前序遍历--根、左、右 -- 深度优先遍历
{
	if (root == NULL)
	{
		
		return NULL;
	}
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

在这里插入图片描述
运行结果:在这里插入图片描述


二叉树的中序遍历

思路导读:二叉树的中序遍历指先遍历左子树,再遍历根,最后遍历右子树。这个就不为大家再画递归展开图了,看代码!
代码演示

void InOrder(TreeNode* root)//中序遍历--左、根、右
{

	if (root == NULL)
	{

		return NULL;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

运行结果在这里插入图片描述


计算树节点

思路导读:我们要想计算树节点的个数,我们只需要将其左子树,右子树,还有根的值全部算出有多少即可,我们只需通过像前序遍历的思想来计算。如果不懂的话可以看下面的递归展开图(博主就画了前几步)。在这里插入图片描述
代码实现:

int TreeSize(TreeNode* root)//计算树节点
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->right) + TreeSize(root->left) + 1;

}

计算叶子节点

思路导读:我们可以通过遍历该树的左子树和右子树,如果左子树和右子树同时为NULL我们就让它返回1,以此类推,我们可以通过像前面一样的方式遍历二叉树达到计算叶子节点的效果(部分递归展开图)。
在这里插入图片描述
代码实现:

int TreeLeafSize(TreeNode* root)//计算叶子节点
{
	if (root == NULL)
	{
		return 0;
	}
	if((root->left)== NULL &&(root->right) == NULL)
	{
		return 1;
	}
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}


计算树的高度

思路导读:计算树的高度我们可以通过比较它的左子树和右子树找出其树高度中较大的那个然后加上一即可算出来这个树的高度,怎么比较呢?那我们就可以通过利用fmax这个函数来比较其找到最大值。
(部分递归展开图如下)在这里插入图片描述
代码实现:

int TreeHight(TreeNode* root)//树给高度
{
	if (root == NULL)
	{
		return 0;
	}
	//fmax函数,它是C语言(C99)中的一个内置函数,用于比较两个浮点数的大小并返回较大值。
	return fmax(TreeHight(root->left), TreeHight(root->right)) + 1;
}

求第k层节点的个数

思路导读:要想求第k层节点的个数我们需要将该层中左子树和右子树的位置分别记录下来,然后将其相加就是该层的个数。那么另一个问题来了,我们如何找到是这一层呢?我们可以通过每让它往下走一层时,就让k-1,依次递归,当k完全等于1的时候说明已经到了这一层,再返回1即可。(递归展开图如下)
在这里插入图片描述
代码实现:

int TreeLevelK(TreeNode* root, int k)//求第k层结点的个数
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;

	

二叉树查找值为x的节点

思路导读:我们通过前面写了这么多的二叉树的接口,这里我们可以想到,我们先查左子树中是否有相同的,然后我们再去查看右子树中是否有相同的,如果左子树找到了就将该值返回即可。(部分递归展开图如下)
在这里插入图片描述
代码实现:

TreeNode* TreeFind(TreeNode* root, BTDataType x)//二叉树查找值为x的结点
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	} 
	TreeNode* cur = TreeFind(root->left,x);//先找左子树,通过指针记录,防止找到了接着往下走
	if (cur)
	{
		return cur;
	}
	TreeNode* cur1 = TreeFind(root->right, x);//再找右子树,同理指针记录
	if (cur1)
	{
		return cur1;
	}
	return NULL;
}

通过前序遍历的数组构建二叉树

我们先假定传来的数组为:char arr[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };
该’#'代表NULL,因此我们先画出其前序遍历的展开图(如下):
在这里插入图片描述
然后我们只需要像前序遍历一样,将其按照根、左子树、右子树一样依次开辟结点赋值,此时我们要注意的是一定要使用指针,防止在递归的过程中其值不会变化。
代码实现如下:

TreeNode* TreeCreate2(char* a, int* pi)//通过前序遍历的数组构建二叉树
{
	if (*(a + *pi) =='#')
	{
		(*pi)++;
		return NULL ;
	}
	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	root->data = *(a + (*pi)++);
	root->left = TreeCreate2(a, pi);
	root->right = TreeCreate2(a, pi);
	return root;
}

运行结果:
在这里插入图片描述


二叉树的销毁

思路导读:二叉树的销毁可以像二叉树的后序遍历一样先左子树、右子树
代码实现:

void DestoryTree(TreeNode* root)//销毁
{
	if (root == NULL)
	{
		return NULL;
	}
	DestoryTree(root->left);
	DestoryTree(root->right);
	free(root);

}

层序遍历 – 广度优先遍历

思路导读:我们知道二叉树的一个特性,每一个节点都有俩个子节点,因此我们在可以充分的利用这个特性来实现我们层序遍历,我们可以模拟实现一个队列,让二叉树的根先存进去队列,然后打印其数据,就打印了第一层的数据,此时我们要打印第二层的数据,我们只需要将根的左子树和右子树在分别存入队列,在第二次循环中在依次打印即可实现层序遍历了。记住一定在队列中存的是二叉树根的地址而不是值(如下图所示)在这里插入图片描述
代码实现:

void levelorder(TreeNode* root)//层序遍历 -- 广度优先遍历
{
	QNode q1;
	QueueInit(&q1);
	// TreeHight(root);
	if (root)
	{
		//QueuePush(Queue * pq, QDataType x)
		QueuePush(&q1, root);
	}
	int level = 1;
	while (!QueueEmpty(&q1))
	{
		while (level--)
		{
			TreeNode* front = QueueFront(&q1);//将头元素地址保存在二叉树中
			QueuePop(&q1);
			printf("%c ", front->data);
			if (front->left)
			{
				QueuePush(&q1, front->left);
			}
			if (front->right)
			{
				QueuePush(&q1, front->right);
			}
		}
		level = QueueSize(&q1);
		printf("\n");
	}
	QueueDestroy(&q1);

测试函数:

void Test1()
{
	TreeNode* root = CreateTree1();
	char arr[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };
	int i = 0;
	levelorder(TreeCreate2(arr, &i));
}

运行结果:在这里插入图片描述


判断是否是完全二叉树

思路导读:我们可以像前面一样,让它们依次层次入队列,如果在入队列的过程中就碰到了NULL,就说明其不是完全二叉树,然后再将最后一层中队列的元素依次出队列,如果出队列的途中也碰到了NULL也就说明其不是完全二叉树。如果都没碰到则是完全二叉树。
在这里插入图片描述
代码实现

bool TreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front == NULL)
			break;
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	// 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🫵🫵🫵 祝各位接下来好运连连 💞

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

14 v-model绑定输入框

概述 v-model用于实现双向数据绑定,使用v-model绑定输入框是Vue3中最常见的用法之一。 比如,在制作登录界面的时候,我们会使用v-model绑定用户名和密码,这里的用户名和密码都是输入框。 基本用法 我们创建src/components/Demo…

nodejs+vue+微信小程序+python+PHP影片数据爬取与数据分析-计算机毕业设计推荐

管理页面:管理员和用户都可以登录。通过输入账号和密码后,校验无误后方可进入对应的主界面,管理员可对用户使用的权限管理,以及对网站信息进行管理[9]。  影片数据爬取与数据分析分为两个部分,即管理员和用户。该系统…

C/C++编程中的算法实现技巧与案例分析

C/C编程语言因其高效、灵活和底层的特性,被广大开发者用于实现各种复杂算法。本文将通过10个具体的算法案例,详细探讨C/C在算法实现中的技巧和应用。 一、冒泡排序(Bubble Sort) 冒泡排序(Bubble Sort)是一…

[Toolschain cpp ros cmakelist python vscode] 记录写每次项目重复的设置和配置 不断更新

写在前面 用以前的设置,快速配置项目,以防长久不用忘记,部分资料在资源文件里还没有整理 outline cmakelist 复用vscode 找到头文件vscode debug现有代码直接关联远端gitros杂记repo 杂记glog杂记 cmakelist 复用 包含了根据系统路径找库…

各种不同语言分别整理的拿来开箱即用的8个开源免费单点登录(SSO)系统

各种不同语言分别整理的拿来开箱即用的8个开源免费单点登录(SSO)系统。 单点登录(SSO)是一个登录服务层,通过一次登录访问多个应用。使用SSO服务可以提高多系统使用的用户体验和安全性,用户不必记忆多个密…

学习Java第74天,Ajax简介

什么是ajax AJAX Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。 AJAX 不是新的编程语言,而是一种使用现有标准的新方法。 AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页…

限流原理与实践:固定窗口、滑动窗口、漏桶与令牌桶解析

方案一、固定窗口限流算法 这里我们通过一个 demo 来介绍固定窗口限流算法。 创建一个 FixWindowRateLimiterService 类。 Service public class FixWindowRateLimiterService {Resourceprivate StringRedisTemplate stringRedisTemplate;private static final DefaultRedisSc…

HCIP —— BGP 基础实验

实验拓扑: 实验要求: 1.所有设备上均有环回接口 2.R1属于AS 100 ,R2-R4 属于AS 200 ,R5 属于AS 300 3.R2 - R4 属于同一个area ,运行OSPF。 4.全网通过运行BGP实现网络互通。 实验步骤: 1.配置 IP地址…

Node.js使用Express框架写服务端接口时,如何将接口拆分到不同文件中

项目目录结构说明: node.js连接mysql数据库步骤可参考:Node.js 连接 MySQL | 菜鸟教程 1、拆分之前的写法,未区分模块,所有接口api都写在了入口文件app.js中; 需求:想要将接口api拆分成根据不同的业务模块…

window10下载与安装zookeeper,图文说明

1,下载 打开连接 ;https://downloads.apache.org/zookeeper/ 选择版本下载 2,解压 cmd黑窗口解压命令 tar -zxvf apache-zookeeper-3.8.3-bin3,修改配置 复制zoo_sample.cfg,重命名为zoo.cfg zoo.cfg配置 # The …

操作系统系列:Unix进程系统调用fork,wait,exec

操作系统系列:Unix进程系统调用 fork系统调用fork()运用的小练习 wait系统调用Zombiesexec 系列系统调用 开发者可以查看创建新进程的系统调用,这个模块会讨论与进程相关的Unix系统调用,下一个模块会讨论Win32 APIs相关的进程。 fork系统调用…

uniapp-安卓APP开发时使用手机调试

调试 1. 手机打开开发者模式: 华为手机举列-->设置-->关于手机-->版本号,多次连续点击“版本号”,就会提示已打开开发者模式 2. 华为手机举列-->设置-->系统和更新-->开发人员选项-->打开 USB调试,进入调试模式 3. 先…

硬件基础--实施运维工程师介绍

1.实施运维工程师介绍 1.1 什么是实施工程师 1.2 是实施工程师的职责 1.3 什么是运维工程师? 1.4 运维工程师的职责 1.5 实施运维的主要技能 2.计算机组件 3.BIOS介绍 ​编辑 4.总结 1.实施运维工程师介绍 1.1 什么是实施工程师 常用操作系统、应用软件及公…

scrapy快加构造并发送请求

scrapy数据建模与请求 学习目标: 应用 在scrapy项目中进行建模应用 构造Request对象,并发送请求应用 利用meta参数在不同的解析函数中传递数据 1. 数据建模 通常在做项目的过程中,在items.py中进行数据建模 1.1 为什么建模 定义item即提前…

使用bs4 分析html文件

首先需要 pip install beautifulsoup4安装 然后为了方便学习此插件,随便打开一个网页,然后鼠标右键,打开源网页,如下图片 这样就可以获得一个网页源码,全选复制粘贴到本地,存储为 .html 文件,…

SpringBoot日志

目录 日志 日志使用 观察日志结构 打印日志 日志框架 门面模式(外观模式) SLF4J框架 日志格式 日志级别 日志配置 配置日志级别 配置控制台颜色 配置日志格式 日志持久化 日志文件分割 更简单的方式---Lombok框架支持 日志 日志作用&…

Tomcat多实例、负载均衡、动静分离

Tomcat多实例部署 安装jdk [rootlocalhost ~]#systemctl stop firewalld.service [rootlocalhost ~]#setenforce 0 [rootlocalhost ~]#cd /opt [rootlocalhost opt]#ls apache-tomcat-8.5.16.tar.gz jdk-8u91-linux-x64.tar.gz rh [rootlocalhost opt]#tar xf jdk-8u91-linu…

C# WPF上位机开发(crc校验)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 为了验证数据传输的过程中有没有发生翻转,我们在传输报文的同时一般还会添加一个crc校验。对于modbus协议也是一样,它在数据…

Jmeter中使用py插件

-安装插件 1、下载插件jython-standalone-2.7.0.jar到jmeter的lib\ext目录下 链接: https://pan.baidu.com/s/13ZXtUwoQEV62M98GaIR26w 提取码:ioyk 2、重启jmeter,查看是否生效,如果这个语言有python的选项说明可以了&#xf…

Excel怎样统计一列中不同的数据分别有多少个?

文章目录 1.打开Excel数据表2.选择“插入”,“数据透视表”3.选择数据透视表放置位置4.将统计列分别拖到“行”和“数值”区间5.统计出一列中不同的数据分别有多少个 1.打开Excel数据表 2.选择“插入”,“数据透视表” 3.选择数据透视表放置位置 4.将统计…