【手撕数据结构】链式二叉树

news2024/11/15 19:45:03

目录

  • 链式二叉树的结构及其声明
  • 链式二叉树的四种遍历方式
    • 前序遍历
    • 中序遍历(中根遍历)
    • 后序遍历
    • 层序遍历
    • 概念
    • 思路分析
    • 详细代码
  • 求树的节点个数
    • 变量累加法(错误)
    • 分治递归法
  • 求树的叶子节点个数
    • 警惕空指针
    • 正确代码
  • 求第k层节点个树
    • 思路分析及规则明细
    • 代码详细
  • 求树的深度/高度
    • 规则明细及思路分析
    • 错误代码示范
    • 代码详细(正确)
  • 查找指定节点
    • 思路分析
    • 代码详细
  • 销毁二叉树
    • 思路分析
    • 代码详细

链式二叉树的结构及其声明

  • 首先来看看它的结构声明。结构体中有三个成员,一个是当前结点的值,还有两个是指向当前结点左孩子结点的指针以及指向右孩子结点的指针
typedef int BTDataType;
typedef struct BinaryTreeNode {
	BTDataType data;
	struct BinaryTreeNode* lchild;
	struct BinaryTreeNode* rchild;
}BTNode;

  • 也就是下面这种样子

在这里插入图片描述

链式二叉树的四种遍历方式

前序遍历

  • 规则:根——左子树——右子树
    在这里插入图片描述
  • 总的来说,我们必须把对于每个节点根节点(自己)访问完,然后访问他的左孩子节点,而对于左孩子节点他也必须访问根节点(自己),然后访问他的左孩子节点,指定为NULL,然后开始访问他的右孩子节点.

在这里插入图片描述

  • 对于1这个节点,先访问本身,打印1;然后接着访问他的左孩子节点2,对于2也是访问他本身,打印2,然后访问他的左孩子节点4,对于4也是访问他本身,打印4,然后访问他的左孩子节点NULL,发现左孩子节点为NULL直接返回(也可以选择打印)
/*先序遍历*/
void PreOrder(BTNode* root)
{
	if (!root)
	{
		//printf("NULL ");可以选择打印
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->lchild);
	PreOrder(root->rchild);
}

  • 结果就是1 2 4 3

中序遍历(中根遍历)

  • 规则:左子树——根——右子树
  • 了解了前序遍历,那中序遍历也不下话下。和先序做一个区分
  • 这不简单了吗?前面是先访问根节点,然后依次访问他的左孩子节点,然后左孩子节点又访问根节点(本身)然后又访问他的左还子节点
  • 然后咱们就依葫芦画瓢,先访问左孩子节点,然后左孩子节点又访问他的左孩子节点,访问为空,然后访问根节点,然后根节点访问完,又访问右孩子节点
/*中序遍历*/
void InOrder(BTNode* root)
{
	if (!root)
	{
		//printf("NULL ");
		return;
	}
	PreOrder(root->lchild);
	printf("%d ", root->data);
	PreOrder(root->rchild);
}

  • 以上一个二叉树1 2 3 4为例,他的中序遍历:4 2 1 3

后序遍历

规则:左子树——右子树——根

  • 这里我相信大家都能自己推出来了,直接上代码吧。
/*后序遍历*/
void PostOrder(BTNode* root)
{
	if (!root)
	{
		//printf("NULL ");
		return;
	}
	PreOrder(root->lchild);
	PreOrder(root->rchild);
	printf("%d ", root->data);
}

  • 1 2 3 4二叉树为例,结果: 4 2 3 1

层序遍历

概念

在这里插入图片描述

  • 就是一层一层从左到右遍历打印,比如上面的二叉树,一层一层从左到右打印就是1 2 3 4 5

思路分析

  • 这里层次遍历就无法使用递归了,递归要么是左子树递归到底,要么是右子树递归到底,我们这里必须把每层的左右都打印完
  • 观察一下,也就是满足 1 2 3 4 5,按节点顺序来,我们前面说的数据结构队列的原则就是先进先出,是不是进满足这个规则。

下面讲讲具体步骤

  • 第一步就是,把根节点入队列,此时这个节点是队头。(目的是让队列不为空)
  • 第二步就是,取队头,并且打印数据,然后出队列(按照队头顺序打印)
  • 第三步就是,如果队头的左右孩子不为空,将他们入队列(先进先出原则,把上一层的左右孩子打印)
  • 第四步,从第二步开始重复,直到队列为空。
    在这里插入图片描述在这里插入图片描述

