链式二叉树的实现及遍历(C语言版)

目录

1 基本概念

1.1 树的概念

1.2 二叉树的链式表示

1.2.1 "左孩子右兄弟"表示法

1.2.2 "左右子树"表示法

1.2.3 手动构建一棵树

2 树的遍历

2.1 前序遍历/先序遍历

2.2 中序遍历

2.3 后序遍历

2.4 层序遍历

2.4.1 算法思想

​编辑 2.4.2 带头尾指针链式队列的代码

3  其他接口函数

3.1 求树的节点个数

3.2 求叶子节点个数

3.3 二叉树的销毁

3.4  遍历寻找二叉树中值为x的节点 


1 基本概念


1.1 树的概念

⭕树是一种非线性的数据结构

⭕树的根结点没有前驱节点,根节点可以指向任意多个子节点(N叉树)

⭕树形结构中,子树之间不能有交集,否则就是图


⭕度:一个节点含有的子树的个数。例如二叉树的根节点的度为2,上图A节点的度为3

⭕树的度:一棵树中最大的节点的度。如二叉树的度就是其根节点的度,上图树的度为3

⭕树的高度或深度:树中节点的最大层次。如上图树的高度为3

⭕叶子节点或终端节点:度为0的节点。如上图的E F G均为叶子节点


1.2 二叉树的链式表示


1.2.1 "左孩子右兄弟"表示法


1.2.2 "左右子树"表示法

问:为什么不叫“左右孩子”表示法呢?

答:结构体内的left指针和right指针的确指向根的左右孩子节点,但是以整体的思想,左右孩子节点又是左子树和右子树的根节点,以“左右子树”命名有利于帮助我们理解二叉树的一系列递归问题。

在现阶段,我们运用“左右子树”表示法更多一些。  


1.2.3 手动构建一棵树


typedef char BTDataType;
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;BTDataType data;
}BTNode;BTNode* A = (BTNode*)malloc(sizeof(BTNode));A->data = 'A';A->left = NULL;A->right = NULL;BTNode* B = (BTNode*)malloc(sizeof(BTNode));B->data = 'B';B->left = NULL;B->right = NULL;BTNode* C = (BTNode*)malloc(sizeof(BTNode));C->data = 'C';C->left = NULL;C->right = NULL;BTNode* D = (BTNode*)malloc(sizeof(BTNode));D->data = 'D';D->left = NULL;D->right = NULL;BTNode* E = (BTNode*)malloc(sizeof(BTNode));E->data = 'E';E->left = NULL;E->right = NULL;A->left = B, A->right = C;B->left = D, B->right = E;

2 树的遍历


2.1 前序遍历/先序遍历

🥝前序遍历/先序遍历:又叫深度优先遍历,根->左子树->右子树

问:下图的树先序遍历的输出结果是什么?


很多教材上的答案是ABDEC,但其实对于初学者特别不友好,初学者可能看得懂这个答案,但是到中序和后序遍历就看不懂了,所以我复现一下遍历过程:

所以教材上的答案多半忽略了对空指针的访问输出,这其实对我们理解遍历是不利的。

上面这个动图是我自己手动制作的,如果想要自己也动起手来,可以访问下面这篇我的博客:

http://t.csdn.cn/JZpjQ


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


2.2 中序遍历

🍋中序遍历:左子树->根->右子树

 问:下图的树中序遍历的输出结果是什么?


建议大家花个几分钟时间自己做一下,空指针访问也表示出来,有利于帮助我们理解递归。

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

答案:

 


2.3 后序遍历

🍇后序遍历:左子树->右子树->根

问:下图的树中序遍历的输出结果是什么?


动图就不制作了,大家可以验证答案后自己动手制作动图。

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


2.4 层序遍历

🍍层序遍历:一层一层节点遍历,又叫广度优先遍历

层序遍历本身直观,实现起来比较麻烦,思想蕴含在代码里。

2.4.1 算法思想

①利用先进先出队列,第一次先入二叉树的根节点到队列中,然后入不为空的子节点,pop掉根节点,如果树的根节点都为空,那么就没有入的必要了。

if (root)QuePush(&q, root);if(root->left)QuePush(&q,root->left);
if(root->right)QuePush(&q,root->right);QuePop(&q);

②第二次入原来根节点的左子树根节点的左右非空节点,然后pop掉该节点。

③第三次入原来根节点的右子树根节点的左右非空节点,然后pop掉该节点。

