数据结构第九讲:二叉树

news2024/11/13 9:42:05

数据结构第九讲:二叉树

  • 1.实现链式结构二叉树
    • 1.1二叉树的节点结构
    • 1.2创建二叉树节点
    • 1.3前中后序遍历
      • 1.3.1前序遍历
      • 1.3.2中序遍历
      • 1.3.3后序遍历
      • 1.3.4总结
    • 1.4二叉树结点的个数
      • 1.4.1错误示范
      • 1.4.2实现方法
    • 1.5二叉树叶子结点的个数
    • 1.6二叉树第k层结点的个数
    • 1.7二叉树的深度/高度
    • 1.8二叉树查找值为x的结点
    • 1.9二叉树的销毁
  • 2.二叉树的层序遍历
    • 2.1什么是层序遍历
    • 2.2层序遍历的实现
      • 2.2.1实现思路
      • 2.2.2先创建一个队列
      • 2.2.3代码的实现
  • 3.判断是否为完全二叉树
    • 3.1解题思路
    • 3.2代码实现

这一讲我们要实现二叉树的链式结构,二叉树结构体中包含了数据、指向左孩子节点的指针和指向右孩子节点的指针,在这一讲中,我们将要体会的递归的暴力!!!

1.实现链式结构二叉树

1.1二叉树的节点结构

//二叉树的节点结构
typedef int BinaryTreeDataType;

typedef struct BinaryTreeNode
{
	BinaryTreeDataType data;//保存数据
	struct BinaryTreeNode* left;//指向左孩子节点
	struct BinaryTreeNode* right;//指向右孩子节点
}BTNode;

1.2创建二叉树节点

也就是为二叉树创建节点,并将节点进行初始化

//创建二叉树节点
BTNode* BuyBTNode(int val)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		return NULL;
	}
	newnode->data = val;
	newnode->left = newnode->right = NULL;

	return newnode;
}

1.3前中后序遍历

接下来我们要实现的是二叉树的遍历:

二叉树的遍历操作分为三种:前序遍历、中序遍历、后序遍历:
在这里插入图片描述
可以看出:这里区分的前中后其实就是根节点遍历的顺序
我们先总体看三种遍历的不同:
在这里插入图片描述

接下来我们来实现三种遍历,注意:三种遍历方法的代码实现非常简单,主要是思路的体会,三种方法都是使用的递归的思想

1.3.1前序遍历

//前序遍历
//函数传入的是树的根节点
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//将节点的数据进行打印
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

在这里插入图片描述

对于递归,return之后只是一个递归函数终止,然而形参的值不变,函数会继续向下执行,形成一个全新的递归函数:
在这里插入图片描述

1.3.2中序遍历

//中序遍历
void InOrder(BTNode* root)
{
	//也就是左根右进行遍历
	if (root == NULL)
	{
		return;
	}
	//先对左边的数据进行读取,其实就是将左边的节点当成是根节点进行传入
	InOrder(root->left);
	//先打印根节点的值,然后再检查右节点是否为空
	printf("%d ", root->data);
	//当右节点不为空时,它会按照从上向下的顺序一直走到右节点的尽头
	//当然,当有右节点中存在左节点时,会先走左节点的循环,因为左节点的循环在上
	//而且会一下走到左节点的尽头,然后从下往上遍历左节点
	InOrder(root->right);
}

1.3.3后序遍历

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//仍然是先走到最后一个左节点
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

1.3.4总结

在这里插入图片描述

1.4二叉树结点的个数

1.4.1错误示范

根据我们之前所讲,那么我们应该会有一个初步的思路,我们先实现一下:

//二叉树结点个数
//先定义一个变量sz
int sz = 0;
int BTSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	//每循环一次,那么就将sz++一次
	++sz;
	BTSize(root->left);
	BTSize(root->right);

	return sz;
}

这时打印的结果也是非常beautiful啊,和预想的一样,但是,当我们这样时:

//二叉树结点个数
int size = BTSize(n1);
printf("%d ", size);//6
size = BTSize(n1);
printf("%d ", size);//12

我们就会惊奇地发现:结点的次数竟然会随着函数调用次数的增加而成倍地增长!原因就是使用了全局变量,全局变量在函数使用后不会销毁,那么我们就要进行更改了:

