数据结构之树 --- 二叉树

目录

定义二叉树的结构体

二叉树的遍历

递归遍历

非递归遍历 

链式二叉树的实现 

二叉树的功能接口

先序遍历创建二叉树

后序遍历销毁二叉树 

 先序遍历查找树中值为x的节点

层序遍历


上篇我们对二叉树的顺序存储堆进行了讲述,本文我们来看链式二叉树。

定义二叉树的结构体

定义链式二叉树同定义链表相同,只是需要注意二叉树有两个指针,类似于双向链表,逻辑上我们将其看作一棵二叉树。下面是定义该树的结构体。

typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;

在创建二叉树之前,我们需要了解前序、中序、后序以及层序遍历。

二叉树的遍历

递归遍历

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

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLRLNRLRN分别又称为先根遍历、中根遍历和后根遍历。

下图展示先序遍历的递归结构:

非递归遍历 

非递归遍历也即层序遍历:

设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

图示: 

链式二叉树的实现 

二叉树的功能接口

数据结构的实现无非是增删查改,二叉树也不例外。

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTNode* root, BTDataType* str);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);层序遍历
//void BinaryTreeLevelOrder(BTNode* root);判断二叉树是否是完全二叉树
//bool BinaryTreeComplete(BTNode* root);

先序遍历创建二叉树

先序遍历创建一个二叉树,我们递归遍历每个元素,然后为其创建节点,但叶子节点没有孩子怎么办?

叶子节点没有孩子,因此当递归到叶子节点的孩子时需要返回NULL;我们需要在需要创建的元素数组中做好标记,比如下面这个代码,当我们遇到 '#' 时返回NULL结束函数,不创建节点。

通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
struct BTNode* PreorderCreate(int* a,int* i)
{if (a[*i] == '#'){(*i)++;return NULL;}struct BTNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));root->val = a[*i];(*i)++;root->left = PreorderCreate(a, i);root->right = PreorderCreate(a, i);return root;
}

后序遍历销毁二叉树 

销毁一个二叉树,我们设想一下,当销毁了树的根节点,那么我们就找不到他的孩子了。因此根节点必然是最后一个销毁的,所以我们用后序遍历来销毁二叉树。

我们递归遍历最深的节点,依次往上销毁。

// 二叉树销毁(后序遍历)
void BinaryTreeDestory(BTNode** root)
{if (*root == NULL)return;BTNode* cur = *root;BinaryTreeDestory(&(*root)->left);BinaryTreeDestory(&(*root)->right);free(*root);*root = NULL;
}

 先序遍历查找树中值为x的节点

树的查找,这里我们以先序遍历为例。

若该节点数据等于x,则返回该节点。否则递归其孩子,我们分别用ret1与ret2记录递归其左右孩子的返回值,然后判断返回值是否存在。根据函数可知,函数的返回只可能为NULL或者是值为x的节点。

至于我们为什么要使用ret1、ret2,那是因为如果直接用 BinaryTreeFind(root->left, x);来判断的话,我们会再次进入这个节点的递归,造成额外的栈帧负担。

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == 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;
}

层序遍历

层序遍历为非递归遍历,对于树而言,非递归往往更难。

这里我们借用队列来实现树的层序遍历,关于队列实现层序遍历,我们后面再讲。

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);while (QueueEmpty(&q)){if (!root)QueuePush(&q, root);BTNode* tmp = QueueFront(&q);QueuePop(&q);printf("%d ", tmp->data);if (!root->left)QueuePush(&q, root->left);if (root->right)QueuePush(&q, root->right);}	
}

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

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

相关文章

OpenHarmony 应用通用签名

一.背景 由于hap包需要经过签名才能安装到设备上,在DevEco Studio可以进行自动签名,但是自动签名只能安装在当前的设备上,在其他设备上不能安装,所以我们需要进行通用的手动签名,手动签名HarmonyOS和OpenHarmony流程是…

golang 图片加水印,字体文件从哪里找

