链式二叉树的前,中,后序遍历 AND 结点个数及高度等 文末附带全部代码

目录

  • 前言
  • 1. 前序遍历
  • 2. 中序遍历
  • 3. 后续遍历
  • 4. 二叉树结点的个数
  • 5. 二叉树叶子结点个数
  • 6. 二叉树的高度
  • 7. 二叉树第K层结点的个数
  • 8. 二叉树查找值为x的结点
  • 全部代码
  • 总结


正文开始

前言

本文旨在介绍二叉树的链式存储中一些函数的实现

博客主页: 酷酷学!!!

更多文章, 期待关注~


前置说明: 在学习二叉树的基本操作前, 需要先创建一棵二叉树, 然后才能学习相关的基本操作. 由于目前阶段对二叉树结构掌握不够深入, 为了降低大家学习成本, 此处手动快速创建一棵简单的二叉树, 快速进入二叉树操作学习, 等二叉树结构了解查差不多时, 我们反过头来再研究二叉树真正的创建方式.

typedef int BTDatatype;typedef struct BTNode
{BTDatatype data;struct BTNode* left;struct BTNode* right;
}BTNode;BTNode* BuyNode(int x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}BTNode* CreateTree()
{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;
}

注意: 上述代码并不是创建二叉树的方式, 真正创建二叉树方式期待后续博客

首先实现下面的方法之前我们先来回顾一下二叉树:

什么是二叉树?

  1. 空树
  2. 非空:根结点,根结点的左子树、根结点的右子树组成的。

在这里插入图片描述

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

1. 前序遍历

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

在这里插入图片描述

我们先来看前序遍历:

前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。简单来说遍历的顺序为 根 -> 左子树 ->右子树

递归的本质: 拆成当前问题和子问题, 返回条件: 最小规模的子问题

我们可以把大问题化小, 首先拆成根, 左子树, 右子树的结构, 然后左子树 又可以拆成 根 左子树 右子树的结构, 结束条件为树为空树.

代码实现:

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

运行结果:
在这里插入图片描述

前序遍历递归图解:

在这里插入图片描述

如图我们可以看出:

前序遍历依次访问的数据为:
1 2 3 4 5 6
如果加上对空树的访问, NULL简写为N:
1 2 3 N N N 4 5 N N 6 N N

下面为递归展开图, 不太理解的伙伴可以参考:

在这里插入图片描述

2. 中序遍历

中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。

即, 每棵树 先访问左子树, 在访问根, 最后访问右子树

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

递归展开图:

在这里插入图片描述

3. 后续遍历

后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

即, 每一棵树,先访问左子树, 在访问右子树, 最后访问根

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

4. 二叉树结点的个数

求二叉树结点的个数, 这里初学者很容易先到使用遍历来求结点个数,如下:

// 错误示范
int TreeSize(BTNode* root)
{static int size = 0;if (root == NULL)return 0;else++size;TreeSize(root->left);TreeSize(root->right);return size;
}

首先做个铺垫再来讲解这段代码的问题

静态全局变量和静态局部变量的区别在于作用域和生存周期。

静态全局变量: 静态全局变量在整个文件中都是可见的,即其作用域为整个文件。静态全局变量只能被当前文件内的函数访问,其他文件无法访问。静态全局变量的生存周期为整个程序的执行期间,即在程序启动时分配内存,在程序结束时释放内存。

静态局部变量: 静态局部变量只在定义它的函数内部可见,即其作用域为定义它的函数内部。静态局部变量的生存周期为整个程序的执行期间,即在程序启动时分配内存,在程序结束时释放内存。与普通局部变量不同的是,静态局部变量只会在第一次进入函数时初始化一次,之后每次进入函数都会保留上一次的值。

静态全局变量和静态局部变量都可以被修改,但是有一些区别:

静态全局变量: 静态全局变量可以被当前文件内的任何函数修改,因为其作用域为整个文件。其他文件无法直接修改静态全局变量。但是,由于其作用域广泛,可能会被不同函数多次修改,导致程序的可维护性降低。

