【新春不断更】数据结构与算法之美:二叉树

news2025/1/30 16:10:53

         Hello大家好,我是但凡!很高兴我们又见面啦!

        眨眼间已经到了2024年的最后一天,在这里我要首先感谢过去一年陪我奋斗的每一位伙伴,是你们给予我不断前行的动力。银蛇携福至,万象启新程。蛇年新春之际,愿你们万事顺遂,岁月皆安,新的一年所想皆如愿,所行皆坦途 。

        好了,给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》

欢迎点赞,关注!

目录

1、 二叉树的动态模拟

1.1新建节点 

1.2建树

1.3计算树的节点个数

1.3.1方法一

1.3.2方法二   

1.4计算树的叶子节点个数

1.5 计算树的第K层节点个数

1.6 树的深度

1.7查找节点 

1.8 遍历

1.8.1前序遍历

1.8.2中序遍历

1.8.3后序遍历

1.8.4层序遍历(广度优先遍历)

1.9判断二叉树是否为完全二叉树

1.10销毁二叉树

   2、二叉树的静态模拟实现


 

1、 二叉树的动态模拟

        我们用链式结构实现二叉树。一般链式结构实现二叉树,我们的结构中包含三个元素,一是该节点的数据,二是左子树节点,三是右子树节点

typedef char BTtype;
typedef struct BinaryTreeNode
{
	BTtype x;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTnode;

1.1新建节点 

BTnode* BTbuybode(char a)//你这声明和定义都不是一个意思好的
{
	BTnode* p = (BTnode*)malloc(sizeof(BTnode));
	if (p == NULL)
	{
		perror("malloc error!");
		exit(1);
	}
	p->left = NULL;
	p->right = NULL;
	p->x = a;
	return p;
}

1.2建树

         需要注意的是,我们的树是需要手动去建的,所以我在这只是给大家一个示例:

//建树
BTnode* nodeA=BTbuybode('A');
BTnode* nodeB = BTbuybode('B');
BTnode* nodeC = BTbuybode('C');
BTnode* nodeD = BTbuybode('D');
BTnode* nodeE = BTbuybode('E');
nodeA->left = nodeB;
nodeA->right = nodeC;
nodeB->left = nodeD;
nodeC->left = nodeE;
BTnode* root = nodeA;

建的树是这样的:

         那么接下来我们就实现以下和树相关的操作。准备好迎接递归的极致暴力美学!

1.3计算树的节点个数

1.3.1方法一

        方法一就是咱们把size作为一个函数的形参,然后把这个树遍历一遍,每遍历一个节点就size(节点个数)加一。但需要注意的是,我们需要传入size的地址才能改变size的值。

void BinaryTreeSize(BTnode* root,int* size)
{
	if (root == NULL)
	{
		return;
	}
	(*size)++;
	BinaryTreeSize(root->left,size);
	BinaryTreeSize(root->right, size);
}

1.3.2方法二   

        方法二是纯递归:  节点个数=左子树节点个数+右子树节点个数,所以我们以此为基础递归就可以了。

int BinaryTreeSize(BTnode* root)
{
	//节点个数=左子树节点个数+右子树节点个数
	//递归出口
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

1.4计算树的叶子节点个数

        树的叶子结点就是没有左右子树的节点,所以咱们得设置两个递归出口。

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);
}

1.5 计算树的第K层节点个数

        当咱们K减成1的时候,就说明到达了第K层。