④依次循环,直到队列为空。那么只要队列不为空,循环就继续。

void LevelOrder(BTNode* root)
{Que q;QueInit(&q);if(root)QuePush(&q,root);while (!QueEmpty(&q)){BTNode* front = QueFront(&q);QuePop(&q);printf("%c ", front->data);if (front->left)QuePush(&q, front->left);if (front->right)QuePush(&q, front->right);}
}

 2.4.2 带头尾指针链式队列的代码

Queue.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>struct BinaryTreeNode;
typedef struct BinaryTreeNode* QDataType;typedef struct QueueNode
{struct QueueNode* next;QDataType data;
}QNode;typedef struct Queue
{QNode* head;QNode* tail;int size;
}Que;void QueInit(Que* pq);
void QueDestroy(Que* pq);
void QuePush(Que* pq, QDataType x);
void QuePop(Que* pq);
QDataType QueFront(Que* pq);
QDataType QueBack(Que* pq);
bool QueEmpty(Que* pq);
int QueSize(Que* pq);

Queue.c

#include"Queue.h"
void QueInit(Que* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}
void QueDestroy(Que* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;
}
void QuePush(Que* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));newnode->next = NULL;newnode->data = x;if (newnode == NULL){perror("malloc fail\n");exit(-1);}if (pq->head == NULL){pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}
void QuePop(Que* pq)
{assert(pq);assert(!QueEmpty(pq));//单结点if (pq->head->next == NULL){free(pq->head);pq->tail = pq->head = NULL;}else{QNode* next = pq->head->next;free(pq->head);pq->head = next;}pq->size--;
}
QDataType QueFront(Que* pq)
{assert(pq);assert(!QueEmpty(pq));return pq->head->data;
}
QDataType QueBack(Que* pq)
{assert(pq);assert(!QueEmpty(pq));return pq->tail->data;
}
bool QueEmpty(Que* pq)
{assert(pq);return pq->head == NULL;
}
int QueSize(Que* pq)
{assert(pq);return pq->size;
}

3  其他接口函数


3.1 求树的节点个数

算法思想:

①左子树的节点个数+根本身+右子树的节点个数

②根为空就返回

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

3.2 求叶子节点个数

算法思想:

①什么是叶子:度为0,即左指针和右指针为空

②遇到空则返回

③一棵树的叶子节点=左子树的叶子节点+右子树的叶子节点

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

3.3 二叉树的销毁

问:为什么选择后序遍历销毁二叉树?而不是前序和中序遍历?

答:前序遍历会导致访问不到根左子树和右子树,引发对空指针的访问;中序遍历会导致访问不到根的右子树,引发对空指针的访问;只有后序遍历才能保证销毁根的左子树和右子树后再销毁根。

void TreeDestroy(BTNode** proot)
{if (*(proot) == NULL)return;TreeDestroy((*proot)->left);TreeDestroy((*proot)->right);free(*proot);*proot=NULL;
}

3.4  遍历寻找二叉树中值为x的节点 

BTNode* TreeFind(BTNode* root, BTDataType x) 
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret=TreeFind(root->left, x);if (ret != NULL)return ret;ret=TreeFind(root->right, x);if (ret != NULL)return ret;return NULL;
}

 4 二叉树代码

BinaryTree.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef char BTDataType;
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;BTDataType data;
}BTNode;
BTNode* BuyNode(BTDataType x);
//前序遍历:根 左子树 右子树
void PrevOrder(BTNode* root);
//中序遍历:左子树 根 右子树
void InOrder(BTNode* root);
//后序遍历:左子树 右子树 根
void PostOrder(BTNode* root);
int TreeSize(BTNode* root);
int TreeLeafSize(BTNode* root);
void TreeDestroy(BTNode** proot);
BTNode* TreeFind(BTNode* root, BTDataType x);
//层序遍历
void LevelOrder(BTNode* root);

 BinaryTree.c

