数据结构--二叉树

目录

 1.二叉树链式结构的实现

1.1 前置说明

1.2 二叉树的遍历

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

1.2.2 层序遍历及判断是否为完全二叉树

1.3 节点个数,叶子节点个数,第k层节点个数以及高度等

1.4 二叉树的创建和销毁


 1.二叉树链式结构的实现


1.1 前置说明

        这时直接创建的二叉树,方便于各个接口函数的测试,当然你也可以选择1.4的方法直接创建。

typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}TreeNode;TreeNode* BuyTreeNode(int x)
{TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));assert(node);node->data = x;node->left = NULL;node->right = NULL;return node;
}
TreeNode* CreateTree()
{TreeNode* node1 = BuyTreeNode(1);TreeNode* node2 = BuyTreeNode(2);TreeNode* node3 = BuyTreeNode(3);TreeNode* node4 = BuyTreeNode(4);TreeNode* node5 = BuyTreeNode(5);TreeNode* node6 = BuyTreeNode(6);TreeNode* node7 = BuyTreeNode(7);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;node5->right = node7;return node1;
}

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
        1. 空树
        2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

        从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。


1.2 二叉树的遍历


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

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

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

下面主要分析前序递归遍历,中序与后序图解类似,可以自己画画看。

        (1.)**前序遍历**,也称为**深度优先遍历**。它从根节点开始,递归地访问左子树,然后访问右子树。 二叉树的前序遍历是按以下顺序访问节点的序列: 1. 根节点 2. 根节点的左子树 3. 根节点的右子树

        当左边递归到空时,会从叶子节点开始依次返回递归的结果,然后开始遍历右子树,递归迭代。
前序遍历递归图解:

前序遍历的代码:

void PrevOrder(TreeNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}

前序遍历结果为:1 2 3 4 5 6

        (2.)**中序遍历**。它从根节点开始,递归地访问左子树,然后访问当前节点,然后访问右子树。 二叉树的中序遍历是按以下顺序访问节点的序列: 1. 当前节点的左子树 2. 当前节点 3. 当前节点的右子树

中序遍历代码:

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

中序遍历的结果:3 2 1 5 4 6

        (3.)**后序遍历**。它从根节点开始,递归地访问左子树,然后访问右子树,最后访问根节点。 二叉树的后序遍历是按以下顺序访问节点的序列: 1. 左子树的所有节点 2. 右子树的所有节点 3. 根节点

后序遍历代码:

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

前序遍历结果为:3 2 5 6 4 1


1.2.2 层序遍历及判断是否为完全二叉树

层序遍历(又称广度优先遍历):除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

        实现层序遍历,我们可以用到队列,A进入队列,可以去到B和C的地址,让A出队就能取到A的数据,然后通过B可以取到D、通过C可以取到E,F,让他们依次入队出队,就能取到每一层 的节点,最后队列为空就结束

void LevelOrder(TreeNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);int levelSize = 1;while (!QueueEmpty(&q)){// 一层一层出while (levelSize--){TreeNode* front = QueueFront(&q);QueuePop(&q);//这里pop掉了front也能取到,因为这只是pop掉了指向节点的指针//并没有真的把节点pop掉了printf("%d ", front->data);if (front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}printf("\n");levelSize = QueueSize(&q);}printf("\n");QueueDestroy(&q);
}

判断是否为完全二叉树

        在层序遍历的基础上,我们稍作修改就可以了。我们再出队列的过程中,如果遇到了NULL,那我们就break,然后去判断NULL的后面是否有数据,如果NULL的后面还有数据,那么这就不是一个完全二叉树。

bool TreeComplete(TreeNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);int levelSize = 1;while (!QueueEmpty(&q)){TreeNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL)break;QueuePush(&q, front->left);QueuePush(&q, front->right);}// 前面遇到空以后,后面还有非空就不是完全二叉树while (!QueueEmpty(&q)){TreeNode* front = QueueFront(&q);QueuePop(&q);if (front)//判断是否为NULL,不是NULL返回false{QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

1.3 节点个数,叶子节点个数,第k层节点个数以及高度等

接口函数

// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

1.二叉树节点的个数

        我们用分治的思想,依次遍历左右两子树,如果不是空则+1

int TreeSize(TreeNode* root)
{return root == NULL ? 0 : TreeSize(root->left) +TreeSize(root->right) + 1;
}

2.叶子节点的个数

        叶子节点的特征就是,左孩子右孩子都为空,则视为叶子节点,分别遍历两个子树的叶子节点,是叶子节点就返回1。

int treeleafsize(TreeNode* root)
{//空返回0if (root == NULL)return 0;//是叶子返回1if ((root->left == NULL) &&(root->right == NULL))return 1;//非0也不是叶子,那继续往下找叶子//分治 叶子=左+右return treeleafsize(root->left) +treeleafsize(root->right);
}