int BinaryTreeLevelKSize(BTnode* root, int k)
{
	//递归出口
	if(k==1)
	{
		if (root == NULL)
		{
			return 0;
		}
		else
		{
			return 1;
		}
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

1.6 树的深度

        注意我是拿C++写的,用了自带的函数max,如果使用C语言写的话max函数得自己写,或者用一个问号表达式来实现类似效果。

int BinaryTreeDeep(BTnode* root)
{
	//计算树的深度
	if (root == NULL)
	{
		return 0;
	}
	return 1 + max(BinaryTreeDeep(root->left), BinaryTreeDeep(root->right));
}

1.7查找节点 

BTnode* BinaryTreeFind(BTnode* root, BTtype x)
{
	//递归出口
	if (root == NULL)
	{
		return 0;
	}
	if (root->x == x)
	{
		return root;
	}
	BTnode* left = BinaryTreeFind(root->left, x);
	if(left)
	{
		return root;
	}
	BTnode* right = BinaryTreeFind(root->right, x);
	if (right)
	{
		return root;
	}
	return NULL;
}

1.8 遍历

1.8.1前序遍历

        前序遍历就是先遍历头节点,然后遍历左子树,最后遍历右子树。我们可以把它拆开来想,我们左子树依然用先遍历头,再遍历左子树,最后遍历右子树的方式来遍历,左子树的左子树依然如此......

void BinaryTreePrevOrder(BTnode* root)
{
	//头 左 右
	//递归出口
	if (root == NULL)
	{
		cout << "NULL" << " ";
		return;
	}
	cout << root->x << " ";
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

1.8.2中序遍历

void BinaryTreeInOrder(BTnode* root)
{
	//左 头 右
	//递归出口
	if (root == NULL)
	{
		cout << "NULL" << " ";
		return;
	}
	BinaryTreeInOrder(root->left); //注意别调用错了,调用中序的
	cout << root->x << " ";
	BinaryTreeInOrder(root->right);
}

1.8.3后序遍历

void BinaryTreePostOrder(BTnode* root)
{
	//递归出口
	if (root == NULL)
	{
		cout << "NULL" << " ";
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	cout << root->x << " ";
}

        我们可以发现,这三个遍历的不同就是打印根节点的位置发生了变化 。我们以上三个遍历都属于深度优先遍历

1.8.4层序遍历(广度优先遍历)

void BinaryTreeLevelOrder(BTnode* root)
{
	queue<BTnode*> q;//创建队列
	q.push(root);
	while (!q.empty())
	{
		BTnode* tmp = q.front();
		q.pop();
		cout << tmp->x << " ";
		//左右子树入队列
		if (tmp->left)
		{
			q.push(tmp->left);
		}
		if (tmp->right)
		{
			q.push(tmp->right);
		}
	}
}

        这个层序遍历我用了c++自带的队列,如果用C语言写的话我们可以把模拟实现的队列文件导入。我之前发过队列的模拟实现,给大家放在这里: 数据结构与算法之美:队列-CSDN博客

1.9判断二叉树是否为完全二叉树

bool BinaryTreeComplete(BTnode* root)
{
	queue<BTnode*> q;//创建队列
	q.push(root);
	while (!q.empty())
	{
		BTnode* tmp = q.front();
		q.pop();
		if (tmp==NULL)
		{
			break;
		}
		//左右子树入队列
		q.push(tmp->left);
		q.push(tmp->right);
	}
	//现在队列中如果还有不为空的节点,就说明不是完全二叉树 
	while (!q.empty())
	{
		BTnode* tmp = q.front();
		q.pop();
		if (tmp)
		{
			return false;
		}
	}
	return true;
}

1.10销毁二叉树

void BinaryTreeDestory(BTnode** root)
{
	//这里因为咱们要改变根节点,应该传入的是根节点的地址,所以得拿二级指针接收
	//递归出口
	if ((*root) == NULL)
	{
		return;
	}
	//自叶向根方向的释放
	//如果先释放的话,就找不到叶子节点了
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

   所有测试代码:

#include"BinaryTree.h"
void test()
{
	//建树
	BTnode* nodeA=BTbuybode('A');
	BTnode* nodeB = BTbuybode('B');
	BTnode* nodeC = BTbuybode('C');
	BTnode* nodeD = BTbuybode('D');
	BTnode* nodeE = BTbuybode('E');
	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	BTnode* root = nodeA;
	//计算节点个数
	int size = 0;
	BinaryTreeSize(root, &size);
	cout <<"节点个数:"<< size<< endl;
	//计算叶子节点个数
	cout << "叶子节点个数:" << BinaryTreeLeafSize(root) << endl;
	//计算第三层节点个数
	cout << "第三层节点个数:" << BinaryTreeLevelKSize(root, 3) << endl;
	//计算二叉树深度
	cout<<"二叉树深度:"<< BinaryTreeDeep(root) << endl;
	//查找值为E的节点
	BTnode* node = BinaryTreeFind(root, 'E');
	if (node)//已找到
	{
		cout << "已找到该节点" << endl;
	}
	else
	{
		cout << "未找到该节点" << endl;
	}
	//查找值为G的节点
	BTnode* node1 = BinaryTreeFind(root, 'G');
	if (node1)//已找到
	{
		cout << "已找到该节点" << endl;
	}
	else
	{
		cout << "未找到该节点" << endl;
	}
	//前序遍历
	BinaryTreePrevOrder(root);
	cout << endl;
	// 二叉树中序遍历
	BinaryTreeInOrder(root);
	cout << endl;
	//后序遍历
	BinaryTreePostOrder(root);
	cout << endl;
	//广度优先遍历
	BinaryTreeLevelOrder(root);
	cout << endl;
	//是否为完全二叉树
	if (BinaryTreeComplete(root))
	{
		cout << "是完全二叉树" << endl;
	}
	else
	{
		cout << "不是完全二叉树" << endl;
	}
	//二叉树的销毁
	BinaryTreeDestory(&root);
}
int main()
{
	test();
	return 0;
}

测试结果:

 

2、二叉树的静态模拟实现

        之前我们介绍堆的时候建堆用的是vector数组,其实静态建树一共有两个方式。一个是vector数组,一个是链式前向星。二叉树当然也可以用这两个方法建,但是呢对于二叉树来说有一个特殊的方法来建树。

        我们创建两个足够大数组,这两个数组分别记录着下标为k的左右节点的值。其中这个k是当前这个节点的值。咱们建树的时候都默认根节点的值为1.

#include<iostream>
#include<queue>
using namespace std;
const int N = 1e4 + 10;
int l[N], r[N];
queue<int> q;
void bfs()
{
	q.push(1);
	while (q.size())
	{
		int v = q.front();
		cout << v << " ";
		q.pop();
		//由于二叉树的存储找不到当前节点的父节点(也就是不能向上查找)
		//所以不需要bool数组标记
		if(l[v])
		{
			q.push(l[v]);
		}
		if(r[v])
		{
			q.push(r[v]);
		}
	}
}
void dfs1(int v)
{
	cout <<v<<" ";
	if (l[v]) dfs1(l[v]);
	if (r[v]) dfs1(r[v]);
}
void dfs2(int v)
{
	if (l[v]) dfs2(l[v]);
	cout << v << " ";
	if (r[v]) dfs2(r[v]);
}
void dfs3(int v)
{
	if (l[v]) dfs3(l[v]);
	if (r[v]) dfs3(r[v]);
	cout << v << " ";
}
int main()
{
	//建二叉树
	int n;
	cin >> n;//节点个数
	cin >> l[1] >> r[1];
	//l[],r[]分别存储当前节点的左右孩子,0代表没有该孩子 
	for(int i=2;i<=n;i++)//循环n-1次
	{
		cin >> l[i] >> r[i];
	}
	//二叉树新增的深度优先遍历方式
	//前序遍历
	dfs1(1);
	cout << endl;
	//中序遍历
	dfs2(1);
	cout << endl;
	//后序遍历
	dfs3(1);
	cout << endl;
	//宽度优先遍历
	bfs();
}

        我们可以发现他的各种遍历的思路是和咱们动态实现一样的。 

        二叉树练习题:题海拾贝:二叉树的模拟题-CSDN博客

        题海拾贝:[USACO3.4] 美国血统AmericanHeritage(求先序排列问题)-CSDN博客

        题海拾贝:[JLOI2009] 二叉树问题-CSDN博客

        好了,今天的内容就分享到这,我们下期再见!

 

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

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

相关文章

Linux环境基础开发工具的使用(apt, vim, gcc, g++, gbd, make/Makefile)

什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序. 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安 装程序)放在一个服务器上, 通过包管理器可以很方便的获取到这个编译好的…

渗透测试之WAF规则触发绕过规则之规则库绕过方式

目录 Waf触发规则的绕过 特殊字符替换空格 实例 特殊字符拼接绕过waf Mysql 内置得方法 注释包含关键字 实例 Waf触发规则的绕过 特殊字符替换空格 用一些特殊字符代替空格&#xff0c;比如在mysql中%0a是换行&#xff0c;可以代替空格 这个方法也可以部分绕过最新版本的…

Harmony Next 跨平台开发入门

ArkUI-X 官方介绍 官方文档&#xff1a;https://gitee.com/arkui-x/docs/tree/master/zh-cn ArkUI跨平台框架(ArkUI-X)进一步将ArkUI开发框架扩展到了多个OS平台&#xff1a;目前支持OpenHarmony、Android、 iOS&#xff0c;后续会逐步增加更多平台支持。开发者基于一套主代码…

小阿卡纳牌

小阿卡纳牌 风&#xff1a;热湿 火&#xff1a;热干 水&#xff1a;冷湿 土&#xff1a;冷干 火风&#xff1a;温度相同&#xff0c;但是湿度不同&#xff0c;二人可能会在短期内十分热情&#xff0c;但是等待热情消退之后&#xff0c;会趋于平淡。 湿度相同、温度不同&#x…

【现代深度学习技术】深度学习计算 | 参数管理

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…

c++ 定点 new

&#xff08;1&#xff09; 代码距离&#xff1a; #include <new> // 需要包含这个头文件 #include <iostream>int main() {char buffer[sizeof(int)]; // 分配一个足够大的字符数组作为内存池int* p new(&buffer) int(42); // 使用 placement new…

宫本茂的游戏设计思想:有趣与风格化

作为独立游戏开发者之一&#xff0c;看到任天堂宫本茂20年前的言论后&#xff0c;深感认同。 游戏研发思想&#xff0c;与企业战略是互为表里的&#xff0c;游戏是企业战略的具体战术体现&#xff0c;虚空理念的有形载体。 任天堂长盛不衰的关键就是靠简单有趣的游戏&#xf…

【AI论文】扩散对抗后训练用于一步视频生成总结

摘要&#xff1a;扩散模型被广泛应用于图像和视频生成&#xff0c;但其迭代生成过程缓慢且资源消耗大。尽管现有的蒸馏方法已显示出在图像领域实现一步生成的潜力&#xff0c;但它们仍存在显著的质量退化问题。在本研究中&#xff0c;我们提出了一种在扩散预训练后针对真实数据…

在线可编辑Excel

1. Handsontable 特点&#xff1a; 提供了类似 Excel 的表格编辑体验&#xff0c;包括单元格样式、公式计算、数据验证等功能。 支持多种插件&#xff0c;如筛选、排序、合并单元格等。 轻量级且易于集成到现有项目中。 具备强大的自定义能力&#xff0c;可以调整外观和行为…

【javaweb项目idea版】蛋糕商城(可复用成其他商城项目)

该项目虽然是蛋糕商城项目&#xff0c;但是可以复用成其他商城项目或者购物车项目 想要源码的uu可点赞后私聊 技术栈 主要为&#xff1a;javawebservletmvcc3p0idea运行 功能模块 主要分为用户模块和后台管理员模块 具有商城购物的完整功能 基础模块 登录注册个人信息编辑…

langchain基础(三)

Chain&#xff1a; 关于三个invoke&#xff1a; 提示模板、聊天模型和输出解析器都实现了langchain的runnable接口&#xff0c; 都具有invoke方法&#xff08;因为invoke方法是Runnable的通用调用方法&#xff09; 所以可以一次性调用多次invoke直接得到最终结果&#xff1a;…

Kafka 深入服务端 — 时间轮

Kafka中存在大量的延迟操作&#xff0c;比如延时生产、延时拉取和延时删除等。Kafka基于时间轮概念自定义实现了一个用于延时功能的定时器&#xff0c;来完成这些延迟操作。 1 时间轮 Kafka没有使用基于JDK自带的Timer或DelayQueue来实现延迟功能&#xff0c;因为它们的插入和…

一文掌握ADB的安装及使用

文章目录 一、什么是ADB&#xff1f;二、 安装ADB2.1 下载ADB2.2 配置环境变量 三、连接Android设备四、 常用ADB命令五、ADB高级功能5.1 屏幕截图和录制5.2 模拟按键输入5.3 文件管理5.4 系统设置管理5.5 系统操作指令5.6 日志操作指令5.7 APK操作指令5.8 设备重启和恢复 六、…

Linux系统下速通stm32的clion开发环境配置

陆陆续续搞这个已经很久了。 因为自己新电脑是linux系统无法使用keil&#xff0c;一开始想使用vscode里的eide但感觉不太好用&#xff1b;后面想直接使用cudeide但又不想妥协&#xff0c;想趁着这个机会把linux上的其他单片机开发配置也搞明白&#xff1b;而且非常想搞懂cmake…

OpenCSG月度更新2025.1

1月的OpenCSG取得了一些亮眼的成绩 在2025年1月&#xff0c;OpenCSG在产品和社区方面继续取得了显著进展。产品方面&#xff0c;推出了AutoHub浏览器自动化助手&#xff0c;帮助用户提升浏览体验&#xff1b;CSGHub企业版功能全面升级&#xff0c;现已开放试用申请&#xff0c…

AWTK 骨骼动画控件发布

Spine 是一款广泛使用的 2D 骨骼动画工具&#xff0c;专为游戏开发和动态图形设计设计。它通过基于骨骼的动画系统&#xff0c;帮助开发者创建流畅、高效的角色动画。本项目是基于 Spine 实现的 AWTK 骨骼动画控件。 代码&#xff1a;https://gitee.com/zlgopen/awtk-widget-s…

go到底是什么意思:对go的猜测或断言

go这个单词&#xff0c;简单地讲&#xff0c;表示“走或去”的意思&#xff1a; go v.去&#xff1b;走 认真想想&#xff0c;go是一个非常神秘的单词&#xff0c;g-和o-这两个字母&#xff0c;为什么就会表达“去&#xff1b;走”的意思呢&#xff1f;它的字面义或本质&…

学习数据结构(2)空间复杂度+顺序表

1.空间复杂度 &#xff08;1&#xff09;概念 空间复杂度也是一个数学表达式&#xff0c;表示一个算法在运行过程中根据算法的需要额外临时开辟的空间。 空间复杂度不是指程序占用了多少bytes的空间&#xff0c;因为常规情况每个对象大小差异不会很大&#xff0c;所以空间复杂…

DeepSeek--通向通用人工智能的深度探索者

一、词源与全称 “DeepSeek"由"Deep”&#xff08;深度&#xff09;与"Seek"&#xff08;探索&#xff09;组合而成&#xff0c;中文译名为"深度求索"。其全称为"深度求索人工智能基础技术研究有限公司"&#xff0c;英文对应"De…

Unity游戏(Assault空对地打击)开发(1) 创建项目和选择插件

目录 前言 创建项目 插件导入 地形插件 前言 这是游戏开发第一篇&#xff0c;进行开发准备。 创作不易&#xff0c;欢迎支持。 我的编辑器布局是【Tall】&#xff0c;建议调整为该布局&#xff0c;如下。 创建项目 首先创建一个项目&#xff0c;过程略&#xff0c;名字请勿…