静态局部变量: 静态局部变量只能在定义它的函数内部被修改,其他函数无法直接修改静态局部变量。由于其作用域限制在函数内部,静态局部变量对于其他函数来说是不可见的,因此可以更好地控制变量的访问权限。此外,静态局部变量在函数调用之间保持其值不变,可以用于在函数调用之间保持状态或者记录某些信息。

因为每次调用函数size都是在栈区开辟的局部变量, 当函数结束后局部变量也会被系统回收, 这里用了static来修饰size, 看似没什么问题, 但是如果多次调用函数问题就来了

在这里插入图片描述
那么如何解决, 我们可以使用全局变量:

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

但是上述的写法虽然可以, 但是每次调用之前都需要重置size的值,过于麻烦

int size = 0;
TreeSize(root, &size);
printf("TreeSize:%d\n",size);size = 0;
TreeSize(root, &size);
printf("TreeSize:%d\n", size);

最好的解决方案还是回归本质, 因为二叉树本身就是递归定义的, 我们采用递归的思想, 分而治之, 问题就迎刃而解

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

首先分为左子树和右子树, 每一棵树都有左子树和右子树, 我们只需要统计每一棵树的左子树和右子树总结点个数加上自己.为空就等于0, 不为空左子树结点个数加上右子树结点个数, 返回给上一层
在这里插入图片描述

5. 二叉树叶子结点个数

叶子结点的个数, 空树就返回0, 递归的思想, 如果左子树和右子树都是空, 则就是叶子节点, 返回1, 统计左子树和右子树的叶子节点个数即可.

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

6. 二叉树的高度

还是采用递归的思想, 一棵树的高度等于左子树的高度与右子树的高度较高的那个树加+1, 结束条件空树就是0.

在这里插入图片描述

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

7. 二叉树第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);

将复杂度问题划分为子问题, 每一层即下一层的k-1层结点的个数, 如果k==1则返回1.

在这里插入图片描述

8. 二叉树查找值为x的结点

BTNode* BinaryTreeFind(BTNode* root, BTDatatype x)
{if (root == NULL){return NULL;}if (root == x){return root;}BTNode* ret1 = BinaryTreeFind(root->left, x);if (ret1){return ret1;}BTNode* ret2 = BinaryTreeFind(root->right, x);if (ret2){return ret2;}return NULL;}

这里要返回一个结点, 因为是递归调用函数, 所以返回值会逐层返回, 将树划分为子问题, 现在如果为NULL,则返回NULL, 如果找到了就返回该结点, 没找到就调用函数去查找左子树与右子树, 如果左子树找到了就返回左子树的结点不需要去右子树查找了, 如果都没找到则返回NULL.

全部代码

Tree.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int BTDatatype;typedef struct BTNode
{BTDatatype data;struct BTNode* left;struct BTNode* right;
}BTNode;BTNode* BuyNode(int x);void PrevOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root);//二叉树结点的个数
int BinaryTreeSize(BTNode* root);
//二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
//二叉树的高度
int TreeHeight(BTNode* root);//二叉树第K层结点的个数
int BinaryTreeLevelKSize(BTNode* root ,int k);//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDatatype x);

Tree.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"BTNode* BuyNode(int x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}void PrevOrder(BTNode* root) 
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}void PostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}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);
}int TreeHeight(BTNode* root)
{if (root == NULL){return 0;}int left = TreeHeight(root->left);int right = TreeHeight(root->right);return left > right ? left + 1 : right + 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);
}BTNode* BinaryTreeFind(BTNode* root, BTDatatype x)
{if (root == NULL){return NULL;}if (root == x){return root;}BTNode* ret1 = BinaryTreeFind(root->left, x);if (ret1){return ret1;}BTNode* ret2 = BinaryTreeFind(root->right, x);if (ret2){return ret2;}return NULL;}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"BTNode* CreateTree()
{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;
}int main()
{BTNode* root = CreateTree();PrevOrder(root);//InOrder(root);//PostOrder(root);//int size = BinaryTreeSize(root);//int size = TreeHeight(root);//int size = BinaryTreeLeafSize(root);//int size = BinaryTreeLevelKSize(root,3);//printf("%d ", size);return 0;
}

