当前位置: 首页 > news >正文

【数据结构】二叉树链式结构的实现及其常见操作

目录

1.手搓二叉树

2.二叉树的遍历

2.1前序、中序以及后序遍历

2.2二叉树的层序遍历

3.二叉树的常见操作

3.1求二叉树节点数量

3.2求二叉树叶子节点数量

3.3求二叉树第k层节点个数

3.3求二叉树的深度

3.4二叉树查找值为x的节点

4.二叉树的销毁


1.手搓二叉树

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

 定义二叉树的节点:

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

根据上图的二叉树结构手写二叉树:

BTNode* BuyNode(BTDataType data)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));assert(node);node->data = data;node->left = node->right = NULL;return node;
}BTNode* CreatBinaryTree()
{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;
}

这样我们就写出了一个简单的二叉树。

2.二叉树的遍历

2.1前序、中序以及后序遍历

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

 按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

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

对于二叉树的遍历,代码写起来很简单,但是对于初学者来说,要理解起来就有点难了,这里先给出三种遍历的代码,大家可以先看看:

前序遍历

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

中序遍历

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

后序遍历

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

看完代码后,是不是觉得这三种遍历都非常的相似呢?我们在编译器上运行一下三种遍历的代码:

int main()
{BTNode* root = CreatBinaryTree();PreOrder(root);printf("\n");InOrder(root);printf("\n");PostOrder(root);return 0;
}

运行结果:

 前序遍历的递归展开图:

 中序遍历和后续遍历和这图也差不多。

2.2二叉树的层序遍历

二叉树的层序遍历是一种广度优先搜索(BFS)的方法。它按层级顺序逐层遍历二叉树,即从根节点开始,先遍历第一层节点,然后遍历第二层节点,依次类推,直到遍历完所有层级。

实现层序遍历的一种常见方法是使用队列。具体思路如下:

  1. 创建一个空队列,并将根节点入队。
  2. 循环执行以下步骤,直到队列为空:
  • 出队一个节点,将其值存储到结果列表中。
  • 若该节点有左孩子,则将左孩子入队。
  • 若该节点有右孩子,则将右孩子入队。

这样,当队列为空时,遍历过程就完成了,结果列表中存储着层序遍历的结果。

 下面代码的使用了C++STL中的队列来完成,避免了手写队列的麻烦:

void LevelOrder(BTNode* root)
{assert(root);queue<BTNode*> a;a.push(root);while (!a.empty()){BTNode* front = a.front();a.pop();printf("%d ", front->data);if (front->left){a.push(front->left);}if (front->right){a.push(front->right);}}
}int main()
{BTNode* root = CreatBinaryTree();LevelOrder(root);return 0;
}

输出结果:

3.二叉树的常见操作

3.1求二叉树节点数量

方法一:

定义一个全局变量count,然后遍历每一个节点,每遍历一个节点,count就自加1

代码:

int count = 0;
void TreeSize1(BTNode* root)
{if (root == NULL){return;}count++;TreeSize1(root->left);TreeSize1(root->right);
}
int main()
{BTNode* root = CreatBinaryTree();count = 0;TreeSize1(root);printf("%d\n", count);return 0;
}

方法二:

int TreeSize2(BTNode* root)
{if (root == NULL){return 0;}return TreeSize2(root->left) + TreeSize2(root->right) + 1;
}
int main()
{BTNode* root = CreatBinaryTree();printf("TreeSize2: %d\n", TreeSize2(root));return 0;
}

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回0。如果根节点不为空,则递归调用自身来计算左子树和右子树的节点数,然后将左子树节点数、右子树节点数以及根节点本身(1个节点)的数量相加,最后返回结果。

通过这种递归的方式,函数可以计算出二叉树中所有节点的总数。

3.2求二叉树叶子节点数量

具体思路:

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回 0。接着,通过判断根节点的左子树和右子树是否都为空来确定当前节点是否为叶子节点。如果是叶子节点,返回 1。如果不是叶子节点,递归调用自身来计算左子树和右子树的叶子节点数,并将其相加作为结果返回。

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);
}
int main()
{BTNode* root = CreatBinaryTree();printf("TreeLeafSize: %d\n", TreeLeafSize(root));return 0;
}

3.3求二叉树第k层节点个数

思路:

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回0。如果k等于1,说明当前层即为目标层,返回1。如果k大于1,则递归调用自身来计算左子树和右子树中第k-1层节点的数量,并将其相加作为结果返回。

通过这种递归的方式,函数可以计算出二叉树中第k层节点的总数。