详细代码

void LevelOrdre(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while(!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%d ", front->data);
		QueuePop(&q);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
	}
	QueueDestroy(&q);
}
  • 注意改变了了队列存储节点的数据类型为二叉树节点
    在这里插入图片描述
  • 入队列的节点注意是取出队头的左右孩子节点

求树的节点个数

变量累加法(错误)

  • 有的同学看到,灵机一动,这么简单?直接定义一个局部变量累加就行了。想法挺好,但现实很残酷,我们每次调用函数都要开辟函数栈帧,而每个函数栈帧都要重新定义这个变量,变量每次都是从0开始。这时候结构每次就是1
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	int size = 0;
	size++;
	BinaryTreeSize(root->left);
	BinaryTreeSize(root->right);
	return size;
}
  • 于是有同学呢又想出一种办法,就是将这个变量定义为静态变量,因为静态变量是存放在静态区中的,不会随着某一个函数的调用结束而销毁,因此我们可以这么写
int TreeNodeNum1(BTNode* root)
{
	if (root == NULL)
		return;
	static int sz = 0;
	sz++;
	TreeNodeNum1(root->lchild);
	TreeNodeNum1(root->rchild);
	return sz;
}

  • 可以看到,第一次的计算确实是可以计算出来这棵树有6个结点,但是在我多调用几次之后结点个数却发生了一个累加,这就是静态变量的特点,均会在上一次的基础上去进行一个运算,无论你是调用其他的什么函数或者是多调用几次,那其实可以看到这里就出现BUG了,
    在这里插入图片描述
  • 其实这一块很简单,我们不需要静态变量,直接将这个变量放到函数外部,作为一个全局变量即可,因为函数内部的返回值我们不好控制,干脆就不要返回值,直接将这个记数的变量定义为全局变量,这样就很好控制了
int sz = 0;
void TreeNodeNum1(BTNode* root)
{
	if (root == NULL)
		return;
	sz++;
	TreeNodeNum1(root->lchild);
	TreeNodeNum1(root->rchild);
}

在这里插入图片描述

分治递归法

  • 我们始终将一个树分割为三个部分,【根】【左子树】【右子树】,因此进行一个分块求解然后再加起来就可以了
  • 也就是说我们把每一个子树的根节点和左孩子节点和右孩子节点的个数累加起来
    在这里插入图片描述
  • 注意的是,我们如果当前节点存在子树,子树应该至少有一个节点,所以统计每个子树节点个数的时候至少为1,如果节点为空,说明没有子树,直接返回0
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

在这里插入图片描述

求树的叶子节点个数

警惕空指针

  • 求解树的叶子结点个数,叶子节点就是左孩子和右孩子节点都为NULL
  • 前面说了如何去求一颗树的节点个数,那么求叶子节点个树就是不加上那些不是左右孩子都为NULL的节点
  • 于是有同学就写了这样的代码,我们来看看有什么问题
int BinaryTreeLeafSize(BTNode* root)
{
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

在这里插入图片描述

  • 所以我们要对当前节点是否为NULL进行判断,如果为NULL说明他肯定不是叶子节点

正确代码

  • 根据上面的推测我们可以写出
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);
}

求第k层节点个树

思路分析及规则明细

📚当k > 1 时,第k层的结点个数为第k - 1层左孩子节点个数 + k-1层右孩子的结点个数
📚当k == 1时,return 1

  • 除了判断k之外,别忘了每次都要判断一下传进来的根结点是否为空,防止访问空指针
    在这里插入图片描述

  • ,所以当k=1的时候,是第k层,第k层节点个数实际就是第k-1层的左右孩子节点个数,求k-1层节点的左右孩子节点即可

代码详细

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

求树的深度/高度

规则明细及思路分析

规则:二叉树高度 = 左子树和右子树中高度大的那个 + 1

  • 思路就是我们需要比较二叉树的右子树和左子树的高度谁更高,此时就是整棵树的深度。想知道对于祖宗节点左子树的深度,就得求出祖宗节点左子树的左子树深度,依次类推比较返回。
  • 值得注意的是,+1是因为如果二叉树不是空树,就至少高度为1(祖宗节点一层)

错误代码示范

int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	return TreeHeight(root->lchild) > TreeHeight(root->rchild) ?		
		   TreeHeight(root->lchild) + 1 : TreeHeight(root->rchild) + 1;
}

  • 从运行结果可以看出是可以计算出来的,树的高度为4,但是呢却存在一个很大的隐患

