模拟实现链式二叉树及其结构学习——【数据结构】

W...Y的主页 😊

代码仓库分享 💕


之前我们实现了用顺序表完成二叉树(也就是堆),顺序二叉树的实际作用就是解决堆排序以及Topk问题。

今天我们要学习的内容是链式二叉树,并且实现链式二叉树,这篇博客与递归息息相关!

目录

链式存储

二叉树链式结构的实现

链式二叉树的快速创建

二叉树的遍历

前序、中序以及后序遍历

前序遍历的实现

中序遍历的实现

后序遍历实现

节点个数以及高度

总结点个数

叶子节点个数

第k层节点个数

整个代码模板以及验证


链式存储

什么是链式存储,就是用链来指示元素的逻辑关系。链式结构又分为二叉链和三叉链,而我们今天学习的是二叉链表,又称链式二叉树。

我们一般用链表来表示一棵二叉树,通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{struct BinTreeNode* _pLeft; // 指向当前节点左孩子struct BinTreeNode* _pRight; // 指向当前节点右孩子BTDataType _data; // 当前节点值域
}

对于链式二叉树,我们与其他前面的链表、顺序表、堆……数据结构有所不同,我们针对这一块并不是增删查改,为什么呢?

因为链式二叉树的存储方式,就是把每一个节点封装在结构体中然后进行链接, 而我们进行增删查改没有必要在这么复杂的结构中实现,当我有每个节点的左右指针时,我可以随心所欲,在哪里都可以进行插入删除。如果非得使用增删查改,我们就可以使用简单一些的数据结构进行。

所以我们学习链式二叉树是为了给我们后面的高级数据结构AVL、红黑树打基础的。所以我们也要认真学!!!

二叉树链式结构的实现

链式二叉树的快速创建

我们为了快速实现链式二叉树,从中感受到链式二叉树的结构,我们快速手动生成一个链式二叉树。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;int val;
}BTNode;
BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");exit(-1);}node->val = x;node->left = NULL;node->right = NULL;return node;
}
int main(void)
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return 0;
}

我们创建了链式二叉树的基本结构,左指针右指针以及存储的值,然后开辟空间将里面的所有内容初始化。在main函数中手动插入了1、2、3、4、5、6封装在结构体中,然后依照下图进行链接快速得到一个链式二叉树。

 下面依照创建完成的链式二叉树继续学习。

二叉树的遍历

前序、中序以及后序遍历

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

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。 

前序遍历的实现

针对前序遍历,我们记住先访问左树,再访问根,最后再访问右树。我们可以先手动遍历一遍,将一颗大树分解成三部分(左树、根、右树),再将左树看作树继续分成左树右树与根,右数也一样,将其一直进行分解,直到左右树为空为止即可停止。

// 二叉树前序遍历
void PreOrder(BTNode* root)
{if (root == NULL){return;}printf("%d ", root->val);PrevOrder(root->left);PrevOrder(root->right);
}

 在前序遍历中,我们使用递归进行。如果root为NULL证明树为空,直接返回即可。当进入下面代码时,前序遵循的是根、左树、右树。所以我们先将根打印出来,然后遍历左树。当进入左树递归时,root->left进入左树,那根的左子节点就变成了左树的根,打印出来继续递归,依次类推。而右树也是一样的,将左树遍历完后,我们将递归右数,先将左树递归的所有函数销毁,进入root->right中进行递归,根的右子节点成为右树的根,打印出进行递归,一直遵循根、左树、右树进行。

递归图解如下:

 虽然代码非常简洁,但是理解起来不太容易,我们不能只记住代码如何写,应该理解其中的原理才行。

中序遍历的实现

只要我们理解了前序遍历,那么中序后序都是非常简单的存在。只需记住:左树、根、右树,然后写出递归即可

void InOrder(BTNode* root)
{if (root == NULL){return;}InOrder(root->left);printf("%d ", root->val);InOrder(root->right);
}

 有没有发现这串代码与前序遍历非常相似,只是将两行代码交换位置就可以实现。我们先将左树的内容递归完然后打印,再打印根的,最后再打印右树的即可。可以参考前置遍历进行推理。