int TreeKLevel(BTNode* root, int k)
{assert(k >= 1);if (root == NULL) return 0;if (k == 1) return 1;return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
int main()
{BTNode* root = CreatBinaryTree();printf("TreeKLevel: %d\n", TreeKLevel(root, 2));//第2层节点数量printf("TreeKLevel: %d\n", TreeKLevel(root, 3));//第3层节点数量printf("TreeKLevel: %d\n", TreeKLevel(root, 4));//第4层节点数量return 0;
}

函数的递归展开图:

3.3求二叉树的深度

int TreeDepth(BTNode* root)
{if (root == NULL) return 0;int l = TreeDepth(root->left); //左子树的深度int r = TreeDepth(root->right); //右子树的深度return (l > r ? l : r) + 1; //返回左右子树深度的较大值加自身的深度1
}
int main()
{BTNode* root = CreatBinaryTree();printf("TreeDepth: %d\n", TreeDepth(root));return 0;
}

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回深度0。接着,函数通过递归调用自身来计算左子树和右子树的深度,分别将结果存储在变量l和r中。然后,通过比较l和r的大小,选择较大的值,并将其加1(代表当前节点的深度),作为整棵二叉树的深度。

通过这种递归的方式,函数可以计算出二叉树的深度(高度)。

3.4二叉树查找值为x的节点

在二叉树查找值为x的节点时,尽量使用前序遍历,可以提高效率。

BTNode* TreeFind(BTNode* root, BTDataType x)
{if (root == NULL) return NULL;if (root->data == x) return root;BTNode* ret1 = TreeFind(root->left, x);if (ret1){return ret1;}BTNode* ret2 = TreeFind(root->right, x);if (ret2){return ret2;}return NULL;
}int main()
{BTNode* root = CreatBinaryTree();BTNode* ret = TreeFind(root, 3);if (ret){printf("找到了:%d\n", ret->data);}else{printf("找不到\n");}return 0;
}

首先检查根节点是否为空,如果为空,说明这是一个空树,直接返回NULL。接着,函数检查当前节点的数据是否等于目标值x,如果等于,说明找到了目标节点,返回指向当前节点的指针。如果不等于,递归调用函数分别在左子树和右子树中查找目标值x,如果返回的指针非空,说明在子树中找到了目标节点,直接返回该指针。如果左右子树都没有找到目标节点,则返回NULL。

通过这种递归的方式,函数可以在二叉树中查找特定值的节点,并返回指向该节点的指针。如果找不到目标值,则返回NULL。

4.二叉树的销毁

对于二叉树的销毁,我们不能使用先序遍历,因为如果使用先序遍历,会将二叉树的根节点先销毁掉,这样就无法找到根节点的左子树和右子树了,如果一定要使用先序遍历,那就得先把节点的左子树和右子树先保存下来。但如果使用后序遍历,就可以轻松解决了。

采用后序遍历销毁,按照左孩子,右孩子,根节点的顺序销毁一颗二叉树。

void TreeDestory(BTNode* root)
{if (root == NULL){return;}TreeDestory(root->left);TreeDestory(root->right);free(root);
}
int main()
{BTNode* root = CreatBinaryTree();PreOrder(root);TreeDestory(root);root = NULL;
}

相关文章:

【数据结构】二叉树链式结构的实现及其常见操作

目录 1.手搓二叉树 2.二叉树的遍历 2.1前序、中序以及后序遍历 2.2二叉树的层序遍历 3.二叉树的常见操作 3.1求二叉树节点数量 3.2求二叉树叶子节点数量 3.3求二叉树第k层节点个数 3.3求二叉树的深度 3.4二叉树查找值为x的节点 4.二叉树的销毁 1.手搓二叉树 在学习…...

从零实战SLAM-第九课(后端优化)

在七月算法报的班&#xff0c;老师讲的蛮好。好记性不如烂笔头&#xff0c;关键内容还是记录一下吧&#xff0c;课程入口&#xff0c;感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…...

Python Opencv实践 - 图像金字塔

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_COLOR) print(img.shape)#图像上采样 #cv.pyrUp(src, dstNone, dstsizeNone, borderTypeNone) #参考资料&#xff1a;https://blo…...

Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的固定帧率(C++)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的固定帧率&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的固定帧率功能的技术背景CameraExplorer如何查看相机固定帧率功能在BGAPI SDK里通过函数设置相机固定帧率 Baumer工业相机通过BGAPI SDK设置相机固定帧…...

计算机竞赛 python+大数据校园卡数据分析

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于yolov5的深度学习车牌识别系统实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;4分工作量&#xff1a;4分创新点&#xff1a;3分 该项目较为新颖&am…...

DNNGP模型解读-early stopping 和 batch normalization的使用

一、考虑的因素&#xff08;仅代表个人观点&#xff09; 1.首先我们看到他的这篇文章所考虑的不同方面从而做出的不同改进&#xff0c;首先考虑到了对于基因组预测的深度学习方法的设计 &#xff0c;我们设计出来这个方法就是为了基因组预测而使用&#xff0c;这也是主要目的&…...