总结

关于二叉树这块比较复杂, 递归的思路需要清晰, 每一次递归调用的作用是什么, 为什么要这样写, 写代码之前手动画一遍递归调用简图, 只要思路通了, 代码就顺了, 不要觉得是在浪费时间, 因为时间本就是来解决问题的.


完, 感谢关注~

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

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

相关文章

高德地图地图 JS API 2.0 基本使用

1. 按 NPM 方式安装使用 Loader npm i amap/amap-jsapi-loader --save2. 新建 MapContainer.vue 文件 在项目中新建 MapContainer.vue 文件&#xff0c;用作地图组件。 3.创建地图容器 在 MapContainer.vue 地图组件中创建 div 标签作为地图容器 &#xff0c;并设置地图容器…

01主动安全系统

“安全”一直是车主对车辆考核的重要指标。车辆安全可以分为从主动安全和被动安全两个方面进行分类。今天就来说说汽车主动安全系统的那些事儿。 01.什么是主动安全系统&#xff1f; 主动安全是指尽量自如地操纵控制汽车的安全系统措施。无论是直线上的制动与加速还是左右打方…

【调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cloudreve】

调试笔记-系列文章目录 调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cloudreve 文章目录 调试笔记-系列文章目录调试笔记-20240526-Linux-在 OpenWrt-23.05 发行版上安装 cloudreve 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调…

RTOS(3)极简ARM架构与汇编

1.掌握八条汇编指令即可 读内存loadLDR R0&#xff0c;[addrA]写内存storeSTR R0&#xff0c;[addrA]加ADD R0&#xff0c;R1&#xff0c;R2减SUB R0&#xff0c;R1&#xff0c;R2比较CMP R0&#xff0c;R1跳转B / BL入栈PUSH { R3&#xff0c;LR }出…

网络原理-以太网协议和DNS协议

一、以太网协议 以太网协议会涉及到数据链路层和物理层。 如图&#xff1a; 这里面的目的地址和源地址指的并不是IP地址,而是MAC地址(物理地址)。长度为6个字节。即最多能表示2^48 个地址,也是非常大的,足够给全球每个设备都分配一个地址,因此在网卡出厂的时候都会带有一个唯…

力扣刷题--2176. 统计数组中相等且可以被整除的数对【简单】

题目描述 给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 k &#xff0c;请你返回满足 0 < i < j < n &#xff0c;nums[i] nums[j] 且 (i * j) 能被 k 整除的数对 (i, j) 的 数目 。 示例 1&#xff1a; 输入&#xff1a;nums [3,1,2,2,2,1,3], k …

【经典面试题】行锁?表锁?间隙锁?意向锁?排他锁?MySQL的锁机制

目录 前言一、锁的分类二、使用场景全局锁表级锁行级锁间隙锁与临键锁 三、锁与事物的隔离级别1. 读未提交&#xff08;Read Uncommitted&#xff09;2. 读已提交&#xff08;Read Committed&#xff09;3. 可重复读&#xff08;Repeatable Read&#xff09;4. 串行化&#xff…

设计合适的存储系统:原则与实践

在现代信息技术领域&#xff0c;存储系统的设计对系统性能、可扩展性和数据管理至关重要。无论是处理大规模数据的企业&#xff0c;还是需要高效数据访问的小型应用&#xff0c;设计一个合适的存储系统都需要综合考虑多种因素。本文将探讨设计存储系统的关键原则和实践&#xf…

# linux 系统 没有 ifconfig 命令,提示: ifconfig: command not found

sudo ip route add default via 192.168.1.1 dev eth0# linux 系统 没有 ifconfig 命令&#xff0c;提示&#xff1a; ifconfig: command not found 一、问题描述&#xff1a; 有些伙伴在学习 linux 系统时&#xff0c;在 使用 ifconfig 命令 查询 系统 IP 出现 ifconfig: co…

06中间件RTOS/CP

