数据结构之二叉树的链式结构——递归的暴力美学

1. 实现链式的二叉树结构

我们之前用顺序表里面数组的底层结构实现了二叉树中堆的结构,但是不是所有的二叉树都具有着堆的性质,所以我们现在需要一个链式结构来描述普遍的二叉树。其底层结构类似一个链表,但是每一个结点由单个区域,一个数据域,用来存储数据,一个是指向左孩子的指针,一个是指向右孩子的指针,这个在前面数据结构之堆(1)的文章里面有提到。所以这个结构的实现结构如下:

//我们可以灵活的使用typedef来进行定义,以便今后方便改变数据类型
typedef char BTDataType;
typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;

 通过上面的结构,我们可以试着自己手动创建一个存储着1 2 3 4 5 6 的完全二叉树:

首先链表的手动创建,一定要记住要先写一个创建一个实实在在的结点的函数:

//创建一个二叉树的结点
BTNode* buynode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->left = newnode->right = NULL;return newnode;
}

 上面和链表思想差不多。

然后就可以手动创建二叉树的链表结构了:

void test()
{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 = node3;node2->left = node4;node2->right = node5;node3->left = node6;
}

 1.2 前中后序遍历

我们创建一个二叉树的链表结构以后,就不能像堆的数组结构那样通过下标来对二叉树进行遍历。所以在这里我们提供了普遍的前中后序三种遍历方法,我们先大概这三种遍历方法的遍历规则:

前序遍历——“根左右”

中序遍历——“左根右”

后序遍历——“左右根”

 1.2.1 前序遍历

前序遍历的遍历规则是“根左右”,即对于一个结点来说,先把它本身当成临时根结点,以它为根结点,先读取他本身,再读取他的左孩子结点,最后读取他的右孩子结点。

记住,读取到谁就把谁当做临时根节点,直到这个临时根节点的左右结点都为空结点,才可以真正的进行下一步的回归。这就是读取中递归过程中结束条件的重要性!!

比如对于这个二叉树:

它通过前序遍历的结果应该是1 2 3 4 5 6。

 现在我们可以对于前序遍历进行代码的实现,大家可以用自己的已经写好的代码在前面手动创建的二叉树里面进行测试,就可以及时纠正代码的错误。

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

 1.2.2 中序遍历

中序遍历的遍历规则是“左根右”,即对于一个结点来说,先把它本身当成临时根结点,以它为根结点,先读取他的左孩子结点,再读取他的本身,最后读取他的右孩子结点。

一样要注意,读取到谁,就先把他当作临时的根结点,一直一直先读取他的左孩子结点,直到左孩子结点为空为止 ,再回归自己,再读取他的右孩子节点。

 用中序遍历法遍历上面的二叉树的话,结果就是3 2 1 5 4 6。

现在对中序遍历进行代码的实现:

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

 1.2.3 后序遍历

后序遍历的遍历规则是“左右根”,即对于一个结点来说,先把它本身当成临时根结点,以它为根结点,先读取他的左孩子结点,再读取他的右孩子结点,最后读取他本身。

用后序遍历法遍历上面的二叉树的话,结果就是3 2 5 6 4 1。 

现在对后序遍历进行代码的实现:

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

 1.2.4 层序遍历

还有一个方法,是层序遍历,层序遍历的规则就是从上到下依次从左到右读取每一层的数据。

用层序遍历的方法遍历上面的二叉树的话,结果就是1 2 4 3 5 6。 

层序遍历的方法要用到队列的运用,因为队列有一个数据先进先出的规则。

具体的实现方法是:

1.先将二叉树的根结点这个结点(注意不是数据)从队列的队尾进入到队列

2.然后再将它读取出来(放在一个临时变量front里面)

3.再将它从队头删除

4.然后将这个结点的左右孩子结点按左右顺序从队尾依次插入

5.再把此时的第一个结点读取出来。

6.重复这个过程,直到队列为空。

这里需要用到队列,我们在写代码的时候记得引用一下队列的实现代码的头文件和源文件。因为在之前的文章中有详细讲解,这里就不再赘述。

// 层序遍历
//层序遍历的意思是二叉树里每一层的数据从左到右依次遍历
//在这里需要用到队列,因为队列的性质是先入先出
void BinaryTreeLevelOrder(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, root->left);}if (front->right){QueuePush(&q, root->right);}}//队列为空QueueDestroy(&q);
}
 判断二叉树是否是完全二叉树

掌握了这个方法后,我们可以利用这个思维进行完全二叉树的判断。我们知道,完全二叉树是一个有序的,除了最后一层以外的层的结点数都是满的,并且最后一层的结点是有序的,所以如果将一个完全二叉树按照上面的方法往队列里面进行插入和提取的话,如果front等于空了,该队列里面的元素也应该全部为空,如果不为空,就证明该二叉树不是完全二叉树。大家可以自己画图举例试验一下。