1.4.2实现方法

//二叉树结点个数
int BTSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	//返回左节点的个数和右节点的个数
	return 1 + BTSize(root->left) + BTSize(root->right);
}

1.5二叉树叶子结点的个数

//二叉树叶子结点个数
int BTLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//这里的返回值也会参与加法运算,所以会呈现出累加的效果
	//也就是说,返回值会存储到BTLeafSize(root->left)或另一个函数中,然后再进入加法运算
	//返回左边的树的叶子节点个数 + 右边的树的叶子结点个数
	return BTLeafSize(root->left) + BTLeafSize(root->right);
}

对于递归,先搞清总体思路,如上边的:返回左边的树的叶子节点个数 + 右边的树的叶子结点个数,然后再想清楚结束条件,如:当左节点和右节点都为0时返回1,此时会发现递归已经写完了!!!

1.6二叉树第k层结点的个数

//二叉树第k层结点的个数
int BTLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	//返回条件:当k = 1时
	if (k == 1)
	{
		return 1;
	}
	//返回左边的二叉树第k层的节点个数和右边二叉树第k层的结点个数
	return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k - 1);
}

1.7二叉树的深度/高度

要想在递归的过程中对数据逐渐进行增大,必须要让return返回的值被接收,而且参与递归运算,这里是创建变量进行存储,还可以使用递归函数进行存储,如:1+BTDeapth(root->right)(这里是瞎写,仅代表一个格式)

//二叉树的深度/高度
int BTDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftdepth = BTDepth(root->left);
	int rightdepth = BTDepth(root->right);
	
	//要想在递归的过程中对数据逐渐进行增大,必须要让return返回的值被接收,而且参与递归运算
	//这里是创建变量进行存储
	//还可以使用递归函数进行存储,如:1+BTDeapth(root->right)(这里是瞎写,仅代表一个格式)
	return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1;
}

在这里插入图片描述

1.8二叉树查找值为x的结点

//二叉树查找值为x的结点
BTNode* BTFind(BTNode* root, BinaryTreeDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	//返回条件:当数据是我们想要的数据时,就进行返回
	if (root->data == x)
	{
		return root;
	}
	BTNode* left = BTFind(root->left, x);
	if (left)
	{
		//这里要十分注意的是:
		//这里的return表示的是整个函数的返回,上面的return代表的是递归函数的返回
		//原因就在于使用了一个值来接受递归函数的返回值,使得递归函数结束递归了
		//如果这里不适用变量来接受的话,函数将会错误返回
		return left;
	}
	BTNode* right = BTFind(root->right, x);
	if (right)
	{
		return right;
	}
	return NULL;
}

1.9二叉树的销毁

//二叉树的销毁
//因为销毁要改变指针的指针的指向,所以这里传的是二级指针
void BTDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	//这里的传参要注意:因为是二级指针接收,所以传入的应该是一级指针的地址
	//直接遍历所有结点,然后一一删除即可
	BTDestory(&(*root)->left);
	BTDestory(&(*root)->right);
	free(*root);
	*root = NULL;
}

2.二叉树的层序遍历

2.1什么是层序遍历

层序遍历就是按照层数,从左到右一次对数据进行遍历:
在这里插入图片描述

2.2层序遍历的实现

2.2.1实现思路

对于递归,它会一直执行下去,直到遇到结束条件,然而,这个算法中,并不支持递归的使用,因为我们想不出来什么结束条件能够成立,所以这时我们就想到了其它的方法:队列!!!

在这里插入图片描述
下面我们来看实现方法:

2.2.2先创建一个队列

恰巧我们刚刚学过了队列,所以我们完全可以将之前写的队列代码拿过来,但是要注意的是,之前我们所实现的队列中保存的值为int类型,但是现在因为插入的值为BTNode*类型,所以还要将类别进行更改:

//结点结构体
//尽管我们之前已经使用typedef将结构体的名字改变成了BTNode*
//这里仍然需要加上struct,否则编译器会识别不出来,万一是一个变量名呢对不对
typedef struct BTNode* QueueDataType;

typedef struct QueueNode
{
	//和链表一样,也需要结点进行链接
	QueueDataType val;
	struct QueueNode* next;
}QueueNode;

2.2.3代码的实现