这里我们直接画图
在这里插入图片描述
在这里插入图片描述

  • 可以看到我们,如果每一次都不去存储左右子树的高度,他们比较完大小后需要重新递归去求左右子树的高度+1,也就造成了【重复计算】
  • 所以我们说说下面正确代码

代码详细(正确)

int BinaryTreeDeapth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int LeftDepth = BinaryTreeDeapth(root->left);
	int RightDepth = BinaryTreeDeapth(root->right);
	return LeftDepth > RightDepth ? LeftDepth + 1 : RightDepth + 1;
}
  • 这样把递归回调的结果存储起来,然后比较其左右子树的高度直接返回就可以避免重新递归。

查找指定节点

思路分析

  • 想要在一个二叉树中查找指定的节点,无法就是在左右子树的节点里面去找。那么我们先查找左子树,如果找到了,就没必要去查找右子树。如果左右子树都查找完没有找到,则是没有该节点

代码详细

在这里插入图片描述

  • 对于祖宗节点1的左右子树

在这里插入图片描述

  • 强调对于2节点的左右子树
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* LeftNode = BinaryTreeFind(root->left, x);
	if (LeftNode)
	{
		return LeftNode;
	}
	BTNode* RightNode = BinaryTreeFind(root->right, x);
	if (RightNode)
	{
		return RightNode;
	}
	return NULL;
}

销毁二叉树

思路分析

  • 同其他思路,如果像销毁整个二叉树,那么就得先销毁他的左右子树,那么对于左右子树,如果他们也有左右子树,需要先销毁他的左右子树,如果先销毁根节点,就无法找到他的左右子树了就是后序遍历)

代码详细

在这里插入图片描述

void BinaryDestroy(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	BinaryDestroy(&((*root)->left));
	BinaryDestroy(&((*root)->right));
	free(*root);
	*root = NULL;
}
  • 这里二级指针是为了改变实参,如果需要接口统一,可以传一级指针,不过需要把实参手动设置NULL

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

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

相关文章

POK´ELLMON:在宝可梦战斗中实现人类水平的人工智能

人工智能咨询培训老师叶梓 转载标明出处 最近,由美国乔治亚理工学院的Sihao Hu、Tiansheng Huang和Ling Liu发表的论文介绍了POKELLMON,这是一个开创性的基于大模型(LLM)的具身智能体,它在战术战斗游戏中,特…

【Android 笔记】Android APK编译打包流程

前言 本文将介绍Android从一个项目打包成APK的过程,其中涉及Android Java和Kotlin文件、资源文件、清单文件、依赖jar包和so库等在打包过程中处理。 步骤 总体的打包流程如下图,下面就介绍下详细的打包步骤。 1、将aidl文件编译成java文件 在构建过程中…

2024音频剪辑指南:探索四大高效工具!

音频剪辑不仅仅是技术活,更是一种艺术创作,它能够让声音更加生动、更具感染力。今天,我们就来探索几款优秀的音频剪辑工具。 福昕音频剪辑 链接:www.pdf365.cn/foxit-clip/ 福昕音频剪辑是一款界面简洁、操作直观的音频编辑软件…

详解安卓辅助功能服务AccessibilityService(无障碍服务,微信抢红包助手原理)

前言 在手机的更多设置或者高级设置中,我们会发现有个无障碍的功能,很多人不知道这个功能具体是干嘛的,包括我们开发也很少接触这部分功能,以至于对这块不甚了解。前段时间在同事的安利下去了解了下这部分功能。在这里和大家浅谈下…

scikit-learn特征预处理

特征预处理 什么是特征预处理 通过一些转换函数,将特征数据转换成更适合算法模型的特征数据的过程 数值数据的无量纲化: 归一化标准化 特征预处理API sklearn.preprocessing为什么进行无量纲化 通过欧式距离公式计算两个约会对象是否属于同一类别 …

使用HTML和cgi实现网页登录功能

0.HTML文件结构 一.HTML文件 1.test.html <!DOCTYPE html> <html><head><meta charset"utf-8"><title>菜鸟教程(runoob.com)</title></head><body><!-- 将结果提交给/cgi-bin/test.cgi下 --><form actio…

联盟推广计划:释放SaaS企业增长潜力

