【数据结构和算法初阶(C语言)】二叉树的链式结构--前、中、后序遍历实现详解,节点数目计算及oj题目详解---二叉树学习日记③

目录

​编辑

1.二叉树的链式存储

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

2.1树的创建 

2.2二叉树的再理解

3.链式结构二叉树的遍历

3.1前序遍历实现:

​编辑

3.2中序遍历实现

3.3后序遍历

​编辑

4.链式二叉树节点数目的计算 

4.1 总节点个数的计算

错误代码1:

错误代码2:

最优解:递归子问题划分解 

4.2叶子节点个数的计算

4.3第k层节点个数的计算

5.二叉树查找节点值为x的节点

错误代码1:

正确代码:

6、二叉树的层序遍历

6.1利用层序遍历判断树是否为完全二叉树

6.2计算二叉树的高度

7.二叉树的创建

8.二叉树的销毁 

 9.结语


1.二叉树的链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链。

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

2.1树的创建 

手动快速创建一棵简单的二叉树

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{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 node1;
}

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解

2.2二叉树的再理解

二叉树是: 1. 空树 2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

3.链式结构二叉树的遍历

二叉树的遍历 

二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次。

二叉树的遍历有:前序/中序/后序的递归结构遍历:

  • 1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。根--左子树-右子树
  • 2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。左子树--根--右子树
  • 3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。左子树---右子树--根

3.1前序遍历实现:


//前序遍历
void PrevOrder(BTNode* root)
{if (root == NULL){printf(" null ");return;}//不为空打印节点内容printf("%d ", root->val);//遍历每个节点的左子树PrevOrder(root->left);//遍历每个节点的右子树PrevOrder(root->right);
}

递归展开图为:

3.2中序遍历实现

//中序遍历
void Inrder(BTNode* root)
{if (root == NULL){printf(" null ");return;}//遍历每个节点的左子树Inrder(root->left);//不为空打印节点内容printf("%d ", root->val);//遍历每个节点的右子树Inrder(root->right);
}

递归展开图为:

3.3后序遍历

