c语言实现二叉树(链式结构)

文章目录

  • 前言
  • 一、二叉树的遍历
    • 1、二叉树的层序遍历
    • 2、二叉树的前序遍历
    • 3、二叉树的中序遍历
    • 4、二叉树的后序遍历
    • 5、代码实现
  • 二、二叉树的一些操作的实现
    • 1、求二叉树的结点个数
    • 2、求二叉树叶子结点个数
    • 3、求二叉树第k层结点个数
    • 4、求二叉树深度
    • 5、二叉树中查找值为x的结点
    • 6、二叉树的销毁
  • 三、二叉树层序遍历的实现
    • 1、层序遍历
    • 2、层序遍历代码实现
    • 3、层序遍历应用 -- 判断二叉树是否是完全二叉树


前言

当我们使用顺序结构实现了二叉树的存储后,接下来就是使用链式结构来实现二叉树的存储。

一、二叉树的遍历

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

1、二叉树的层序遍历

二叉树的层序遍历在前面介绍二叉树的顺序结构存储时,已经使用到了,即完全二叉树在数组中的存储顺序就是该二叉树的层序遍历顺序。
在这里插入图片描述

2、二叉树的前序遍历

前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。#号表示空树的位置。
在这里插入图片描述

3、二叉树的中序遍历

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

在这里插入图片描述

4、二叉树的后序遍历

后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。#号表示空树的位置。
在这里插入图片描述

5、代码实现

知道了二叉树的前、中、后序遍历的规则后,我们就可以实现一个二叉树,然后实现这些遍历方法。
二叉树定义

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>typedef int BTDataType;typedef struct BinaryTreeNode
{//存储该结点的左子树的根结点地址struct BinaryTreeNode* left;//存储该结点的右子树的根结点地址struct BinaryTreeNode* right;//存储该结点的数据BTDataType data;
}BTNode;

创建二叉树结点

//创建一个新的结点,并且将该结点返回
BTNode* BuyNode(BTDataType x)
{BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));if (newNode == NULL){perror("malloc fail");exit(-1);}newNode->left = NULL;newNode->right = NULL;newNode->data = x;return newNode;
}

创建一棵二叉树

//创建一棵二叉树,并且返回该二叉树的根结点
BTNode* CreateBinaryTree()
{//建立二叉树的结点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;
}

实现先序遍历

//先序遍历
void PreOrder(BTNode* root)
{//如果访问的结点为NULL,就打印#if (root == NULL){printf("# ");return;}//如果访问结点不为NULL,就将该结点的数据打印出来printf("%d ", root->data);//因为为先序遍历,所以先访问根节点,然后再访问左子树PreOrder(root->left);//当左子树访问完再访问右子树PreOrder(root->right);
}

即函数递归调用的顺序如图。
在这里插入图片描述

实现中序遍历

//中序遍历
void PreOrder(BTNode* root)
{//如果访问的结点为NULL,就打印#if (root == NULL){printf("# ");return;}//因为为中序遍历,所以先访问左子树,然后再访问根节点PreOrder(root->left);//左子树访问完后再打印根结点数据printf("%d ", root->data);//当根结点访问完再访问右子树PreOrder(root->right);
}

实现后序遍历

//后序遍历
void PostOrder(BTNode* root)
{//如果访问的结点为NULL,就打印#if (root == NULL){printf("# ");return;}//因为为后序遍历,所以先访问左子树,然后再访问右子树PostOrder(root->left);//当左子树结点访问完毕后,访问右子树结点PostOrder(root->right);//当左右子树结点都访问完后,再访问根节点数据printf("%d ", root->data);
}

二、二叉树的一些操作的实现

当我们实现了二叉树的遍历后,就可以再实现二叉树的其他一些操作

1、求二叉树的结点个数

当要求二叉树结点时,我们想到的第一个方法就是定义一个全局变量count,然后再依次访问二叉树的结点,如果结点不为空,则count++,最后count的值就是二叉树中的结点个数。需要注意的是,当我们第二次调用这个方法时,因为count为全局变量,此时已经不为0,所以要重置count的值为0。