鼠标左键双击此电脑图标在此电脑文本框输入电脑默认字体地址:C:\Windows\Fonts找到需要用到的字体文件,复制到指定文件夹

Halcon阈值处理的几种分割方法threshold/auto_threshold/binary_threshold/dyn_threshold

Halcon阈值处理的几种分割方法 文章目录 Halcon阈值处理的几种分割方法1. 全局阈值2. 基于直方图的自动阈值分割方法3. 自动全局阈值分割方法4. 局部阈值分割方法5. var_threshold算子6 . char_threshold 算子7. dual_threshold算子 在场景中选择物体或特征是图像测量或识别的重…

uniapp中uview组件库的Search 搜索 的用法

目录 基本使用 #设置输入框形状 #是否开启清除控件 #是否开启右边控件 #自定义样式 API #Props #Events 基本使用 通过placeholder参数设置占位内容通过v-model双向绑定一个变量值,设置初始化时搜索框的值,如果初始内容为空,那么请绑…

Linux驱动开发学习笔记6《蜂鸣器实验》

目录 一、蜂鸣器驱动原理 二、硬件原理分析 三、实验程序编写 1、 修改设备树文件 (1)添加pinctrl节点 (2)添加BEEP设备节点 (3)检查PIN 是否被其他外设使用 2、蜂鸣器驱动程序编写 3、编写测试AP…

Ubuntu 安装MySQL以及基本使用

前言 MySQL是一个开源数据库管理系统,通常作为流行的LAMP(Linux,Apache,MySQL,PHP / Python / Perl)堆栈的一部分安装。它使用关系数据库和SQL(结构化查询语言)来管理其数据。 安装…

Halcon区域生长的几种算法regiongrowing/regiongrowing_mean/watersheds

Halcon区域生长的几种算法 文章目录 Halcon区域生长的几种算法1. regiongrowing 算子2. regiongrowing_mean算子3. 分水岭算法 如果想要获得具有相似灰度的相连区域,可以使用区域生长法寻找相邻的符合条件的像素。区域生长法的基本思想是,在图像上选定一…

Spring Boot整合GraphQL

RPC选型入门测试系列文章 GraphQL是一种用于API开发的查询语言和运行时环境。它由Facebook开发并于2015年开源。GraphQL的主要目标是提供一种更高效、灵活和易于使用的方式来获取和操作数据。与传统的RESTful API相比,GraphQL允许客户端精确地指定需要的数据&#…

【51单片机系列】DS1302时钟模块扩展实验之与EEPROM结合使用只进行一次初始化工作

本文是关于时钟芯片DS1302的扩展实验。 文章目录 一、实验分析二、proteus仿真原理图三、软件设计及结果 本实验实现的目的:利用AT24C02掉电不丢失的功能,存储数据用来辨别DS1302时钟是否已经初始化,如果初始化就不执行DS1302初始化函数。 一…

Android移动端超分辨率调研(未完成 目前自用)

作用 图片加载是目前几乎所有的APP都具备的基础能力,在节省服务商的传输带宽之外,也可以降低用户消费端流量的消耗,提升用户的加载速度。帮助每一个产品用更低的成本达到更好的图片加载效果。 效果 另一方面 用TensorFlow实现的图像极度压…

yolov5 主要流程

1.介绍 本文包含了有关yolov5目标检测的基本流程,包括模型训练与模型部署,旨在帮助小伙伴们建立系统的认知💖💖 YOLO是 "You only look once "的首字母缩写,是一个开源软件工具,它具有实时检测…

【23.12.29期--Redis缓存篇】谈一谈Redis的集群模式

谈一谈Redis的集群模式 ✔️ 谈一谈Redis的集群模式✔️主从模式✔️ 特点✔️Redis主从模式Demo ✔️哨兵模式✔️Redis哨兵模式Demo✔️特点 ✔️Cluster模式✔️Redis Cluster模式Demo✔️特点 ✔️ 谈一谈Redis的集群模式 Redis有三种主要的集群模式,用于在分布…