【C语言】手撕二叉树

标题:【C语言】手撕二叉树

@水墨不写bug


正文开始:

        二叉树是一种基本的树形数据结构,对于初学者学习树形结构而言较容易接受。二叉树作为一种数据结构,在单纯存储数据方面没有  顺序表,链表,队列等线性结构  有优势,但是二叉树的搜索功能十分强大——高阶数据结构比如红黑树,B树等的基础就是搜索二叉树。

        二叉树的基本知识讲解:二叉树讲解

        对于初学者,首先学习二叉树的存储结构对于理解二叉树的本质结构非常有效。

(一)头文件

        本文不加解释的给出二叉树的头文件:

        Bitree.h

        并根据头文件来实现二叉树数据结构的相关功能。包括:二叉树的(前序)构建,二叉树的销毁,二叉树节点个数,二叉树叶子节点个数,二叉树第K层节点个数,二叉树查找值为x的节点,二叉树前序遍历,二叉树中序遍历,二叉树后序遍历,层序遍历,判断二叉树是否是完全二叉树等共11个功能。

#pragma once#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<string.h>//节点数据类型
typedef char BiTreeDataType;//二叉树节点
typedef struct BiTreeNode
{BiTreeDataType _val;struct BiTreeNode* _left;struct BiTreeNode* _right;
}BTNode;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BiTreeDataType* a, int* pi);// 二叉树销毁
void BinaryTreeDestory(BTNode* root);// 二叉树节点个数
int BinaryTreeSize(BTNode* root);// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BiTreeDataType x);// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

(二)功能实现

        本文的二叉树是通过递归实现的,想要写好递归,需要注意一下几点:

        递归首先要有递归出口:

        确保递归函数的每一步都朝着基本情况靠近:在写递归函数时,确保每一步递归调用都是在缩小问题规模的基础上进行的。如果递归函数每一步都朝着基本情况靠近,那么递归一定会终止;

        写递归要有的思路:

        只考虑两层情况,即  本层递归  和  下一层递归  ,根据本层递归与下一层递归的关系,来写递归的逻辑;

        写递归要相信自己的函数:

        在写递归的时候,我们使用的下一层递归函数通常是一个黑盒,但是我们要相信递归函数一定完成任务。

(1)前序遍历

        首先判断此节点是否是空节点,若是,则退出递归。(这就是递归的出口)

        接下来访问此节点:printf()函数的调用代表访问节点。

        最后递归访问此节点的左子树和右子树。


// 二叉树前序遍历 root + left + right
void BinaryTreePrevOrder(BTNode* root)
{//递归出口if (root == NULL)return;printf("%d", root->_val);BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}

(2)中序遍历

        首先判断此节点是否是空节点,若是,则退出递归。(这就是递归的出口)

        然后递归访问次节点的左子树。

        接下来访问此节点:printf()函数的调用代表访问节点。

        最后递归访问此节点的右子树。


// 二叉树中序遍历 left + root + right
void BinaryTreeInOrder(BTNode* root)
{//递归出口if (root == NULL)return;BinaryTreePrevOrder(root->_left);printf("%d", root->_val);BinaryTreePrevOrder(root->_right);
}

(3)后序遍历

        首先判断此节点是否是空节点,若是,则退出递归。(这就是递归的出口)

        然后递归访问次节点的右子树。

        接下来访问此节点:printf()函数的调用代表访问节点。

        最后递归访问此节点的左子树。


// 二叉树后序遍历 left + root + right
void BinaryTreePostOrder(BTNode* root)
{//递归出口if (root == NULL)return;BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);printf("%d", root->_val);
}

(4)二叉树的(前序)构建

       前序构建的思路就是前序遍历的思路:先完成对根节点的访问,再递归构建左右子树。

访问根节点:

        根据字符串的当前的字符来决定:如果当前字符为‘#’,返回NULL表示空节点;如果不是‘#’,则创建新节点,并将此字符存入新节点。

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树  pi的作用计数,便于填充数组
// 接受一个字符串,‘#’ 表示 空节点。
BTNode* BinaryTreeCreate(BiTreeDataType* a, int* pi)
{if (a[*pi] == '#'){(*pi)++;return NULL;}BTNode* root = (BTNode*)malloc(sizeof(BTNode));if (root == NULL){perror("malloc fail");exit(-1);}root->_val = a[(*pi)++];root->_left = BinaryTreeCreate(a,pi);root->_right = BinaryTreeCreate(a,pi);return root;
}

(5)二叉树的销毁

        递归销毁,如果此节点为空,此时不能访问此节点,直接返回即可,目的是返回到上一层来销毁;(也就是递归出口的定义)

        之后再递归销毁左子树和右子树,最后释放此节点。


// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{if (root == NULL)return;BinaryTreeDestory(root->_left);BinaryTreeDestory(root->_right);free(root);//要在外部将root置空
}