int count = 0;
void BinaryTreeSize(BTNode* root)
{//如果访问的结点为NULL,则count不进行+1if (root == NULL){return;}//每访问到一个不为NULL结点就让count+1++count;//然后再去访问该结点的左子树和右子树BinaryTreeSize(root->left);BinaryTreeSize(root->right);
}

上述方法使用全局变量得到树节点的数量是不安全的,并且全局变量可以被修改,而且每次调用函数时,还需要重置全局变量。所以我们可以使用第二种方法来求出二叉树结点个数。

int TreeSize2(BTNode* root)
{//当root结点为NULL时,就返回0if (root == NULL){return 0;}//当root结点不为NULL,就返回root结点的数量,然后加上root结点左子树和右子树的结点的数量。return 1 + TreeSize2(root->left) + TreeSize2(root->right);
}

函数的递归和回退图。
在这里插入图片描述

2、求二叉树叶子结点个数

//二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{//如果该结点为NULL,则返回0if (root == NULL){return 0;}//如果该结点为叶子结点,则返回1if (root->left == NULL && root->right == NULL){return 1;}//如果该结点不为NULL,也不为叶子结点,就返回该节点的左子树和右子树中的叶子结点数return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

3、求二叉树第k层结点个数

//二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{assert(k >= 1);//如果该结点为NULL,就返回0if (root == NULL){return 0;}//k==1,说明要求的就是这一层的结点数,返回1if (k == 1){return 1;}//如果该结点不为NULL,且k!=1,说明求的不是该层的结点数,让k-1,然后求该结点的左子树和右子树return BinaryTreeLevelKSize(root->left,k-1) + BinaryTreeLevelKSize(root->right,k-1);
}

4、求二叉树深度

//求二叉树深度
int BinaryTreeDepth(BTNode* root)
{//如果该结点为NULL,则深度为0if (root == NULL){return 0;}//然后求出该结点的左子树和右子树的深度int leftDepth = BinaryTreeDepth(root->left);int rightDepth = BinaryTreeDepth(root->right);//如果该结点的左子树深度大于右子树深度if (leftDepth > rightDepth){//就返回该结点左子树的深度加这一层的深度return leftDepth + 1;}//如果该结点的左子树深度小于等于右子树深度else{//就返回右子树的深度加这一层的深度return rightDepth + 1;}
}

5、二叉树中查找值为x的结点

//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{//当结点为NULL时,返回NULLif (root == NULL){return NULL;}//当该结点数据为x时,返回该结点if (root->data == x){return root;}//当该结点数据不为x时,先遍历该结点的左子树BTNode* left = BinaryTreeFind(root->left, x);//如果该结点的左子树返回的结点不为NULL,则说明在左子树中找到了存储x的结点,则此时left就存储该结点的地址。直接将left返回即可if (left!=NULL){return left;}//如果该结点的左子树也没有查到就去遍历该结点的右子树,BTNode* right = BinaryTreeFind(root->right, x);//当该结点的右子树返回的结点不为NULL,则说明在右子树中找到了存储x的结点,此时right就存储该结点的地址。直接将right返回即可if (right!=NULL){return right;}//当该结点的数据和该结点的左子树和右子树的结点中都没有该数据,则二叉树中没有该数据,此时返回NULLreturn NULL;
}

6、二叉树的销毁

二叉树的销毁就是依次将二叉树的各结点申请的空间都释放,则需要遍历一遍二叉树,这里我们需要采用后序遍历,即先将根结点的左子树的结点释放完,再释放根结点的右子树的结点,最后再释放根结点的空间。因为如果采用先序遍历先将根结点空间释放,则就找不到根节点的左子树和右子树了。

//二叉树的销毁
void BinaryTreeDestroy(BTNode* root)
{if (root == NULL){return;}//采用后序遍历释放二叉树结点BinaryTreeDestroy(root->left);BinaryTreeDestroy(root->right);free(root);
}

三、二叉树层序遍历的实现

1、层序遍历