#include"BinaryTree.h"
#include"Queue.h"BTNode* BuyNode(BTDataType x)
{BTNode* root = (BTNode*)malloc(sizeof(BTNode));root->data = x;root->left = root->right = NULL;return root;
}
//前序遍历:根 左子树 右子树
void PrevOrder(BTNode* root)
{if (root == NULL){printf("(null) ");return;}printf("%c ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}
//中序遍历:左子树 根 右子树
void InOrder(BTNode* root)
{if (root == NULL){printf("(null) ");return;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
}
//后序遍历:左子树 右子树 根
void PostOrder(BTNode* root)
{if (root == NULL){printf("(null) ");return;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->data);
}
int TreeSize(BTNode* root)
{return root == NULL ? 0 : 1 + TreeSize2(root->left) + TreeSize2(root->right);
}
int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL)return 1;return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
//层序遍历
void LevelOrder(BTNode* root)
{Que q;QueInit(&q);if(root)QuePush(&q,root);//队列每一个元素的类型都是BTNode*while (!QueEmpty(&q)){BTNode* front = QueFront(&q);QuePop(&q);printf("%c ", front->data);if (front->left)QuePush(&q, front->left);if (front->right)QuePush(&q, front->right);}
}
void TreeDestroy(BTNode** proot)
{if (*proot == NULL)return;TreeDestroy((*proot)->left);TreeDestroy((*proot)->right);free((*proot));*proot = NULL;
}
BTNode* TreeFind(BTNode* root, BTDataType x) 
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret=TreeFind(root->left, x);if (ret != NULL)return ret;ret=TreeFind(root->right, x);if (ret != NULL)return ret;return NULL;
}

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

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

相关文章

MongoDB基础详解

一、MongoDB概述 MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统&#xff0c;由 C 编写的。MongoDB 提供了 面向文档 的存储方式&#xff0c;操作起来比较简单和容易&#xff0c;支持“无模式”的数据建模&#xff0c;可以存储比较复杂的数据类型&#xff0c;是一…

图像采集 deep OCR

按照芯片类型可以分为CCD相机、CMOS相机 按照传感器的结构特性可以分为线阵相机、面阵相机 按照扫描方式可以分为隔行扫描相机、逐行扫描相机 按照分辨率大小可以分为普通分辨率相机、高分辨率相机按照输出信号方式可以分为模拟相机、数字相机 按照输出色彩可以分为单色(黑白)相…

【Linux学习】02Linux基础命令

Linux&#xff08;B站黑马&#xff09;学习笔记 01Linux初识与安装 02Linux基础命令 文章目录 Linux&#xff08;B站黑马&#xff09;学习笔记前言02Linux基础命令Linux的目录结构Linux命令入门ls命令 目录切换相关命令(cd/pwd)cd命令pwd命令 相对路径、绝对路径和特殊路径符创…

自我介绍+项目

自我介绍 1.自我介绍2.快手实习难点&#xff1a;上锁函数 防抖函数结合亮点&#xff1a;技术选型 Uber实习auth0 原理 1.自我介绍 &#xff08;乖巧&#xff0c;听话的声音&#xff09; 好的&#xff0c;面试官你好。我是一名前端开发工程师&#xff0c;叫李小菲&#xff0c;…

Seata流程源码梳理下篇-TC

我们上篇简单梳理了下TM、RM的一些流程&#xff08;离现在过得挺久的了&#xff0c;这篇我们这篇来梳理下TC的内容。 TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态&#xff0c;驱动全局事务提交或回滚。 TM (Transaction Manager) - 事务管理器 定…

Flask-[实现websocket]-(2): flask-socketio文档学习

一、简单项目的构建 flask_websocket |---static |---js |---jquery-3.7.0.min.js |---socket.io_4.3.1.js |---templates |---home |---group_chat.html |---index.html |---app.py 1.1、python环境 python3.9.0 1.2、依赖包 Flask2.1.0 eventlet0.33.3 #使用这个性能会…

将本地项目上传至Github详解

目录 1 前言2 本地代码上传2.1 命令行方法2.2 图形界面法2.3 结果 1 前言 GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;因为只支持Git作为唯一的版本库格式进行托管&#xff0c;故名GitHub 。开发者常常将github作为代码管理平台&#xff0c;方便代码存储、版本…

基于SpringBoot的的师生健康信息管理系统

目录 前言 一、技术栈 二、系统功能介绍 管理员功能模块 学生功能模块 教师功能模块 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着移动应用技术的发展&#xff0c;越来越多的用户借助于移动手机、电脑完成生活中的事务&#xff0c;许多的传统行业也…

超级详细 SQL 优化大全

1、MySQL的基本架构 1&#xff09;MySQL的基础架构图 左边的client可以看成是客户端&#xff0c;客户端有很多&#xff0c;像我们经常你使用的CMD黑窗口&#xff0c;像我们经常用于学习的WorkBench&#xff0c;像企业经常使用的Navicat工具&#xff0c;它们都是一个客户端。右…

北工大汇编题——分支程序设计

题目要求 信息检素程序设计&#xff1a;在数据区&#xff0c;有9个不同的信息&#xff0c;编号 0-8&#xff0c;每个信息包括20 个字符。从键盘接收 0-8 之间的一个编号&#xff0c;然后再屏幕上显示出相应编号的信息内容&#xff0c;按“q”键退出 完整代码 DATAS SEGMENTn0…

Day 03 python学习笔记

位运算 基于二进制的运算&#xff08;计算机的底层基于位运算&#xff09; 计算机最小单位&#xff1a;bit (比特/位/二进制) 1byte&#xff08;字节&#xff09; 8bit &#xff08; 0000 0000&#xff09; &&#xff1a;与 &#xff08;全真为真&#xff0c;一假则…

项目开发过程中遇到了什么困难?

1.需求最初是什么样的&#xff1f; 2.如何挖掘的需求&#xff0c;挖掘后真实的需求是什么样的&#xff1f; 3.我们做了那些调查&#xff1f; 4.我们给出了那些方案&#xff0c;优缺点是什么&#xff1f; 5.根据实际情况&#xff0c;老板的期望&#xff0c;最终我们选择的什…

linux ansible(三)

ansible 配置详解 3.1 ansible 安装方式 ansible安装常用两种方式&#xff0c;yum安装和pip程序安装 3.1.1 使用 pip&#xff08;python的包管理模块&#xff09;安装 需要安装一个python-pip包&#xff0c;安装完成以后&#xff0c;则直接使用pip命令来安装我们的ansible包 …

Leetcode 01-算法入门与数组-③数组排序

LeetCode 01-算法入门与数组-③数组排序 一. 冒泡排序 1. 冒泡排序算法思想 冒泡排序&#xff08;Bubble Sort&#xff09;基本思想&#xff1a; 经过多次迭代&#xff0c;通过相邻元素之间的比较与交换&#xff0c;使值较小的元素逐步从后面移到前面&#xff0c;值较大的元素…

SAP PO运维(一):系统概览异常处理

打开SAP PIPO Netweaver Administration界面,系统概览下显示异常: 参考SAP note: 2577844 - AS Java Monitoring and Logging parametrization best practice service/protectedwebmethods = SDEFAULT -GetVersionInfo -GetAccessPointList -ListLogFiles -ReadLogFile -Para…

为什么选择Spring Cloud

Spring Cloud与Netflix Netflix是一家做视频网站的公司&#xff0c;之所以要说一下这个公司是因为Spring Cloud在发展之初&#xff0c;Netflix做了很大的贡献。包括服务注册中心Eureka、服务调用Ribbon、Feign&#xff0c;服务容错限流Hystrix、服务网关Zuul等众多组件都是Net…

在PyTorch里面利用transformers的Trainer微调预训练大模型

背景 transformers提供了非常便捷的api来进行大模型的微调&#xff0c;下面就讲一讲利用Trainer来微调大模型的步骤 第一步&#xff1a;加载预训练的大模型 from transformers import AutoModelForSequenceClassificationmodel AutoModelForSequenceClassification.from_pr…

Linux下ThinkPHP5实现定时器任务 - 结合crontab

实例一&#xff1a; 1.在/application/command创建要配置的PHP类文件&#xff0c;需要继承Command类&#xff0c;并重写configure和execute两个方法&#xff0c;例如: <?php namespace app\command; use think\console\Command; use think\console\Input; use think\cons…

FatFS文件系统在MCU上的应用

FatFS文件系统是单片机领域有名的一个文件系统&#xff0c;由于它的轻量级和兼容性&#xff0c;备受MCU开发者青睐。 在实现如U盘文件读写&#xff0c;SD卡的文件读写等工作时&#xff0c;我们往往需要一个文件系统来支持我们的工作。特别在一些MCU应用中&#xff0c;文件系统…

PPPoE配置

实验需求 配置IP地址使用PPPOE拨号上网设置路由让直播业务部和营销部都可以访问外网 实验拓扑 实验步骤 配置 R1地址池 电信链路&#xff1a; [Huawei]undo info-center enable Info: Information center is disabled. [Huawei]sysname r1 [r1]ip pool zhibo  //配置…