所以注意,在进行代码实现时候,要记得把空结点也要放进队列当中。

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);//结束条件是一直取一直取,直到把队列里面的数据都取完为止while (!QueueEmpty(&q)){//先取对头这个结点BTNode* front = QueueFront(&q);//然后将这个节点删除掉QueuePop(&q);//如果front结点是一个空结点,直接跳出循环,去判断后面的结点里面有没有不为空的节点if (front == NULL){break;}//如果front结点不为空,将这个结点的左右孩子结点放插入进去,空结点也要插入QueuePush(&q, root->left);QueuePush(&q, root->right);}//break后,队列不一定为空while (!QueueEmpty(&q)){//先取对头这个结点BTNode* front = QueueFront(&q);//然后将这个节点删除掉QueuePop(&q);//如果front指针不为空,那就不是完全二叉树if (front != NULL){QueueDestroy(&q);return false;}}//后面都为空指针,那就是完全二叉树QueueDestroy(&q);return true;
}

 1.2.5 链式二叉树结构的其他实现——递归思想

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}// 二叉树叶子节点个数(叶子结点是指没有左右结点的结点)
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层节点个数
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);
}//求二叉树的深度/高度
int BinaryTreeDepth(BTNode* root)
{if (root == NULL){return 0;}int left = BinaryTreeDepth(root->left);int right = BinaryTreeDepth(root->right);return left > right ? left + 1 : right + 1;
}// 二叉树查找值为x的节点,是各个结点都不一样的情况
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}BTNode* leftfind = BinaryTreeFind(root->left, x);if (leftfind){return leftfind;}BTNode* rightfind = BinaryTreeFind(root->right, x);if (rightfind){return rightfind;}return NULL;
}// 二叉树销毁
//要进行销毁操作,实参指针也得被销毁,形参的变化如果要影响实参,就得传实参的地址,
// 因为实参是一个一级指针,所以我们得用二级指针来接受实参的地址
void BinaryTreeDestory(BTNode** root)
{//先销毁左子树,再销毁右子树,最后销毁根节点//然后根节点继续是上一个结点左子树,依次递归if (*root == NULL){return;}BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));//一直递归到该root的左右节点都为NULL,才可以free掉rootfree(*root);*root == NULL;
}

2. OJ -通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

 这个需要强烈的递归思想,我总结了一下递归思想的注意事项:

1.一定要注意合适的截止条件

2.在进入递归的时候,要重新将整个程序走一遍,在到达截止条件得到返回值后,一定要注意要继续走完回归的这个函数接下来的函数操作

    //scr就是一个数组指针,n为数组中的元素个数,*pi为数组的下标
BTNode* BinaryTreeCreate(BTDataType* src, int n, int* pi)
{//若*pi大于n(即原数组已经走完)或者走到了#就返回NULLif (*pi >= n || src[*pi] == '#'){(*pi)++;//*pi要一直往后走return NULL;}//要创建好二叉树的结点BTNode* cur = (BTNode*)malloc(sizeof(BTNode));//前序遍历,就按根左右遍历cur->data = src[*pi];(*pi)++;cur->left = BinaryTreeCreate(src, n, pi);cur->right = BinaryTreeCreate(src, n, pi);return cur;
}

希望对大家有所帮助。

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

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

相关文章

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-31目录1. Large Language Models for Manufacturing摘要创新点算法模型实验效果(包含重要数据与结论)推荐…

利用SpringBoot构建城镇住房保障平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理城镇保障性住房管理系统的相关信息成为必然…

【笔记】扩散模型(九):Imagen 理论与实现

论文链接:Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding 非官方实现:lucidrains/imagen-pytorch Imagen 是 Google Research 的文生图工作,这个工作并没有沿用 Stable Diffusion 的架构,而是级…

Windows下载安装Ollama本地运行大模型,新手详细

目录 1. 下载安装Ollama2. 环境配置- 关闭开机自启动(可选):- 配置环境变量(必须):- 配置端口(可选):- 允许浏览器跨域请求(可选): 3.…

代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础

目录 图论理论基础 深度优先搜索理论基础 卡玛网 98.所有可达路径 广度优先搜索理论基础 图论理论基础 图论理论基础 | 代码随想录 图的基本概念 图的种类 大体分为有向图和无向图。 图中的边有方向的是有向图: 图中的边没有方向的是无向图: 图…

牛客练习赛131(dp,dfs,bfs,线段树维护等差数列)

文章目录 牛客练习赛131(dp,dfs,bfs,线段树维护等差数列)A. 小H学语文B. 小H学数学(dp、偏移值)C. 小H学生物(DFS、树上两点间路径的距离)D. 小H学历史(BFS)E. 小H学物理…

干货分享篇:Air780EP的硬件设计原理全解析(上)

一、绪论 Air780EP是一款基于移芯EC718P平台设计的LTE Cat 1无线通信模组。支持FDD-LTE/TDD-LTE的4G远距离无线传输技术。另外,模组提供了USB/UART/I2C等通用接口满足IoT行业的各种应用诉求。 二、综述 2.1 型号信息 表格 1:模块型号列表 2.2 主要性能…