这里如果实在理解不了的可以认为调用InOrder(root->left)是遍历打印左树,printf是打印根,而调用递归InOrder(root->right)是为了遍历打印右树。

后序遍历实现

我相信大家已经知道函数怎么写了,那我就直接给模板:

void PostOrder(BTNode* root)
{if (root == NULL){return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->val);
}

 下面我们更进一步的学习链式二叉树的结构,上强度!!!

节点个数以及高度

总结点个数

我们需要建立一个函数,求出整个二叉树所有的节点个数。

有人一定会想直接全部遍历一遍然后创建一个全局变量用来统计个数就可以了。没错这个方法是可行的,我们刚才说的二叉树的遍历,然后在这里用一遍不就拿下来了。

int size = 0;
int TreeSize(BTNode* root)
{if (root == NULL)return 0;else++size;TreeSize(root->left);TreeSize(root->right);return size;
}

我们直接遍历整棵树,先遍历左树、再遍历右数,每遍历一个节点size++即可。

但是这个函数有一个极大的缺点,当我们使用函数时,我们可以在任意一个位置去调用,全局变量是存储在静态区的,不会随着函数的结束而销毁,当我们调用过一次后, 再一次去调用此函数,如果没有及时将size归零,结果将会累加起来成为错误的结果。

我们应该优化一下程序:

int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

直接使用递归,将左右子树包含在返回值中,我们在后面加上1,如果递归成功将+1,然后返回最后递归之和。

我们可以做个比喻:在一个大学中校长想要统计全校的人数,校长不可能亲自挨家挨户访问查人,它会通知每个院的院长,然后院长通知每个系的系主任,由系主任通知导员,最后由导员通知每个班的班长来统计人数。一级一级的下放任务。而这个递归也是如此,统计总节点个数就是一级一级下方给各个节点计数。 

叶子节点个数

需要求出链式二叉树的叶子节点个数,叶节点或终端节点:度为0的节点称为叶节点;

大致原理都是一样的,只是条件不同。我们需要叶子节点就必须是度为0的节点,所以一个节点的root->right == NULL && root->left == NULL 这是必要条件,然后我们就应该去遍历二叉树的左子树与右子树去寻找满足条件的节点。最后求出左右子树的叶子节点之和即可。

代码实现:

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

第k层节点个数

当我们需要某一层的节点个数,我们也需要创建一个函数来进行,那我们应该怎么弄呢?

还是一级一级去派遣,假设我们需要第三层的节点个数,当我们刚进入在第一层时,我们距离目标层数还有两层之差,当我们遍历到第二层时,我们距离目标还有一层,当我们进入目标层后我们就要进行遍历节点, 统计出左右子树k层节点之和返回即可。

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

需要注意的是我们先得使用assert进行断言,防止树为空。


整个代码模板以及验证

下面是增章全部代码以及测试用例:

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;int val;
}BTNode;BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");exit(-1);}node->val = x;node->left = NULL;node->right = NULL;return node;
}void PrevOrder(BTNode* root)
{if (root == NULL){return;}printf("%d ", root->val);PrevOrder(root->left);PrevOrder(root->right);
}void InOrder(BTNode* root)
{if (root == NULL){return;}InOrder(root->left);printf("%d ", root->val);InOrder(root->right);
}
void PostOrder(BTNode* root)
{if (root == NULL){return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->val);
}
//总节点个数
//int TreeSize(BTNode* root)
//{
//	static int size = 0;
//	if (root == NULL)
//		return 0;
//	else
//		++size;
//	TreeSize(root->left);
//	TreeSize(root->right);
//	return size;
//}
//int size = 0;
//int TreeSize(BTNode* root)
//{
//	if (root == NULL)
//		return 0;
//	else
//		++size;
//	TreeSize(root->left);
//	TreeSize(root->right);
//	return size;
//}
int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//叶子节点个数
int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->right == NULL && root->left == NULL)return 1;return TreeLeafSize(root->right) + TreeLeafSize(root->left);
}
//第k层节点个数
int TreeKLevel(BTNode* root, int k)
{assert(k > 0);if (root == NULL){return 0;	}if (k == 1){return 1;}return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}int main(void)
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;PrevOrder(node1);printf("\n");InOrder(node1);printf("\n");PostOrder(node1);printf("\n%d", TreeSize(node1));printf("\n%d", TreeSize(node1));printf("\n%d", TreeLeafSize(node1));printf("\n%d", TreeKLevel(node1, 2));return 0;
}