二叉树的层序遍历的实现需要队列的辅助。例如有下面一棵二叉树,先将二叉树的根结点入队列。
在这里插入图片描述
然后将队列的队头出队,并且将队头结点的左右孩子入队列。
在这里插入图片描述
然后重复上述操作。将剩下的结点依次进入队列。
在这里插入图片描述
直到队列为空。结点出队的顺序即为二叉树层序遍历顺序。
在这里插入图片描述

2、层序遍历代码实现

层序遍历需要借助一个辅助队列,先将二叉树根结点入队,然后让根节点出队,让根节点的左右孩子入队,然后让左右孩子出队,让左右孩子的左右孩子入队,依次循环下去,直到队列为空。

//二叉树的层序遍历
void LevelOrder(BTNode* root)
{//创建一个辅助队列Queue qt;QueueInit(&qt);//如果二叉树为空,就退出函数if (root == NULL){return;}//将根节点的地址入队QueuePush(&qt, root);//如果队列中不为空,就进入循环while (!QueueEmpty(&qt)){//创建一个BTNode*类型的指针接收队列中队头结点存的地址BTNode* head = QueueFront(&qt);//将队头结点出队。QueuePop(&qt);//打印出队结点的数据printf("%d ", head->data);//如果出队结点的左孩子不为空,就将结点左孩子入队if (head->left != NULL){QueuePush(&qt, head->left);}//如果出队结点的右孩子不为空,就将结点右孩子入队if (head->right != NULL){QueuePush(&qt, head->right);}}printf("\n");QueueDestroy(&qt);}

3、层序遍历应用 – 判断二叉树是否是完全二叉树

当有一棵二叉树需要判断其是否为完全二叉树时,此时就可以借用二叉树的层序遍历。
例如有如下一个完全二叉树
在这里插入图片描述
该二叉树按照层序遍历的步骤,先让根结点入队,然后队头结点出队,并让队头结点存的结点的左右孩子入队。如果左右孩子为空也入队。
在这里插入图片描述
当队列中队头结点存的数据为NULL时,停止上述操作。此时队列中结点存储的数据全为NULL,可由此得出该二叉树为完全二叉树
在这里插入图片描述

例如有如下一个非完全二叉树
在这里插入图片描述
该二叉树按照层序遍历的步骤,先让根结点入队,然后队头结点出队,并让队头结点存的结点的左右孩子入队。如果左右孩子为空也入队。
在这里插入图片描述
当队列中队头结点存的数据为NULL时,停止上述操作。此时队列中的结点存储的数据有的为NULL,有的不为NULL,则可以由此得出该二叉树不为完全二叉树。
在这里插入图片描述
由上述的条件我们可以修改层序遍历的一些逻辑,使其可以判断二叉树是否为完全二叉树。

int BinaryTreeComplete(BTNode* root)
{//创建一个辅助队列Queue qt;QueueInit(&qt);//如果二叉树根节点不为空,就将根节点的地址入队列if (root != NULL){QueuePush(&qt, root);}//如果队列中不为空,就进入循环while (!QueueEmpty(&qt)){//将队列的队头结点存的数据返回BTNode* head = QueueFront(&qt);//将队头结点出队QueuePop(&qt);//如果该队头结点存的数据不为NULL,就将该结点的左右孩子入队,左右孩子为NULL也入队if (head != NULL){QueuePush(&qt, head->left);QueuePush(&qt, head->right);}//如果该队头结点存的数据为空,说明已经到达队列中第一个NULL,此时跳出循环else{break;}}//此时如果队列不为空,就继续遍历队列中的元素while (!QueueEmpty(&qt)){//将队头结点存的数据返回BTNode* head = QueueFront(&qt);//将队头结点出队QueuePop(&qt);//如果队列中第一个NULL后还有队列结点存的数据不为NULL,则说明该二叉树不为完全二叉树if (head != NULL){//将队列销毁QueueDestroy(&qt);//返回0则代表该二叉树不是完全二叉树return 0;}}//如果当队列中结点全部遍历完,并且存的数据都为NULL,说明该二叉树为完全二叉树//销毁队列QueueDestroy(&qt);//返回1代表为完全二叉树return 1;
}

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

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

相关文章

streamlit-高级功能

缓存 st.cache_data st.cache_resource 为应用程序添加会话状态 会话状态 会话状态应用到应用程序 会话状态和小部件关联 可序列化会话状态 注意事项和限制 命令行选项 应用程序菜单 菜单选项 开发者选项 streamlit配置 按钮行为和示例 连接到数据 数据框 形式 小部件语义 …

前端vue引入高德地图入门教程

距离上一篇关于前端项目中使用高德地图的文章已经将近5年之久&#xff0c; 这是我的第一篇关于高德地图的文章 这期间前端技术日新月异&#xff0c;5年前JQuery还如日中天&#xff0c;如今已经销声匿迹&#xff0c;很少有公司招聘还在要求JQuery&#xff0c;更多的是Vue、React…

keras深度学习框架构建LeNet5神经网络模型实现手写数字识别

之前两篇文章分别通过keras深度学习框架构建简单神经网络和卷积神经网络实现过手写数字识别实验。这篇文章分享我根据LeNet5模型构建的卷积神经网络来实现手写数字识别。 这个实验是根据LeNet5模型构建卷积神经网络&#xff0c;LeNet5模型的原理图如下所示&#xff1a; 相信大家…

中欧财富:分布式数据库的应用历程和 TiDB 7.1 新特性探索

作者&#xff1a;张政俊 中欧财富数据库负责人 中欧财富是中欧基金控股的销售子公司&#xff0c;旗下 APP 实现业内基金品种全覆盖&#xff0c;提供基金交易、大数据选基、智慧定投、理财师咨询等投资工具及服务。中欧财富致力为投资者及合作伙伴提供一站式互联网财富管理解决方…

js优雅的统计字符串字符出现次数

题目如下 统计一串字符串中每个字符出现的频率 示例字符串 let str asdfasqwerqwrdfafafasdfopasdfopckpasdfassfd小白写法 let str asdfasqwerqwrdfafafasdfopasdfopckpasdfassfdlet result {}; for (let i 0; i < str.length; i) {if (result[str[i]]) {result[str[…

论文笔记: One Fits All:Power General Time Series Analysis by Pretrained LM

1 intro 时间序列领域预训练模型/foundation 模型的研究还不是很多 主要挑战是缺乏大量的数据来训练用于时间序列分析的基础模型——>论文利用预训练的语言模型进行通用的时间序列分析 为各种时间序列任务提供了一个统一的框架 论文还调查了为什么从语言领域预训练的Transf…

【ag-grid-vue】column

网格中的每一列都使用列定义(ColDef)来定义。列根据在网格选项中指定的列定义的顺序在网格中定位。 列定义 下面的例子展示了一个定义了3列的简单网格: <template><ag-grid-vuestyle"height: 300px; width: 1000px"class"ag-theme-balham":colum…

get√接口自动化核心知识点浓缩,为面试加分

日常接触到的接口自动化从实际目标可以划分为两大类&#xff1a; 1、为模拟测试数据而开展的接口自动化 这种接口自动化大多是单次执行&#xff0c;目的很明确是为了功能测试创造测试数据&#xff0c;节约人工造数据的时间和人工成本&#xff0c;提高功能测试人员的测试效率。…

chain of thought (思维链, cot)

定义 思维链 (Chain-of-thought&#xff0c;CoT) 的概念是在 Google 的论文 "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models" 中被首次提出。思维链&#xff08;CoT&#xff09;是一种改进的提示策略&#xff0c;用于提高 LLM 在复杂推理…

【UE5】给模型指定面添加自定义材质

实现步骤 1. 首先我们向UE中导入一个简单的模型&#xff0c;可以看到目前该模型的材质插槽只有一个&#xff0c;当我们修改材质时会使得模型整体的材质全部改变&#xff0c;如果我们只想改变模型的某些面的材质就需要继续做后续操作。 2. 选择建模模式 3. 在模式工具栏中点击…

Linux学习之Ubuntu 20使用systemd管理OpenResty服务

sudo cat /etc/issue可以看到操作系统的版本是Ubuntu 20.04.4 LTS&#xff0c;sudo lsb_release -r可以看到版本是20.04&#xff0c;sudo uname -r可以看到内核版本是5.5.19&#xff0c;sudo make -v可以看到版本是GNU Make 4.2.1。 需要先参考我的博客《Linux学习之Ubuntu 2…

SpringBoot Mybatis 多数据源 MySQL+Oracle

一、背景 在SpringBoot Mybatis 项目中&#xff0c;需要连接 多个数据源&#xff0c;连接多个数据库&#xff0c;需要连接一个MySQL数据库和一个Oracle数据库 二、依赖 pom.xml <dependencies><dependency><groupId>org.springframework.boot</groupId&…

【Golang】go条件编译

交叉编译只是为了能在一个平台上编译出其他平台可运行的程序&#xff0c;Go 作为一个跨平台的语言&#xff0c;它提供的类库势必也是跨平台的&#xff0c;比如说程序的系统调用相关的功能&#xff0c;能根据所处环境选择对应的源码进行编译。让编译器只对满足条件的代码进行编译…

【Linux】centos8安装cmake3.27.4

第一步&#xff0c;去官网下安装包&#xff0c;一定不要下错了 下好了之后&#xff0c;用ftp软件传到云服务器或者虚拟机上&#xff0c;我用的是centos8系统&#xff0c;安装之前先准备好这些依赖项 yum install -y gcc gcc-c make automake yum install -y openssl openssl-…

多线程应用——单例模式

单例模式 文章目录 单例模式一.什么是单例模式二.如何实现1.口头实现2.利用语法特性 三.实现方式&#xff08;饿汉式懒汉式&#xff09;1.饿汉式2.懒汉式3.线程安全的单例模式4.双重检查锁5.禁止指令重排序 一.什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff…

LLM本地知识库问答系统(二):如何正确使用LlamaIndex索引

推荐阅读列表&#xff1a; LLM本地知识库问答系统&#xff08;一&#xff09;&#xff1a;使用LangChain和LlamaIndex从零构建PDF聊天机器人指南 上一篇文章我们介绍了使用LlamaIndex构建PDF聊天机器人&#xff0c;本文将介绍一下LlamaIndex的基本概念和原理。 LlamaIndex简介…

视频分割合并工具说明

使用说明书&#xff1a;视频分割合并工具 欢迎使用视频生成工具&#xff01;本工具旨在帮助您将视频文件按照指定的规则分割并合并&#xff0c;以生成您所需的视频。 本程序还自带提高分辨率1920:1080&#xff0c;以及增加10db声音的功能 软件下载地址 https://github.com/c…

FPGA原理与结构——时钟IP核原理学习

一、前言 在之前的文章中&#xff0c;我们介绍了FPGA的时钟结构 FPGA原理与结构——时钟资源https://blog.csdn.net/apple_53311083/article/details/132307564?spm1001.2014.3001.5502 在本文中我们将学习xilinx系列的FPGA所提供的时钟IP核&#xff0c;来帮助我们进一…

TCP/IP五层模型、封装和分用

1.网络通信基础2.协议分层OSI七层协议模型TCP/IP五层/四层协议模型【重点】 3. 封装&分用 1.网络通信基础 IP地址&#xff1a;表示计算机的位置&#xff0c;分源IP和目标IP&#xff1b;举个例子&#xff1a;买快递&#xff0c;商家从上海发货&#xff0c;上海就是源IP&…

理虚实一体化全栈全场景云计算应用实训室解决方案

一、 云计算应用统概述 云计算应用系统是指基于云计算技术构建的应用系统&#xff0c;它将软件、数据、计算和存储资源部署在云服务器上&#xff0c;通过网络根据应用按照一定策略为用户提供相关服务。云计算应用系统广泛应用于各个领域&#xff0c;包括但不限于金融、教育、政…