(6)二叉树节点个数

        递归计数,如果此节点为空,返回0,不计数。

        否则返回左子树的计数结果和右子树的计数结果 + 1(此节点的计数)。


// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == NULL)return 0;return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

(7)二叉树叶子节点个数

        叶子节点有一个特征:左子树和右子树都为空。

        如果遇到这样的节点,计数+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);
}

(8)二叉树第K层节点个数

       模拟递归:

        想象一下:在一个楼层中,假如你要下降K层,如何计数呢?

        在这里我们需要定义一个变量k作为标牌,标识还需要下降的层数。由于本层默认标定为1,所以当k==1时,计数+1;

        最后返回递归左子树和和右子树的结果。


// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{assert(k > 0);if (NULL == root)return 0;if (k == 1)return 1;return BinaryTreeLevelKSize(root->_left,k-1) + BinaryTreeLevelKSize(root->_right,k-1);
}

(9)二叉树查找值为x的节点

         查找思路:

        首先判断根节点是否为空,若为空,表示访问到叶子节点的左右节点,返回空表示没有找到。(另一种情况根节点为空表示整棵树都为空,也返回空表示没有找到)

        如果找到x返回这个节点的地址,表示找到了。

        接下来递归访问左右子树:

        需要注意的是:

        创建一个临时变量ret_l(ret_r)来接收在左右子树查找的结果,如果找到(ret_l(ret_r)不为空)则返回左(右)子树返回的结果;

        如果左右子树都没有找到,则返回空。


// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BiTreeDataType x)
{if (root == NULL)return NULL;if (root->_val == x)return root;BTNode* ret_l = BinaryTreeFind(root->_left, x);if (ret_l)return ret_l;BTNode* ret_r = BinaryTreeFind(root->_right, x);if (ret_r)return ret_r;return NULL;
}

(10)层序遍历

        思路:(不使用递归,通过一个循环和数据结构队列来实现。)

        初始时进一个根节点,接下来出一进二,从队头出一个数据同时压入两个数据——出根节点的同时进左子节点和右子节点,直到把队列出为空。

        printf()代表了对此节点的遍历。


// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{Queue queue;Qinit(&queue);if (root != NULL)Qpush(&queue, root);while (!Qempty(&queue)){BTNode* front = Qfront(&queue);//队列先进先出Qpop(&queue);printf("%d", front->_val);if (front->_left)Qpush(&queue,front->_left);if (front->_right)Qpush(&queue,front->_right);}QDestroy(&queue);
}

(11)判断二叉树是否是完全二叉树

       

        思路:

        通过层序遍历来遍历二叉树。完全二叉树和非完全二叉树的区别就是完全二叉树的最后一个节点之前没有空节点。通过层序遍历来遍历二叉树,当找到空节点时,此时查找队列内是否有非空节点:

        如果都是空,此树是完全二叉树;

        如果有非空节点,此树不是完全二叉树。


// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue queue;Qinit(&queue);if (root != NULL)Qpush(&queue, root);while (!Qempty(&queue)){BTNode* front = Qfront(&queue);//队列先进先出Qpop(&queue);if (front == NULL)break;Qpush(&queue, front->_left);Qpush(&queue, front->_right);}//如果队列里剩下的全是NULL,则是完全二叉树;否则不是while (!Qempty(&queue)){BTNode* front = Qfront(&queue);//队列先进先出Qpop(&queue);if (front){QDestroy(&queue);return false;}}QDestroy(&queue);return true;
}