【目标检测】目标检测 相关学习笔记

目标检测算法 PASCALVOC2012数据集 挑战赛主要分为 图像分类 目标检测 目标分割 动作识别 数据集分为四个大类 交通&#xff08;飞机 船 公交车 摩托车&#xff09; 住房&#xff08;杯子 椅子 餐桌 沙发&#xff09; 动物&#xff08;鸟 猫 奶牛 狗 马 羊&#xff09; 其他&a…...

面试攻略,Java 基础面试 100 问(十六)

反射使用步骤(获取Class对象、调用对象方法) 获取想要操作的类的Class对象&#xff0c;他是反射的核心&#xff0c;通过Class对象我们可以任意调用类的方法。 调用 Class 类中的方法&#xff0c;既就是反射的使用阶段。 使用反射 API 来操作这些信息。 什么是 java 序列化&…...

章节5:脚本注入网页-XSS

章节5&#xff1a;脚本注入网页-XSS XSS &#xff1a;Cross Site Script 恶意攻击者利用web页面的漏洞&#xff0c;插入一些恶意代码&#xff0c;当用户访问页面的时候&#xff0c;代码就会执行&#xff0c;这个时候就达到了攻击的目的。 JavaScript、Java、VBScript、Activ…...

ATF(TF-A)安全通告 TFV-5 (CVE-2017-15031)

安全之安全(security)博客目录导读 ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-5 (CVE-2017-15031) 二、CVE-2017-15031 一、ATF(TF-A)安全通告 TFV-5 (CVE-2017-15031) Title 未初始化或保存/恢复PMCR_EL0可能会泄露安全世界的时间信息 CVE ID CVE-2017-1503…...

迅捷视频工具箱:多功能音视频处理软件

这是一款以视频剪辑、视频转换、屏幕录像等特色功能为主&#xff0c;同时附带有视频压缩、视频分割、视频合并等常用视频处理功能为主的视频编辑软件。该软件操作简单易用&#xff0c;即使没有视频处理经验的用户也可以轻松上手。将视频添加到工具箱对应功能后&#xff0c;简单…...

linux--fork()详解

fork() 参考链接&#xff1a;链接 进程控制原语包括&#xff1a;进程的建立、进程的撤销、进程的等待和进程的唤醒。 fork&#xff0c;在英语用译为叉子&#xff0c;形状像Y&#xff0c;反过来就如下图&#xff1a; 就是本来只有一个进行app,然后它调用了fork()函数&#xf…...

go_并发编程(1)

go并发编程 一、 并发介绍1&#xff0c;进程和线程2&#xff0c;并发和并行3&#xff0c;协程和线程4&#xff0c;goroutine 二、 Goroutine1&#xff0c;使用goroutine1&#xff09;启动单个goroutine2&#xff09;启动多个goroutine 2&#xff0c;goroutine与线程3&#xff0…...

第一百一十五回 权限管理包permission_handler

文章目录 概念介绍使用方法示例代码经验分享 我们在上一章回中介绍了局部动态列表相关的内容&#xff0c;本章回中将介绍权限管理包 permission_hanadler.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 权限是使用某种功能的授权&#xff0c;比如使用手机上的相机…...

【机器学习】sklearn数据集的使用,数据集的获取和划分

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 sklearn数据集 二、安装sklearn二、获取数据集三、…...

Mysql之 optimizer_trace 相关总结

Mysql之 optimizer_trace 相关总结 MySQL官网介绍&#xff1a;https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_OPT_TRACE.html 1. 简介 MySQL优化器可以生成Explain执行计划&#xff0c;通过执行计划查看sql是否使用了索引&#xff0c;使用了哪种索&#xff1b; 但…...

【Linux命令详解 | wget命令】 wget命令用于从网络下载文件,支持HTTP、HTTPS和FTP协议

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 基本文件下载2. 递归下载整个网站3. 限制下载速率4. 防止SSL证书校验5. 断点续传6. 指定保存目录7. 自定义保存文件名8. 增量下载9. 使用HTTP代理10. 后台下载 总结 简介 在编程世界中&#xff0c;处理网络资源是…...

DockePod信号处理机制与僵尸进程优化

Docke&Pod信号处理与僵尸进程优化 容器与信号的关系 SIGTERM信号&#xff1a;程序结束(terminate)信号&#xff0c;这是用来终止进程的标准信号&#xff0c;也是 kill 、 killall 、 pkill 命令所发送的默认信号。与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程…...

NetApp StorageGRID 对象存储,使您能够跨公有、私有云和混合多云环境管理非结构化数据

NetApp StorageGRID 对象存储&#xff0c;使您能够跨公有、私有云和混合多云环境管理非结构化数据 主要优势 智能&#xff1a;了解行业领先的数据生命周期管理软件。 • 借助 NetApp StorageGRID 基于对象的存储解决方案的数据管理功能、您可以从大型非结构化数据中获得高价值…...