void EndOrder(BTNode* root)
{if (root == NULL){printf(" null ");return;}//遍历每个节点的左子树EndOrder(root->left);//遍历每个节点的右子树EndOrder(root->right);//不为空打印节点内容printf("%d ", root->val);}

4.链式二叉树节点数目的计算 

4.1 总节点个数的计算

简单思想:遍历一遍

错误代码1:

int TreeSize(BTNode* root)
{int size = 0;//记录树节点的个数if (root == NULL){return 0;}else{size++;//节点不为空,size+1}TreeSize(root->left);TreeSize(root->right);return size;}

错误原因:每次调用函数,都会重新定义一个size,且赋值为0,当函数调用结束,变量销毁,size并没有带回调用这个函数的函数,最后输出的size为:1 

错误代码2:

对上一个代码改进,将局部变量定义为全局变量:
局部的静态全局变量只会被执行一次

}//求二叉树的节点个数
int TreeSize(BTNode* root)
{static int size = 0;//记录树节点的个数if (root == NULL){return 0;}else{size++;//节点不为空,size+1}TreeSize(root->left);TreeSize(root->right);return size;}

 

执行结果没有问题,但是当我们第二次调用这个函数或者再次计算其他树的节点个数的时候:

看到了明显的错误。第二次调用没有进行初始化,不符合用户的预期,因为static修饰的变的生命周期是全局,但是作用域没有扩张,不能手动修改。

修改方式为将size定义为全局变量,然后手动每次调用前都初始化一下size:

int size = 0;//记录树节点的个数
int TreeSize(BTNode* root)
{if (root == NULL){return 0;}else{size++;//节点不为空,size+1}TreeSize(root->left);TreeSize(root->right);return size;}int ret = TreeSize(node1);
printf("%d ", ret);
size = 0;
int obj = TreeSize(node1);
printf("%d ", obj);

最优解:递归子问题划分解 

原理图解:

return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;

如果节点为空就返回0,如果不为空就返回左子树节点个数+右子树几点个数+自身

 

执行过程如图:红色代表向下执行,绿色代表返回:

4.2叶子节点个数的计算

思想:是空节点就返回0,是叶子节点就返回1,不是叶子节点就返回左子树的叶子节点数+右子树的叶子节点数。

叶子节点的特征:左子树与右子树为空

//叶子节点个数
int TreeLeaSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL)//叶子节点{return 1;}//不是空也不是叶子节点:return TreeLeaSize(root->left) + TreeLeaSize(root->right);}

4.3第k层节点个数的计算

当层数减少到1,就返回这层节点的个数,每个节点都返回1,如果要求的k层没有节点就返回空

//第K层节点的个数
int TreeLevel(BTNode* root, int k)
{assert(k > 0);//层数都是正的,防止用户传错if (root == NULL){return 0;//树不存在或者遍历到空节点}if (k == 1){return 1;//递归到要求的那一层了}return TreeLevel(root->left, k - 1) + TreeLevel(root->right, k - 1);}

 

5.二叉树查找节点值为x的节点

推荐使用前序遍历效率高,后序遍历要最后才找根,根就遍历来两次,效率低了一层。

思路:如果根是就返回,如果不是就到左右子树中去找。

错误代码1:

BTNode* TreeFind(BTNode* root, int x)
{if (root == NULL){return NULL;}if (root->val = x){return root;}TreeFind(root->left, x);TreeFind(root->right, x);

问题:到左右子树中去寻找过后,加入找到返回调用这个函数的函数中没有接收这个返回值的东西。

正确代码:

BTNode* TreeFind(BTNode* root, int x)
{if (root == NULL){return NULL;}if (root->val = x){return root;}BTNode* ret = NULL;ret = TreeFind(root->left, x);if (ret){return ret;}ret = TreeFind(root->right, x);if (ret){return ret;}return NULL;//左右都没有找到}

6、二叉树的层序遍历

使用队列的思想,将树的根节点先入队列,将后面所有节点带出。

void LevelOrder(BTNode* root)
{Queue q;QUeueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//取队头数据printf("%d ", front->val);if (front->left){QueuePush(&q, front->left);}if (front->right){QueuePush(&q, front->right);}//删除头部元素QueuepPop(&q);}QueueDestory(&q);
}

6.1利用层序遍历判断树是否为完全二叉树

层序遍历:非空节点连续就是完全二叉树,非空节点不连续,非空节点中间有节点就不是完全二叉树

void LevelOrder(BTNode* root)
{Queue q;QUeueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//取队头数据printf("%d ", front->val);if (front == NULL){break;//遇到空节点,跳出这层循环}QueuePush(&q, front->left);QueuePush(&q, front->right);//删除头部元素QueuepPop(&q);}//已经遇到空节点,如果队列后面的节点还有非空就不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//取队头数据QueuepPop(&q);if (front != NULL){QueueDestory(&q);return false;}}QueueDestory(&q);return false;
}

6.2计算二叉树的高度

思想:分解为左树和右数高度大的那一颗+1得到树的高度

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

 这个代码不好的点在于,比较完过后,还会递归重复一次,修改:

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

7.二叉树的创建

使用前序遍历的方法创建二叉树:

代码实现为:

BTNode* CreatTree(char* str ,int*pi)
{if(str[*pi]=='#'){//如果此时字符为#,这个点就是空,不用申请节点(*pi)++;return NULL;}//不为空就申请节点BTNode* root = (BTNode*)malloc(sizeof(BTNode)*1);root->val = str[*pi];(*pi++);//然后递归连接左右子树root->left = CreatTree(str,pi);root->right= CreatTree(str,pi);return root;}

8.二叉树的销毁 

由于每个节点都是在堆上malloc出来的,所以用完就销毁,返还给操作系统。

void TreeDestory(BTNode* root)
{if (root == NULL){return;}TreeDestory(root->left);TreeDestory(root->right);free(root);//root == NULL;这里不用置空,这里的root只是实参的零时拷贝//可以通过传入二级指针的方式来指针也可以在主函数置空}

 9.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,数据结构内容持续更新中,感谢大家的关注与喜欢。

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

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

相关文章

Java毕业设计-基于springboot开发的游戏分享网站平台-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示(源代码在文末)二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、后台登录2.1管理员功能模块2.2用户功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开发的…

【Python基础教程】3 . 算法的时间复杂度

🎈个人主页:豌豆射手^ 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:python基础教程 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、…

解决npm init vue@latest证书过期问题:npm ERR! code CERT_HAS_EXPIRED

目录 一. 问题背景 二. 错误信息 三. 解决方案 3.1 临时解决办法 3.2 安全性考量 一. 问题背景 我在试图创建一个新的Vue.js项目时遇到了一个问题:npm init vuelatest命令出现了证书过期的错误。不过这是一个常见的问题,解决起来也简单。 二. 错误…

关于Devc++调试的问题以及解决STL变量无法查看

目前Devc的调试主要有以下几点: 1.调试不能直接查看stl变量,会卡死不动 2.目前单步进入只能用鼠标键按 3.若想按下一步进入函数体内,要在函数体内打上断点才行 4.调试到return 0 ;上一句就停了,不会结束程序 5.目前F2跳至断点…

内网靶机~~dc-2

一、信息收集 1.端口扫描: nmap -sV -p 1-10000 10.1.1.4 2.CMS识别 3.目录扫描: dirsearch http://10.1.1.4/ 4.FLAG1 似乎让我们用cewl生成密码字典,并爆破登录。 cewl -w rewl_passwd.txt http://dc-2/index.php/flag/ 总结&#xff…

一、JAVA集成海康SDK

JAVA集成海康SDK 文章目录 JAVA集成海康SDK前言一、项目依赖 jar1. examples.jar2. 项目依赖 jna.jar,可以通过 maven依赖到。二、集成SDK1.HcNetSdkUtil 海康 SDK封装类2.HCNetSDK3.Linux系统集成SDK三、总结前言 提示:首先去海康官网下载 https://open.hikvision.com/dow…

cPanel中如何添加邮箱黑/白名单

最近我们的邮箱经常收到推广类型的垃圾邮件,我们向我们的虚拟主机提供商Hostease咨询后,他们建议可以将这些可疑的推广邮箱加入到黑名单中,防止再次受到骚扰,下面我就介绍如何添加邮箱的黑/白名单。 首先我们要进入到Hostease虚拟…

eNSP综合实验(PPP认证、VPN配置、RIP协议、NAT)

题目如上 第一步:配置IP地址 ip分配如下图所示 开始配置IP(PC省略) R1: [R1]undo [R1]undo in [R1]undo info-centere [R1]undo info-center e [R1]undo info-center enable Info: Information center is disabled. [R1]int g0/0/0 [R1-Gigabit…

什么是framebuffer,怎么应用(二)————如何打印BMP图片、字幕函数、字符串

如何切换到终端模式 在昨天写的文章中,没有写到如何切换到终端模式,在编译完函数之后,我们需要从桌面切换到终端模式: ALTCTRLF3切换到终端模式后,登录账号名与密码,其余操作均有桌面终端一样。 如何切换…

HTML input 实现回车切换到下一个输入框功能

前言 遇到需求&#xff0c;在客户填写单子时&#xff0c;有多个输入框&#xff0c;为了省事&#xff0c;不需要频繁移动光标填写。 实现效果 实现方式一 HTML <input type"text" name"serialNumber1" onkeydown"cursor(this);"/><in…

spring多线程实现+合理设置最大线程数和核心线程数

1.最简单的方法&#xff1a; 需要在 Spring Boot 主类上添加 EnableAsync 注解启用异步功能&#xff1b;需要在异步方法上添加 Async 注解。 示例代码如下&#xff1a; SpringBootApplication EnableAsync public class Application {public static void main(String[] args…

什么是DevOps?如何使用DevOps?

无论您是在维持公司基础设施的正常运行&#xff0c;还是在为客户的IT问题管理提供支持&#xff0c;抑或是在构建、测试或修复软件&#xff0c;还是在保护同事免受安全威胁&#xff0c;您都可能接触过 DevOps。 毕竟&#xff0c;这个术语已经出现了 15 年&#xff0c;其采用率也…