完~

未经作者同意禁止转载 

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

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

相关文章

ZNS SSD+F2FS文件系统|如何降低GC开销?--2

在F2FS&#xff08;Flash-Friendly File System&#xff09;中&#xff0c;Over-provisioning&#xff0c;OP配置是一种优化策略&#xff0c;旨在通过预留一部分存储空间不分配给用户使用&#xff0c;以提升文件系统的性能、耐用性和可靠性。在F2FS与ZNS SSD的结合中&#xff0…

Win10 打开有些软件主界面会白屏不显示,其他软件都正常

环境&#xff1a; Win10专业版 英伟达4070 显卡 问题描述&#xff1a; Win10 打开有些软件主界面会白屏不显示,打开远程协助软件AIRMdesk,白色&#xff0c;其他软件都正常 解决方案&#xff1a; 网上说电脑没有接显示器独立显卡的关系导致 我是只有一台主机&#xff0c;没…

mmclassification 训练自己的数据集

文章目录 从源码安装数据集准备config文件训练附录 从源码安装 git clone https://github.com/open-mmlab/mmpretrain.git cd mmpretrain pip install -U openmim && mim install -e .下面是我使用的版本 /media/xp/data/pydoc/mmlab/mmpretrain$ pip show mmcv mmpr…

Fisher判别示例:鸢尾花(iris)数据(R)

先读取iris数据&#xff0c;再用程序包MASS&#xff08;记得要在使用MASS前下载好该程序包&#xff09;中的线性函数lda()作判别分析&#xff1a; data(iris) #读入数据 iris #展示数据 attach(iris) #用变量名绑定对应数据 library(MASS) #加载MASS程序包 ldlda(Species~…

路由引入,过滤实验

实验拓补图 实验目的&#xff1a; 1、按照图示配置 IP 地址&#xff0c;R1&#xff0c;R3&#xff0c;R4 loopback口模拟业务网段 2、R1 和 R2 运行 RIPv2,R2&#xff0c;R3和R4运行 OSPF&#xff0c;各自协议内部互通 3、在 RIP 和 oSPF 间配置双向路由引入,要求除 R4 上的…

Jackson 2.x 系列【30】Spring Boot 集成之数据脱敏

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 本系列Spring Boot 版本 3.2.4 源码地址&#xff1a;https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 概述2. 实现思路3. 案例演示3.1 脱敏规则3.2 自…

【全网首发】Mogdb 5.0.6新特性:CM双网卡生产落地方案

在写这篇文章的时候&#xff0c;刚刚加班结束&#xff0c;顺手写了这篇文章。 前言 某大型全国性行业核心系统数据库需要A、B两个物理隔离的双网卡架构方案&#xff0c;已成为行业标准。而最新发布的MogDB 5.0.6的CM新增支持流复制双网段部署&#xff0c;用于网卡级高可用容灾(…

Vue--》深入了解 VueUse 功能性工具集

今天博主为大家介绍一款实用性的插件名字叫做 VueUse &#xff0c;它是专门为 Vue.js 生态系统设计的功能性工具集合。其提供了许多可重用的功能函数&#xff0c;可以帮助开发者更轻松地构建 Vue.js 应用程序。其提供了大量的功能&#xff0c;包括状态管理、副作用管理、组合式…

SpringCloud系列(12)--服务提供者(Service Provider)集群搭建

前言&#xff1a;在上一章节中我们成功把微服务注册进了Eureka集群&#xff0c;但这还不够&#xff0c;虽然注册服务中心Eureka已经是服务配置了&#xff0c;但服务提供者目前只有一个&#xff0c;如果服务提供者宕机了或者流量过大&#xff0c;都会影响到用户即服务使用者的使…

GoJudge环境部署本地调用云服务器部署go-judge判题机详细部署教程go-judge多语言支持

前言 本文基于go-judge项目搭建&#xff0c;由于go-judge官网项目GitHub - criyle/go-judge: Sandbox Server in REST / gRPC API. Based on Linux container technologies.&#xff0c;资料太少&#xff0c;而且只给了C语言的调用样例&#xff0c;无法知道其他常见语言比如&am…

4.23学习总结