3.第k层的节点个数

        同样的,这里我们也用分治的思想。我们引入了变量k,我们从第一层开始,如果k不等于1的话,我就进行k-1的操作,直到k=1就到达了指定层数,k=1那么节点数就+1,统计每次递归到k=1的节点,就得到了第k层的节点数。

int lvksize(TreeNode* root, int k)
{assert(k > 0);if (root == NULL)return 0;if (k == 1){return 1;}return lvksize(root->left, k - 1) + lvksize(root->right, k - 1);
}

4.二叉树查找值为x的节点

        节点的查找不再是简单的遍历,我们递归一次就要保存一次递归的节点,因为递归是返回给每次调用的函数本身,函数是不能存值的,因此我们需要一个变量保存。

TreeNode* findtree(TreeNode* root, int x)
{if (root == NULL){return NULL;}if (root->data==x){return root;}//保存左子树的结果TreeNode* ret=findtree(root->left,x);if (ret){return ret;} //保存右子树的结果TreeNode* ret1=findtree(root->right, x);if (ret1){return ret1;}
}

5. 二叉树的高度

要找到二叉树的高度,我们可以使用以下算法(同样是分治的思想):

        1. 从根节点开始。

        2. 如果节点没有子节点,那么它的高度为 0。

        3.分别遍历左右子树

        4. 如果节点有一个子节点,那么它的高度为 1 加上其子节点的高度。

        5. 如果节点有两个子节点,那么它的高度为 1 加上其子节点高度的最大值。

        6.比较左右子树的大小,大的那个为二叉树的高度,最后加上根节点,就得到了二叉树的高度。

int TreeHeight(TreeNode* root)
{if (root == NULL)return 0;return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
}

这里我们用到fmax函数进行比较,当然你也可以选择使用运算符进行比较。


1.4 二叉树的创建和销毁

1.二叉树的创建

        用先序,中序,后序的方式去直接创建二叉树,那么,知道先序的序列就用先序的序列去递归创建树,知道中序的序列就用中序的序列去递归创建树,知道后序的序列就用后序的序列去递归创建树

eg:用数组来存储

TreeNode* TreeCreate(char* a, int* pi)
{if (a[*pi] == '#'){(*pi)++;return NULL;}TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));if (root == NULL){perror("malloc fail");exit(-1);}root->data = a[(*pi)++];root->left = TreeCreate(a, pi);root->right = TreeCreate(a, pi);return root;
}

2.二叉树的销毁

        关于二叉树的销毁,你可以以任意序列的去销毁二叉树

void destroy_tree(Node *root) {if (root == NULL) {return;}destroy_tree(root->left);destroy_tree(root->right);free(root);
}

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

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

相关文章

Mysql启动占用内存过高解决

Hi, I’m Shendi Mysql启动占用内存过高解决 前言 最近服务器内存不够用了,甚至还出现了内存溢出问题,导致程序宕机。但请求与用户量并没有多少,所以从各种启动的程序中想方设法的尽可能的减少其占用的内存。 而在我的服务器中,…

几何尺寸智能测量仪为您带来经济效益提升

线材、棒材、管材、板材等产品的品质检测离不开一些基础几何尺寸的检测,随着产线自动化的提升,越来越多的产线开始使用智能测量仪,这不仅仅是因为其能带来品质的提升,更是因为其能带来的经济效益。 几何尺寸智能测量仪种类繁多&am…

JAVA网络编程——BIO、NIO、AIO深度解析

I/O 一直是很多Java同学难以理解的一个知识点,这篇帖子将会从底层原理上带你理解I/O,让你看清I/O相关问题的本质。 1、I/O的概念 I/O 的全称是Input/Output。虽常谈及I/O,但想必你也一时不能给出一个完整的定义。搜索了谷哥欠,发…

区块链创新应用场景不断拓展,实现去中心化

小编介绍:10年专注商业模式设计及软件开发,擅长企业生态商业模式,商业零售会员增长裂变模式策划、商业闭环模式设计及方案落地;扶持10余个电商平台做到营收过千万,数百个平台达到百万会员,欢迎咨询。 区块…

【Vulnhub 靶场】【BuffEMR: 1.0.1】【简单 - 中等】【20210831】

1、环境介绍 靶场介绍:https://www.vulnhub.com/entry/buffemr-101,717/ 靶场下载:https://download.vulnhub.com/buffemr/BuffEMR-v1.0.1.ova 靶场难度:简单 - 中等 发布日期:2021年08月31日 文件大小:4.6 GB 靶场作…

为什么每个 Java 开发者都需要了解 Scala

前面我们一起回顾了第九期 Scala & Java Meetup 中最受关注的话题 —— jdk 并发编程的终极解决方案:虚拟线程,探讨了这一新特性对包括 Scala 在内的响应式编程语言的影响。 本次 Meetup 的首位分享者 Chunsen,在加入 Tubi 成为 Scala 开…