代码运行结果如下:

运行结果分别为前置遍历、中序遍历、后序遍历、总节点个数(两次)、叶子节点个数、第二层节点个数

 参考下图均正确!!!


以上就是本次博客的全部内容,希望可以帮助到大家!!!支持博主的一键三连一下,谢谢大家❤️

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

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

相关文章

Leetcode.712 两个字符串的最小ASCII删除和

题目链接 Leetcode.712 两个字符串的最小ASCII删除和 mid 题目描述 给定两个字符串 s1 和 s2&#xff0c;返回 使两个字符串相等所需删除字符的 ASCII 值的最小和 。 示例 1: 输入: s1 “sea”, s2 “eat” 输出: 231 解释: 在 “sea” 中删除 “s” 并将 “s” 的值(115)加…

Keepalived+LVS高可用集群

目录 一、keepalived介绍&#xff1a; 二、keepalived工具介绍&#xff1a; &#xff08;1&#xff09;管理 LVS 负载均衡软件&#xff1a; &#xff08;2&#xff09;支持故障自动切换&#xff1a; &#xff08;3&#xff09;实现 LVS 负载调度器、节点服务器的高可用性&…

合宙Air724UG LuatOS-Air LVGL API控件-二维码(Qrcode)

二维码&#xff08;Qrcode&#xff09; 示例代码 qrcodelvgl.qrcode_create(lvgl.scr_act(),nil)lvgl.qrcode_set_txt(qrcode,"https://doc.openluat.com/home")lvgl.obj_set_size(qrcode,400,400)lvgl.obj_align(qrcode, nil, lvgl.ALIGN_CENTER, 0, 0)创建 可以通…

【Nginx25】Nginx学习:连接限制和请求限制

Nginx学习&#xff1a;连接限制和请求限制 之前我们就已经学习过了一些和流量限制相关的配置指令&#xff0c;它们是 HTTP 核心配置中的内容 &#xff0c;不记得的小伙伴可以回去看一下 Nginx学习&#xff1a;HTTP核心模块&#xff08;七&#xff09;请求体与请求限流https://m…

第3章_瑞萨MCU零基础入门系列教程之开发环境搭建与体验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

【网络编程】TCP Socket编程

TCP Socket编程 1. ServerSocket2. Socket3. TCP的长短连接4. Socket 通信模型5. 代码示例&#xff1a;TCP 回显服务器 流套接字&#xff1a; 使用传输层TCP协议 TCP: 即Transmission Control Protocol&#xff08;传输控制协议&#xff09;&#xff0c;传输层协议。 TCP的特点…

一文详解二叉搜索树

数据结构-二叉查找树 前言 **摘自百度百科&#xff1a;**二叉查找树&#xff08;Binary Search Tree&#xff09;&#xff0c;&#xff08;又&#xff1a;二叉搜索树&#xff0c;二叉排序树&#xff09;它或者是一棵空树&#xff0c;或者是具有下列性质的二叉树&#xff1a; 若…

JDK8特性——接口增强函数式接口Optional方法引用

文章目录 接口增强默认方法静态方法 函数式接口SupplierConsumerFunctionPredicate Optional 类以前对null 的处理Optional的基本使用Optional的常用方法 方法引用方法引用的格式对象名::方法名类名::静态方法名类名::引用实例方法类名::构造器数组::构造器 接口增强 在JDK8之…

智能远程监考方案助力企业考试化繁为简

在音视频数字化之旅中&#xff0c;轻装上阵。 近年来&#xff0c;在数字化浪潮之下&#xff0c;远程考试频繁成为各领域热词&#xff0c;各企业也纷纷改革求新&#xff0c;将原本的企业内部考试转移到线上&#xff0c;从而获取更低廉的组考成本&#xff0c;更高的管理效率&…

深度学习基础之梯度下降