//二叉树层序遍历
void LevelOrder(BTNode* root)
{
	//先创建一个队列
	Queue q;
	//队列初始化
	Init(&q);
	//将二叉树链表入队列
	QueuePush(&q, root);

	//循环,当队列为空时结束循环,当队列不为空时进行循环
	while (!QueueEmpty(&q))
	{
		//将一个结点出队列
		BTNode* ret = QueueFront(&q);
		printf("%d ", ret->data);
		QueuePop(&q);
		//如果有左右结点的话,按顺序入队列
		if (ret->left)
		{
			QueuePush(&q, ret->left);
		}
		if (ret->right)
		{
			QueuePush(&q, ret->right);
		}
	}
	Destory(&q);
}

3.判断是否为完全二叉树

3.1解题思路

这一道题目仍然是应用队列的知识:
在这里插入图片描述

3.2代码实现

//判断是否为完全二叉树
bool BinaryTreeComplete(BTNode* n1)
{
	//先创建一个队列
	Queue q;
	Init(&q);

	//先将二叉树中的数据全部插入到队列中
	//先将对头元素插入到队列中
	QueuePush(&q, n1);
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		if (top == NULL)
		{
			break;
		}
		//如果top不为空,将top的左孩子结点和右孩子结点入队,这样就保障了NULL结点的入队
		QueuePush(&q, top->left);
		QueuePush(&q, top->right);
	}
	//入队完成之后,检查队列中的数据,不能够存在一个NULL数据
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		//如果存在不为空的数据,返回false
		if (top)
		{
			Destory(&q);
			return false;
		}
	}
	Destory(&q);
	return true;
}

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

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

相关文章

看门狗应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

看门狗应用编程 看门狗应用编程介绍 看门狗定时器的基本概念 看门狗是一个可以在一定时间内被复位/重置的计数器 如果在规定时间内没有复位,看门狗计时器溢出会对CPU产生复位信号使系统重启 有些看门狗可以只产生中断信号而不会使系统复位 I.MX6UL/I.MX6ULL So…

如何减少内存碎片的产生——页

文章目录 1 页的设计目的2 进程块和主存块的对应关系3 页、页框、页表3.1 页(Page)3.2 页框(Page Frame)3.3 页表(Page Table) 4 逻辑地址到物理地址的转换4.1 转换过程4.2 具体示例4.3 图示 参考资料封面 …

C语言程序设计25

《C程序设计教程(第四版)——谭浩强》 习题2.2 分析下面程序的运行结果,然后上机验证。 代码: //《C程序设计教程(第四版)——谭浩强》 //习题2.2 分析下面程序的运行结果,然后上机验证。#inc…

【C语言篇】操作符详解(下篇)

文章目录 操作符详解(下篇)前言条件操作符移位操作符左移操作符右移操作符 位操作符下标引用操作符函数调用操作符 操作符的属性:优先级和结合性优先级结合性表达式求值整形提升算术转换 操作符详解(下篇) 前言 操作…

JavaScript基础——JavaScript常见语句(判断语句、循环语句、控制流语句)

JavaScript提供了丰富的语句来控制程序的执行流程,包括用于条件判断的if、switch和三元运算符,以及用于循环的for、while、do...while、for...in和for...of。此外,还有控制流语句如break、continue和return。 判断语句 if 语句 if 语句&…

C/C++开发,opencv轮廓提取实现

一、cv::findContours轮廓提取函数 1.1 cv::findContours函数简介 cv::findContours 函数是用于从二值图像(灰度图)中检索轮廓。这个函数在OpenCV的不同版本中参数可能有所不同,但基本概念保持一致。特别是在OpenCV 3.x和4.x版本中&#xff…

贪吃蛇(使用QT)