Python将Word文档转为PDF

将word转pdf,只能使用办公工具,但是这些工具大都是收费。因此想用python 将word转pdf,发现很好用特此记录下。方法一:使用docx2pdf模块将docx文件转为pdf 要实现这样的功能,需要用到的就是 docx2pdf 这个python第三方库。对于doc…

无惧任天堂的法律威胁:Switch模拟器Ryujinx v1.2.72版发布

此前任天堂向多个提供 Nintendo Switch 模拟器项目发送律师函甚至直接起诉,要求这些项目立即停止更新、删除以及向任天堂提供经济赔偿。其中 Ryujinx 项目已经在 2024 年 10 月 1 日因任天堂的法律威胁而放弃项目,不过很快就有分叉版本出现,这…

JavaWeb——Web入门(6/9)-HTTP协议:协议解析(客户端的 HTTP 协议解析、服务端的 HTTP 协议解析、Web服务器的作用)

目录 概述 客户端的 HTTP 协议解析 服务端的 HTTP 协议解析 Web服务器的作用 概述 了解完 HTTP 协议的请求数据格式以及响应数据格式之后,接下来我们来讲了解 HTTP 协议的解析。 HTTP 协议的解析分为客户端和服务端两个部分,客户端浏览器中内置了解…

操作系统-实验报告单(2)

目录 1 实验目标 2 实验工具 3 实验内容、实验步骤及实验结果 一、自定义操作系统并启动 1. 最简单操作系统的编写并生成镜像文件 2.虚拟机启动操作系统 【思考题:1、仔细阅读helloos.nas,结合操作系统启动过程尝试分析它的作用;2、若…

城镇住房保障:SpringBoot系统优化技巧

3系统分析 3.1可行性分析 通过对本城镇保障性住房管理系统实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本城镇保障性住房管理系统采用SSM框架,JA…

FlyMcu串口下载STLink Utility

1、FlyMcu FlyMcu串口下载,同STC-ISP(51单片机下载)。 使用步骤: 1、STM32的USART1通过串口转usb连接到电脑 2、通过keil生成Hex、bin文件 生成bin、hex文件可参考 keil生成bin文件(简单)-CSDN博客 创建…

aws(学习笔记第十课) 对AWS的EBS如何备份(snapshot)以及使用snapshot恢复数据,AWS实例存储

aws(学习笔记第十课) 对AWS的EBS如何备份(snapshot)以及使用snapshot,AWS实例存储 学习内容: 对AWS的EBS如何备份AWS实例存储EBS和实例存储的不足 1. 对AWS的EBS如何备份(snapshot)以及使用snapshot恢复数…

论文2—《基于柔顺控制的智能神经导航手术机器人系统设计》文献阅读分析报告

论文报告:基于卷积神经网络的手术机器人控制系统设计 摘要 本研究针对机器人辅助微创手术中定向障碍和缺乏导航信息的问题,设计了一种智能控制导航手术机器人系统。该系统采用可靠和安全的定位技术、7自由度机械臂以及避免关节角度限制的逆运动学控制策…

《数据结构与算法》二叉树基础OJ练习

二叉树的基础知识详见:《数据结构与算法》二叉树-CSDN博客 1 单值二叉树 思路 我们把树分成当前树(用根和左孩子还有右孩子进行比较,如果左孩子或者右孩子为空那就不比了,如果左右孩子或者其中一个存在就比较,相等就是…

栈和队列(C 语言)

目录 一、栈1. 栈的概念2. 栈的结构3. 栈的实现思路4. 栈的实现代码 二、队列1. 队列的概念2. 队列的结构3. 队列的实现思路4. 队列的实现代码5. 循环队列 一、栈 1. 栈的概念 栈是一种特殊的线性表,只允许在固定的一端进行插入和删除操作,该端被称为栈…

自动化测试工具Ranorex Studio(二十五)-库的拆分

默认地,每一个Ranorex Studio项目包含一个对象库文件,这个文件自动用在每一个新创建的录制中。你可以在一个单独的库文件中管理一个测试套件项目中所有的UI元素,但是在一个自动化测试项目中多个对象库的存在还是有一些原因的: .测…

Centos下安装Maven(无坑版)

Linux 安装 Maven Maven 压缩包下载与解压 华为云下载源,自行选择版本 下面的示例使用的是 3.8.1 版本 wget https://repo.huaweicloud.com/apache/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz解压 tar -zxvf apache-maven-3.8.1-bin.tar.gz移…

99、Python并发编程:多线程的问题、临界资源以及同步机制

引言 多线程技术的引入,可以帮助我们实现并发编程,一方面可以充分利用CPU计算资源,另一方面,可以在用户体验上带来极大的改善。但是,多线程技术也存在一些问题。本文就来简单聊一下多线程引入导致的问题,以…