使用Java服务器实现UDP消息的发送和接收(多线程)

目录 简介&#xff1a;1. 导入必要的库2. 创建服务器端代码3. 创建客户端代码4. 实现多线程处理5. 测试运行示例代码&#xff1a;函数说明服务器端代码说明&#xff1a;客户端代码说明&#xff1a; 总结&#xff1a; 简介&#xff1a; 在本篇博客中&#xff0c;我们将介绍如何…...

Linux--查看端口占用情况

查看端口占用情况 在Linux使用过程中&#xff0c;需要了解当前系统开放了哪些端口&#xff0c;并且要查看开放这些端口的具体进程和用户&#xff0c;可以通过netstat命令进行简单查询 netstat命令各个参数说明如下&#xff1a;   -t : 指明显示TCP端口   -u : 指明显示UDP…...

微信小程序|自定义弹窗组件

目录 引言小程序的流行和重要性自定义弹出组件作为提升用户体验和界面交互的有效方式什么是自定义弹出组件自定义弹出组件的概念弹出层组件在小程序中的作用和优势为什么需要自定义弹出组件现有的标准弹窗组件的局限性自定义弹出组件在解决这些问题上的优势最佳实践和注意事...

【数据结构】实现顺序表

目录 一.介绍顺序表二.实现顺序表1.创建多文件2.顺序表的存储方式3.函数的声明4.初始化顺序表5.清理顺序表6.打印顺序表7.扩容8.尾插8.尾删9.头插10.头删11.查找12.修改13.在pos位置插入13.在pos位置删除 三.全部代码1.SeqList.h2.SeqList.c3.Test.c 一.介绍顺序表 顺序表是用…...

【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】

目录 1、 Frmebuffer(帧缓冲&#xff09;操作介绍1.1 显示设备的抽象1.2 内存映像1.3 输出画面数据1.4 用户态下操作屏显1.4.1 用文件I / O 操作屏显1.4.2 mmap() 函数1.4.3 ioctl()函数1.4.5 用命令操作屏1.4.6 测试程序 2、Framebuffer总体框架2.1 框架要点2.2 fbmem.c分析2.…...

自己动手写数据库系统:实现一个小型SQL解释器(中)

我们接上节内容继续完成SQL解释器的代码解析工作。下面我们实现对update语句的解析&#xff0c;其语法如下&#xff1a; UpdateCmd -> INSERT | DELETE | MODIFY | CREATE Create -> CreateTable | CreateView | CreateIndex Insert -> INSERT INTO ID LEFT_PARAS Fie…...

HTML 与 XHTML 二者有什么区别

HTML 与 XHTML 二者有什么区别&#xff0c;你觉得应该使用哪一个并说出理由。 HTML 与 XHTML 之间的差别&#xff0c;主要分为功能上的差别和书写习惯的差别两方面。 关于功能上的差别&#xff0c;主要是 XHTML 可兼容各大浏览器、手机以及 PDA&#xff0c;并且浏览器也能快速正…...

fiddler抓包问题记录,支持https、解决 tunnel to 443

fiddler下载安装步骤及基本配置 fiddler抓包教程&#xff0c;如何抓取HTTPS请求&#xff0c;详细教程 可能遇到的问题及解决方案 1. 不能正常访问页面&#xff08;所有https都无法访问&#xff09; 解决方案&#xff1a;查看下面配置是否正确 Rules-customization 找到 OnB…...

Kubesphere中DevOps流水线无法部署/部署失败

摘要 总算能让devops运行以后&#xff0c;流水线却卡在了deploy这一步。碰到了两个比较大的问题&#xff0c;一个是无法使用k8sp自带的kubeconfig认证去部署&#xff1b;一个是部署好了以后但是没有办法解析镜像名。 版本信息 k8s&#xff1a;v1.21.5 k8sp&#xff1a;v3.3.…...

使用Nginx解决跨域问题

前言&#xff1a; 项目是公司的老项目&#xff0c;只有部署在服务器上的时候&#xff0c;项目才可以正常运行&#xff08;接口是通的&#xff09;&#xff1b;现在需求&#xff1a;在现有的项目代码上进行修改&#xff0c;请求接口是第三方给的。接口是正常的&#xff0c;通过A…...

在 OpenCV 中使用深度学习进行年龄检测-附源码

文末附完整源码和模型文件下载链接 在本教程中,我们将了解使用 OpenCV 创建年龄预测器和性别分类器项目的整个过程。 年龄检测 我们的目标是创建一个程序,使用图像来预测人的性别和年龄。但预测年龄可能并不像你想象的那么简单,为什么呢?您可能会认为年龄预测是一个回归问…...