Autosar CP 操作系统详解-CSDN博客 1. 什么是RTOS &#xff1f; RTOS&#xff0c;英文全称是 Real-time Operation System&#xff0c;中文就是 实时操作系统&#xff0c;又称及时操作系统。 实时操作系统&#xff0c;是指当外界事件或数据产生时&#xff0c;能够接受并以足…

中国人工智能大模型价格战

近年来&#xff0c;人工智能技术迅猛发展&#xff0c;尤其是大模型领域的突破让人们看到了更多的可能性。然而&#xff0c;在这一高科技领域&#xff0c;中美两国的竞争日趋激烈。近日&#xff0c;中国互联网巨头们纷纷启动大模型价格战&#xff0c;引发了广泛关注。这场价格战…

express处理get请求和post请求

一、处理get请求 &#xff08;1&#xff09;req.query 1》定义&#xff1a; 此属性是一个对象&#xff0c;包含路由中每个查询字符串参数的属性。此对象默认为 {} 2》代码示例&#xff1a; 例如&#xff1a;获取http&#xff1a;//127.0.0.1:8000/?namejane 的name的值 …

怎么在Qt Designer设计的界面上显示Matplotlib的绘图?

首先&#xff0c;利用Qt Designer设计界面。 设计好后保存为ui文件。 接着&#xff0c;将ui文件转为py文件。 我喜欢在python中进行转换&#xff0c;因此把转换命令封装为函数&#xff0c;运行一下即可。 import os # pyuic5 -o output_file.py input_file.ui #通过命令把.ui…

【云原生】Kubernetes-----POD资源限制与探针机制

目录 引言 一、资源限制 &#xff08;一&#xff09;基本定义 &#xff08;二&#xff09;资源单位 1.CPU资源 2.内存资源 &#xff08;三&#xff09;请求与限制 &#xff08;四&#xff09;定义方式 1.编写yaml文件 2.查看资源情况 二、Pod探针机制 &#xff08;…

动态规划之背包问题中如何确定遍历顺序的问题-组合or排列?

关于如何确定遍历顺序 322. 零钱兑换中&#xff0c;本题求钱币最小个数&#xff0c;那么钱币有顺序和没有顺序都可以&#xff0c;都不影响钱币的最小个数。 所以本题并不强调集合是组合还是排列。 如果求组合数就是外层for循环遍历物品&#xff0c;内层for遍历背包。 如果求…

UML建模

一、概述 二、类图 三、用例图 四、顺序图 五、活动图 六、状态图 七、通信图 八、构件图

学 C/C++ 具体能干什么?

学习 C 和 C 后&#xff0c;你可以从事许多不同的工作和项目&#xff0c;这两种语言以其高性能和低级控制而闻名&#xff0c;特别适合以下几个领域&#xff1a; 1. 系统编程 C 和 C 是系统编程的首选语言&#xff0c;适用于操作系统、驱动程序和嵌入式系统开发。 操作系统开发…

MySQL--InnoDB体系结构

目录 一、物理存储结构 二、表空间 1.数据表空间介绍 2.数据表空间迁移 3.共享表空间 4.临时表空间 5.undo表空间 三、InnoDB内存结构 1.innodb_buffer_pool 2.innodb_log_buffer 四、InnoDB 8.0结构图例 五、InnoDB重要参数 1.redo log刷新磁盘策略 2.刷盘方式&…

常量知识点

常量的声明 关键字&#xff1a;const 固定写法&#xff1a; const 变量类型 变量名 初始值; 附上代码&#xff1a; //变量的声明 int i 10; //常量的声明 const int j 11;常量的特点 必须初始化不能被修改 作用&#xff1a;声明一些常用不变的变量&#xff0c;如PI等 附…

代码随想录算法训练营第50天|● 309.最佳买卖股票时机含冷冻期 ● 714.买卖股票的最佳时机含手续费 ●总结

309. 买卖股票的最佳时机含冷冻期llllll 冷冻期算单独一种情况&#xff1a;1.今天持有&#xff08;已有or今天买&#xff09;2.已经卖出3.今天卖出4.冷冻期 在最后一天的所有卖出状态中找最大值&#xff0c;包括冷冻期 class Solution:def maxProfit(self, prices: List[int]…