一.NIO(一) (一).简介: NIO 是 Java SE 1.4 引入的一组新的 I/O 相关的 API&#xff0c;它提供了非阻塞式 I/O、选择器、通道、缓冲区等新的概念和机制。相比与传统的 I/O 多出的 N 不是单纯的 New&#xff0c;更多的是代表了 Non-blocking 非阻塞&#xff0c;NIO具有更高的并…

OKCC搭建配置什么样的服务器合适

OKCC呼叫中心系统是一种采用软硬件结合的架构方式、及分布式的IP技术&#xff0c;从多角度为企业提供整合的一体化解决方案。因此&#xff0c;搭建OKCC呼叫中心系统所使用的服务器应该满足以下几点要求&#xff1a; 稳定性&#xff1a;服务器需要具有较高的稳定性和可靠性&…

JavaSE——程序逻辑控制

1. 顺序结构 顺序结构 比较简单&#xff0c;按照代码书写的顺序一行一行执行。 例如&#xff1a; public static void main(String[] args) {System.out.println(111);System.out.println(222);System.out.println(333);} 运行结果如下&#xff1a; 如果调整代码的书写顺序 , …

2024华中杯A题|太阳能路灯光伏板的朝向设计问题(思路、代码.....)

太阳能路灯由太阳能电池板组件部分(包括支架)、LED灯头、控制箱(包含控制器、蓄电池)、市电辅助器和灯杆几部分构成。太阳能电池板通过支架固定在灯杆上端。太阳能电池板也叫光伏板,它利用光伏效应接收太阳辐射能并转化为电能输出,经过充放电控制器储存在蓄电池中。太阳能…

Midjourney-01 初试上手 注册使用并生成你的第一张AI图片 详细流程 提示词 过程截图 生成结果 付费文生图的天花板!

背景介绍 Midjourney是一款基于人工智能技术的绘画软件&#xff0c;利用深度学习算法来辅助用户进行绘画创作。这款软件能够通过用户输入的文本描述生成图像&#xff0c;支持多种生成方式&#xff0c;包括文字生成图片、图片生成图片和混合图片生成图片。 图像生成方式&#…

开发区块链DApp应用,引领数字经济新潮流

随着区块链技术的飞速发展&#xff0c;分布式应用&#xff08;DApp&#xff09;正成为数字经济中的一股强劲力量。DApp以其去中心化、透明公正的特点&#xff0c;为用户带来了全新的数字体验&#xff0c;开创了数字经济的新潮流。作为一家专业的区块链DApp应用开发公司&#xf…

软件项目经理需要具备这 11 个能力

当前软件开发技术更新换代越来越快&#xff0c;各种项目实施管理思想也日新月异&#xff0c;作为一个软件项目经理&#xff0c;需要具备这 11 种能力&#xff1a; 1. 项目管理能力 了解项目管理的基本原则和方法&#xff0c;包括制定项目计划、资源分配、风险管理、问题解决和…

出海不出局 | 小游戏引爆高线市场,新竞争态势下的应用出海攻略

出海小游戏&#xff0c;出息了&#xff01; 根据 Sensor Tower 近期发布的“2024 年 3 月中国手游收入 TOP30”榜单&#xff0c;出海小游戏在榜单中成了亮眼的存在。 其中&#xff0c;《菇勇者传说》3 月海外收入环比增长 63%&#xff0c;斩获出海手游收入增长冠军&#xff0c…

IUG-CF论文精读

Neural collaborative filtering with ideal user group labels &#xff08;具有理想用户组标签的神经协同过滤&#xff09; 论文地址&#xff1a;https://www.sciencedirect.com/science/article/pii/S0957417423023898 摘要&#xff1a; 人口统计信息是推荐系统(RSs)的关键…

Mysql用语句创建表/插入列【示例】

一、 创建表 COMMENT表示字段或列的注释 -- 新建student表 CREATE TABLE student (id BIGINT NOT NULL COMMENT 学生id, enroll_date DATE NOT NULL COMMENT 注册时间, NAME VARCHAR(18) DEFAULT NOT NULL COMMENT 学生姓名, deal_flag TINYINT(1) DEFAULT 0 NOT NULL COMM…