【投稿优惠|检索稳定】2024年信息系统、工程与数字化经济国际会议(ICISEDE 2024)

2024年信息系统、工程与数字化经济国际会议(ICISEDE 2024) 2024 International Conference on Information Systems and Engineering and the Digital Economy(ICISEDE 2024) [会议简介] 2024 年信息系统、工程与数字化经济国际会议(ICISEDE 2024)将于 2024 年 1 月 20 日在厦门…

Endnote在word中加入参考文献及自定义参考文献格式方式

第一部分:在word中增加引用步骤 1、先下载对应文献的endnote引用格式,如在谷歌学术中的下载格式如下: 2、在endnote中打开存储env的格式库,导入对应下载的文件格式:file>import>file>choose,import对应文件&a…

IT外包驻场加速企业IT创新

随着科技的快速发展,企业在追求创新和应用IT技术方面面临挑战。IT外包驻场服务成为许多企业的选择,助力企业实现快速、高效的IT项目实施和应用。 IT外包驻场服务的主要目标是帮助企业在IT创新方面取得突破。这种服务模式不仅仅是提供技术支持&#xff0c…

配电室无人值守改造

配电室无人值守改造是通过运用先进的技术和设备,将传统的需要人工值守的配电室改造成可以远程监控和管理的智能化配电室,从而实现无人值守。这种改造可以提高配电室的安全性、可靠性和效率,降低运维成本。 建立智能监控系统:通过安…

Vue3选项式-基础部分篇

Vue3选项式风格-基础部分篇 简介模板语法文本插值原始HTMLAttribute 绑定使用 JavaScript 表达式调用函数全局组件调用内置指令动态参数注意事项 data()data()深度响应 methods有状态的methods(防抖) DOM更新时机计算属性class和style绑定条件渲染列表渲染数组变换侦听事件处理…

echart中定义brush,默认状态,触发状态

1.定义矩形选择笔刷:brush 2.设置brush的默认状态和选中逻辑

理解VAE(变分自编码器)

1.贝叶斯公式 贝叶斯理论的思路是,在主观判断的基础上,先估计一个值(先验概率),然后根据观察的新信息不断修正(可能性函数)。 P(A):没有数据B的支持下,A发生的概率,也叫做先验概率。…

小视频怎么做成二维码?视频二维码3步生成

在日常工作和生活中经常会看到各种类型的小视频、短视频,比如网页、抖音等等的视频都是可以下载查看的。当我们想要将下载视频分享给多个人看时,生成二维码的方式会更加的方便,那么视频如何生成二维码呢?下面就将快捷生成二维码的…

第75讲:MySQL数据库MVCC多版本并发控制核心概念以及底层原理

文章目录 1.当前读与快照读的基本概念1.1.当前读的基本概念1.2.快照读的基本概念 2.什么是MVCC多版本并发控制3.MVCC多版本并发控制依赖的三个组件重要概念3.1.MySQL表中三个隐式字段的概念3.2.undo log日志以及版本链的概念3.3.ReadView读视图的概念 4.MVCC实现多版本并发控制…

【FPGA】Verilog:BCD 加法器的实现

0x00 XOR 运算在 2 的补码加减法中的应用 2 的补码加减法的特点是,当从某个数中减去负数时,将其转换为正数的加法来计算,并将减去正数的情况转换为负数的加法来计算,从而将所有减法运算转换为加法运算。在这种情况下,…

SpringAMQP 快速入门

SpringAMQP 快速入门 1. 创建项目2. 快速入门2.2.1 消息发送2.2.2 消息接收 3. 交换机3.1 Fanout Exchange(扇出交换机)3.1.1 创建队列与交换机3.1.2 消息接收3.1.3 消息发送 3.2 Direct Exchange(直连交换机)3.2.1 创建交换机与队…

Validate 验证规则详解

前言: 以前小编发过一篇Validate 验证规则 如何使用的,没有去将Validate 验证规则的原理应用场景,这篇文章来完善一下。 不知道如何使用的朋友可以点击下面传送门 传送门 讲解: Validate 验证规则通常指的是在 Web 开发中,使用验证器&…

【开源】基于Vue.js的智慧社区业务综合平台

文末获取源码,项目编号: S 077 。 \color{red}{文末获取源码,项目编号:S077。} 文末获取源码,项目编号:S077。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 业务类型模块2.2 基础业务模块2.3 预…

精选Axure原型设计模板,RP原型组件库(PC端移动端元件库及Axure函数及运算符说明)

好的原型组件会大大的提高产品经理的工作效率,现精选了一批Axure 8的原型设计模板,包含了原型设计的常用元素和AxureRP 8函数及运算符的说明文档,及各种设备模板框架。 分享给大家可以共同学习,文末可下载完整原型组件包~&#x…