贪吃蛇小游戏 一.项目介绍**[贪吃蛇项目地址](https://gitee.com/strandingzy/QT/tree/zyy/snake)**界面一:游戏大厅界面二:关卡选择界面界面三:游戏界面 二.项目实现2.1 游戏大厅2.2关卡选择界面2.3 游戏房间2.3.1 封装贪吃蛇数据结构2.3.2 …

如何通过谷歌外链快速增加网站流量?

利用谷歌外链提升流量的方法非常直接,但实际上,外链影响的是关键词排名,关键词排名提升了,自然就会有流量,所以谷歌外链不是直接能提升网站流量,而是间接的,下面,我会详细介绍几种有…

验收测试:确保软件符合业务需求和合同要求

目录 前言1. 验收测试的概念1.1 用户验收测试(UAT)1.2 操作验收测试(OAT) 2. 验收测试的主要作用2.1 确认业务需求的满足2.2 验证合同要求的实现2.3 提升用户信心 3. 验收测试在整个测试中的地位3.1 测试的最后一道关卡3.2 用户与…

niushop逻辑漏洞

niushop逻辑漏洞 安装环境 这里控制不了 抓包在数据包中可以改数据

非线性系统稳定控制器设及案例仿真(s-function函数)

目录 前沿一、案例11. 系统模型 二、案例21. 系统模型2. 稳定性分析3. 仿真(包含代码)1. 仿真效果2. 仿真结果3. 仿真剖析4. 仿真图与代码 前沿 定义一个系统 x ˙ f ( x , u ) \dot{x} f(x,u) x˙f(x,u), 其中 x x x 为状态变量, u u u 为系统输入&#xff0c…

跨平台终端工具Tabby安装配置与远程ssh连接Linux_ubuntu详细教程

文章目录 前言1. Tabby下载安装2. Tabby相关配置3. Tabby简单操作4. ssh连接Linux4.1 ubuntu系统安装ssh4.2 Tabby远程ssh连接ubuntu 5. 安装内网穿透工具5.1 创建公网地址5.2 使用公网地址远程ssh连接 6. 配置固定公网地址 前言 今天和大家分享一下如何在Windows系统使用Tabb…

构建LVS负载均衡群集

构建LVS负载均衡群集 实验环境实验环境 201:客户端 101:调度器 (需要用到2个网卡) 102:web 103:web 104:NFS存储 101:(添加一个网卡) 2 [root@localhost ~]# cd /etc/sysconfig/network-scripts/ 3 4 //查看另一个网卡的名字ens36 5 [root@localhost network-scrip…

人工智能深度学习系列—深度学习中的相似性追求:Triplet Loss 全解析

文章目录 1. 背景介绍2. Loss计算公式3. 使用场景4. 代码样例5. 总结 1. 背景介绍 在机器学习和模式识别领域,相似性度量是核心问题之一。Triplet Loss,作为一种特殊的损失函数,被设计用来学习数据的相对距离,从而使得相似样本更…

5.C_Demo_排序

冒泡排序法 原理: 依次比较相邻的两个元素,如果顺序错误就交换。 思路: 这种方法,显然需要很多轮才能完成,每一轮只能排序一个最大值或最小值(第一层for),将全部的数据排序完成,需要很多轮(第…

第三期书生大模型实战营之书生大模型全链路开源开放体系

一. Introduction 大模型是发展通用人工智能的重要途经 二. 开源历程以及InternLM2 2024年1月17日 InternLM2 2开源 三. 书生浦语2.0的主要亮点 3.1 超长上下文 3.2 综合性能全面提升 3.3 优秀的对话和创作体验 3.4 工具调用能力整体升级 3.5 突出的数理能力和实用的…

Among Us 私服的制作之路

文章目录 Among Us 私服的制作之路这游戏通常包括以下核心元素:角色设定:游戏机制:游戏界面: 第四步:添加社交特性第五:测试与优化方面 十分基础的框架(Web)注意事项 Among Us 私服的制作之路 作者正在准备…

嵌入式:简单的UI框架

1:UI框架简介 除了服务框架外,我们还需要对外显示UI,所以我们就需要一个UI的框架,跟服务框架一样,不用这个UI框架我们也是可以实现,但是这样每个人写的UI都会有差异,需要的事件,数据…

牛客JS题(二十)判断斐波那契数组

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 循环判断斐波那契数列组递归判断斐波那契数列组合法性判断 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charset"utf-8" /></head><body><scrip…

嵌入式数据库 sqlite3

数据库文件与普通文件区别: 1.普通文件对数据管理(增删改查)效率低 2.数据库对数据管理效率高,使用方便 常用数据库: 1.关系型数据库 将复杂的数据结构简化为二维表格形式 大型:Oracle、DB2 中型:MySql、SQLServer 小型:Sqlite …