在SaaS行业&#xff0c;用户增长是企业成功的关键。本文深入探讨联盟推广计划&#xff0c;分析其核心特点和优势&#xff0c;以及如何实施这一策略以实现用户增长和品牌扩展。随着SaaS市场的不断成熟&#xff0c;企业越来越需要创新的营销策略来突破增长瓶颈。PartnerShare联盟…

2025舜宇内推码

舜宇光学集团校招 【2025内推码】 DSwNQ9yu DSwNQ9yu DSJXN8Mr 舜宇光学科技2025校招内推&#xff01;冲冲冲&#xff01; 光学龙头-舜宇集团2025届全球校园招聘正式启动&#xff01;&#xff01;&#xff01; 提供住宿&#xff08;硕士单人间&#xff0c;独立卫浴&#xff0…

【大数据平台】性能优化与成本控制

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

multimodel ocr dataset

InternLM-XComposer2-4KHD InternLM-XComposer2-4KHD a light-weight Vision Encoder OpenAI ViT-Large/14Large Language Model InternLM2-7B, 这篇论文采用的是一种动态分辨率的输入&#xff1b; 全图有一个global view,resize到336*336&#xff1b; 然后把图片resize再pad…

PointPillars算法解析

说明 本篇主要对基于LIDAR的3D目标检测算法PointPillars算法论文进行解析。 论文地址&#xff1a;https://arxiv.org/pdf/1812.05784.pdf 代码地址&#xff1a;https://github.com/open-mmlab/OpenPCDet 参考链接1&#xff1a;https://zhuanlan.zhihu.com/p/357626425 参考链接…

探索数据结构:红黑树的分析与实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 红黑树的介绍 1.1. 红黑树的引入 我们前面学习了AVL树&#xff0c;…

re正则模块

正则是一个十分重要且基础的模块 学习正则模块就要了解正则的一些基本字符 正则的基本方法有很多 但是大体上分为三种匹配 分割 替换 匹配有match search fullmatch findall finditer 注意finditer得到结果是一个可迭代类型需要遍历才能得到结果 使用group方法就可以查看返回…

【安全】XSS

文章目录 xss1.反射型XSS Payload的一些情况010203040506070809101112131415 HTML文档处理过程0x01 HTML解析0x02 URL解析0x03 JavaScript 解析 2.DOM型Ma Spaghet!JefffUgandan KnucklesRicardo MilosAh Thats HawtLigmaMafia 3.存储型 xss 用户的输入没有进行很好的过滤&…

对比新旧两个数据库表之间的差异

ServerDatabaseVersionUpdateHelper 一个对比不同数据库之间表数据差异的开源软件&#xff0c;欢迎大家到github上点赞 应用下载地址 功能介绍 对比表结构差异和表数据之间的差异 并根据查询生成新的更新sql语句 使用 1. 填写新旧数据库配置 server数据库地址;port数据库端…

报错:xx in xx cannot be applied to ‘()‘ @Data注解的无参构造方法不生效(原因及解决办法)

问题描述 创建User类时&#xff0c;添加了Data注解和User的构造方法 import lombok.Data;Data public class User {private Long id;private String name;private Integer age;private String email;public User(Long id, String name, Integer age, String email) {this.id …

机器学习--常见算法总结

有监督学习算法 1. 线性回归算法 概念&#xff1a;线性回归是一种统计方法&#xff0c;用于预测一个变量&#xff08;因变量&#xff09;与一个或多个自变量&#xff08;特征变量&#xff09;之间的关系。目标是通过线性方程建立自变量和因变量之间的关系模型。 作用&#x…

vertical-align: bottom;

问: 这个弹框中, "张三" 文字在某些ios手机中会上升到顶部, 图片也会移动, 西方二维码也会向下移动, 请问什么原因? 回答: 我们在 "张三" 这个元素dt上, 加上了vertical-align: bottom;这个属性, 让这个在顶部的元素在最下面, 就解决了样式错乱的问题.

SCC-F 23212-0-110310控制器abb面价

SCC-F 23212-0-110310控制器面价 SCC-F 23212-0-110310控制器面价 SCC-F 23212-0-110310控制器面价 SCC-F 23212-0-110310控制模块接线图 SCC-F 23212-0-110310控制模块电路图 SCC-F 23212-0-110310控制模块线路图 SCC-F 23212-0-110310伺服电机控制器是数控系统及其他相…

【C语言】最详细的单链表(两遍包会!)

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、单链表的概念 1. 单链表的特点 2. 单链表的基本…