1. 引言 梯度下降是一种用于最小化&#xff08;或最大化&#xff09;损失函数的优化算法。它是机器学习和深度学习中的一个关键概念&#xff0c;通常用于调整学习算法中的参数。 梯度下降背后的核心思想是迭代调整参数以最小化损失函数。它的工作原理是计算损失函数相对于每个…

数据治理-度量指标

为应对长期学习曲线的阻力和挑战&#xff0c;对数据治理项目必须要有通过证明数据治理参与者如何增加业务价值和实现目标的指标来衡量进展和成功。 为了管理所需的行为变化&#xff0c;要着重衡量数据治理的推广进展、与治理需求和符合程度以及数据治理为组织带来的价值。重点是…

【LeetCode-中等题】18. 四数之和

文章目录 题目方法一&#xff1a;双指针&#xff08;定2动2&#xff09; 题目 方法一&#xff1a;双指针&#xff08;定2动2&#xff09; 这题可以参考【LeetCode-中等题】15. 三数之和 区别在于&#xff0c;三数之和只需要用一个for循环定住一个数&#xff0c;然后设置两个前…

数据结构与算法(C语言版)P4---顺序表、链表总结

顺序表和链表&#xff08;双向带头链表&#xff09;的区别 顺序表&#xff1a; 优点&#xff1a; 支持随机访问。需要随机访问结构支持算法可以很好的使用。cpu高速缓存利用率&#xff08;命中率&#xff09;更高。存储密度高 缺点&#xff1a; 头部中部插入删除时间效率低。…

合宙Air724UG LuatOS-Air LVGL API控件-窗口 (Window)

窗口 (Window) 分 享导出pdf 示例代码 win lvgl.win_create(lvgl.scr_act(), nil) lvgl.win_set_title(win, "Window title") -- close_btn lvgl.win_add_btn_right(win, "\xef\x80\x8d") -- --lvgl.obj_set_event_cb(cl…

典型数据结构-图,图的存储、基本操作和遍历

图 引自&#xff1a;《数据结构教程》。 概念 图可以使得元素之间的关系是 多对多。图中任意两个数据元素之间都可能存在连接关系。图作为一种数据结构&#xff0c;可以表达数据元素之间广泛存在着的更为复杂的关系。在众多应用之中&#xff0c;如电子线路分析、工程计划分析、…

Junit单元测试异常处理方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Junit单元测试异常处理方法 前言案例准备一、类方法内处理异常二、测试方法中处理异常1.try/catch/finally 语句2.Test(expected)3.ExpectedException 前言 提示&#xff1a…

深度解析shell脚本的命令的原理之mv

mv 是 Unix 或 Linux 中的一个基本命令&#xff0c;用于移动或重命名文件和目录。以下是对这个命令的深度解析&#xff1a; 基本操作&#xff1a;mv 命令的基本操作是将一个或多个源文件或目录移动到一个目标文件或目录&#xff0c;或者重命名源文件或目录。这是通过改变文件系…

详细指南:基于差分进化的马尔可夫链蒙特卡罗加速技术在MATLAB中的应用

第一部分:概念简介与基础知识 1. 什么是马尔可夫链蒙特卡罗 (Markov Chain Monte Carlo, MCMC)? 马尔可夫链蒙特卡罗是一种通过马尔可夫链来估计复杂分布的统计方法。通过构建一个特定的马尔可夫链,使其平稳分布等于目标分布,我们可以从该马尔可夫链中抽取样本来估计目标…

银河麒麟--国产操作系统-九五小庞

那么&#xff0c;我国国产操作系统现状到底如何呢&#xff1f; 自 1999 年徐冠华部长一语点破我们的产业软肋之后&#xff0c;国产操作系统起步于国家“七五”计划期间&#xff0c;目前国产操作系统均是基于Linux内核进行的二次开发&#xff0c;中国国产操作系统进入Linux元年…

CSS:隐藏移动端的滚动条的方式

目录 方式一&#xff1a;-webkit-scrollbar方式二&#xff1a;overflow方式三&#xff1a;clip-path方式四&#xff1a;mask 遮罩总结参考 移动端开发中&#xff0c;有一个横向滚动元素&#xff0c;产品告诉我不需要滚动条&#xff0c;我说这个简单&#xff0c;隐藏一下不就行了…