数据结构杂谈(八)——树(上)

文章目录

  • 8 树(上)
    • 8.1 引入
    • 8.2 树的基础知识
    • 8.3 树的存储结构
      • 8.3.1 双亲表示法
      • 8.3.2 孩子表示法
    • 8.4 二叉树
      • 8.4.1 基础知识
      • 8.4.2 高频考点
      • 8.4.3 二叉树的性质
      • 8.4.4 二叉链表
      • 8.4.5 树和二叉树的转换
      • 8.4.6 森林和二叉树的转换
    • 8.5 遍历

8 树(上)

8.1 引入

我们在前面的章节中一直在谈一对一的线性结构,可现实中,还有很多一对多的情况需要处理。

一对一和一对多

为什么说前面学习的是一对一的线性结构?从顺序表中我们可以看出,一个结点总是跟在一个结点的后面,栈、串、队列也皆是如此,故我们说它们都是一对一的线性结构。

image-20220510123502810

可是树却不是一对一的线性结构,因为对于树来说,其一个结点的后面可能跟着多个结点,故它是一种一对多的线性结构。

image-20220510123558016

8.2 树的基础知识

我们来讨论一下

物如其名,树结构看起来就像一颗倒挂的是树。树实际上也是一个递归结构,如果我们把A1称为父结点,则A1下的A2、A3、A4就是它的子结点。那么对于A1来说,其子结点有3个,对于A2来说,其也可能有子结点。

image-20220510123918043

不同的术语

有时候,不同的教材不同的地方有不同的术语,如父子结点有时候也叫做双亲和孩子。

我们把某一结点的分支数量叫做结点的度。对于上图,显然A1结点的度是3,A7结点的度为0。我们把整棵树中所有结点拥有的最大分支数称为树的度,如上图中树的度为3,因为遍历树中所有结点,度最大的那个结点度为3。

同理,在上图中,A5的祖先为A2和A1,A1的子孙为A2~A7。

一棵树可以分为多个层。如下图所示:

image-20220510124921941

同一个双亲的结点互称兄弟,如A2可以是A3的兄弟结点,反过来A3是A2的兄弟结点这种表述也无误;树的层数叫做树的高度或深度,如上图中树的深度是3。结点从树的上面往下面数,第几层即为结点的深度;结点从下往上面数,第几层即为结点的高度。如A1,它从下往上数在第三层,故A1结点高度为3,而从上往下数第一层,故其深度为1。

树的叶结点指的是:若结点没有继续往下的分支了,那么该结点即为叶结点。如上图中A5,A6,A3,A4均为叶结点。

image-20220510132304675

408科目10年05题

在一颗度为4的树T中,若有20个度为4的结点,10个度为3的结点,1个度为2的结点,10个度为1的结点,则树T的叶结点个数为?

  • 10∗1(10个度为1)+1∗2(1个度为2)+10∗3(10个度为3)+20∗4(20个度为4)=12210*1(10个度为1)+1*2(1个度为2)+10*3(10个度为3)+20*4(20个度为4) = 122101(101)+12(12)+103(103)+204(204)=122
  • 123(总节点数)=10+1+10+20+叶结点123(总节点数) = 10+1+10+20+叶结点123()=10+1+10+20+

故叶结点数为82。

8.3 树的存储结构

通过对前面的学习,我们需要思考一下如何用代码实现树的顺序存储,树提供了三种表示方法,分别是双亲表示法孩子表示法孩子兄弟表示法

8.3.1 双亲表示法

双亲表示法的重点在于,我们存储的内容不仅仅是结点的数据,而且还要存储其父节点是谁。故我们想到在结构体中定义两个数据,一个用于存结点数据,另一个用于存父节点在数组中的下标。

由于根结点是没有双亲的,故我们约定根节点的双亲位置设置为-1。如下图所示:

image-20220510132105842

#define MAX_TREE_SIZE 100//结点结构
typedef struct
{Elem data;//结点数据int parent;//双亲位置
}PTNode;//树结构
typedef struct
{PTNode node[MAX_TREE_SIZE];int r,n;//根的位置和节点数
}

这样的存储结构有一个好处,我们可以根据结点中存储的双亲位置来找到它的双亲结点,但是问题是,如果你想要知道结点的孩子是什么就无能为力了,你得遍历整个结构。

8.3.2 孩子表示法

如果树中含有多颗子树,我们可以考虑使用多重链表。多重链表的意思就是每个链表节点中都包含有多个指针域,每个指针域指向子树的根节点。但是这种表示法有一个问题,就是每个子树的度是不一样的,有些子树的度是1有些是2甚至于度为0。

对于上述的问题我们有两种不同的方案。

方案一

我们直接可以让树的度作为指针域的个数。我们知道,树的度是树中所有结点的最大度数。这样的话子树的度小于树的度时,我们把其余的指针域置空即可。如下所示:

image-20220510140743360

但是这种方案在于浪费内存。我们来看看方案二。

方案二

既然怕浪费,那我们就按需分配。

我们在结构体定义时多加一个整形类型的变量degree用于记录度域(即度的范围)。如下所示:

image-20220510141218528

这种方案虽然避免了浪费空间,但是却浪费了时间,因为要维护度的数值。

既然上述方案都不太靠谱,我们就用方案3的孩子表示法吧。具体办法是,把每个结点的孩子结点排列,用单链表作为存储结构。如果是叶子结点则单链表为空。

以上的文字可能有点晦涩难懂,我们看图理解一下。

image-20220510142026928

一开始我们用结构体定义两个变量。一个是结点数据,一个是指针域,其结构体看做是一个结点。而后将所有的结点存储一个数组中。如果一个结点有孩子,那么其指针域指向存放其孩子的链表,链表中存储的是关于该节点的所有孩子。

如上图所示,A1是有三个孩子A2、A3、A4,故其孩子链表中存放三个结点,孩子链表的头指针存于数组的0号位,数组的0号位存储的是一个结点的结构体,结构体中有A1和其孩子链表的头指针。

8.4 二叉树

8.4.1 基础知识

在8.3.2 讲的孩子表示法中,孩子链表中存储的结点是没有顺序之分的。也就是说,传统的树没有任何约束,度数可以任意,孩子之间也没有次序。

image-20220510142733938

为此,我们引入了二叉树。二叉树规定每个节点的孩子最多只能有两个,即度数<=2;并且其孩子节点中,左边的孩子叫做左孩子,右边的叫右孩子

image-20220510142947343

二叉树也可以有多种情况,如下图所示:

image-20220510143047632

如果一颗二叉树是满的,比如一共三层,每层都是满的。如下图所示:

image-20220510143223461

这个时候我们叫他满二叉树

如果将一颗二叉树从下往上,总右往左删除,它们无论怎么删除,它都是一颗完全二叉树

image-20220510143328617

image-20220510143350661

也就是说,如果一颗满二叉树少了A7但有A8,其他不变,那么这个二叉树就不是完全二叉树。并且,满二叉树可以看做是完全二叉树的特别版。

8.4.2 高频考点

第一个比较常考的是关于求完全二叉树的高度。完全二叉树的高度由二叉树中的结点个数来决定。为了方便讲解,我们先探究满二叉树的规律。通过列举或者通过高中的等比数列知识都是可以发现这个规律的。如下所示:

image-20220510143819564

也就是说,一个高为h的满二叉树,其结点为2h−12^h-12h1。那么,一个高位h的完全二叉树结点数肯定小于2h−12^h-12h1且大于2h−1−12^{h-1}-12h11

我们可以将上述的不等式进行化简,如下所示:

image-20220510144606025

至此,我们可以导出完全二叉树的高度公式了。

有时候我们我们可能会见到不一样的公式,这是因为在上述化简第二步时选择直接把1扔掉还是通过计算同时+1的差异导致的。

image-20220510144909490

408科目09年05题

已知一颗完全二叉树的第6层(设根为第1层)有8个叶结点,则该完全二叉树的结点个数最多是?

考虑到最多,那么必然是这种情况:

image-20220510145852082

也就是说,前五层结点数为:2^5-1 = 31,满6层的二叉树结点为2^6-1 = 63,故第六层结点数为63-31 = 32,又叶子结点为8,故非叶子结点有32-8 = 24,故这个完全二叉树的结点个数最多是:63+24*2=111个

8.4.3 二叉树的性质

最简单的,总分支数= 总结点数-1,这个很简单,对于传统的树也是同样满足的。

设分支数为i的结点数为NiN_iNi,则满足总结点数N=N0+N1+N2N = N_0+N_1+N_2N=N0+N1+N2。同样地,总分支数N−1=N1+2N2N-1 = N_1+2N_2N1=N1+2N2,这个在我们前面学基础知识时就已经提到过了。

对于上述的方程,我们可以解得N0=N2+1N_0 = N_2+1N0=N2+1,故叶子结点数 = 双分支结点数+1,这是一个非常重要的结论。有时考题里面会说二叉树含有空分支,此时考题就更为灵活了,需要注意一下。

对于一颗存储在数组的完全二叉树来说。

image-20220510152249339

父结点位置如果为i,则左孩子结点位置为2i+1;右孩子结点位置为2i+2。

以上的规律在不同的学校考题中有所不同,如果位序从1开始,则左孩子结点位置为2i;右孩子结点为2i+1。

8.4.4 二叉链表

对于二叉树的链式存储来说和树的链式存储并无二致,并且我们可以说明存放孩子结点的链表第一个结点为左孩子,第二个结点为右孩子,这完全没有问题。

image-20220510153017655

但是我们可以回想我们使用孩子表示法的初衷。是由于每个子树度数的不确定性我们才使用这种方法,但是现在二叉树确定了,为何还要怎么搞,岂不大材小用?

我们不如回归本心,写一个结点结构体,结构体中含有左右子结点的指针域和结点所含数据,然后将所有结点放于数组中即可。

image-20220510153354800

上述的结构我们称为二叉链表

二叉链表实际上可以用在传统树的存储,因为在之前的孩子表示法设计中,某结点的孩子结点是作为链表接在结点的指针域中的,所有孩子结点不需要都和父节点有关系,只需要其中一个和父节点有关系即可。故我们可以改造传统树,变成二叉链表能够存储的样子:

image-20220510154137952

image-20220510154203725

以上这种表示方法我们称为孩子兄弟表示法

8.4.5 树和二叉树的转换

前面说过的转换我们只是口头讲述,并没有一种系统的方式来转换。

一种简单的方式是,将兄弟结点用一条线连起,然后只保留一条通往父节点的线,其他多余的线删除,如下图所示:

image-20220510154920025

删除线后,我们把它掰成二叉树的模样即可。

image-20220510154959340

如果想要将二叉树转为树,只需要从根节点开始,一条路从头走到尾,途径的所有结点都加上一条线和根节点即可,对于其他剩余的结点在也可以用同样的操作。如下图所示:

image-20220510155246820

然后掰回树。

image-20220510155319606

8.4.6 森林和二叉树的转换

image-20220510155634386

森林就是多棵树放在一起,如果想要转换为二叉树,只需将森林中的所有树先单颗转为二叉树。需要注意的是上图的第三颗树,它看起来像是二叉树,但我们不确定,故我们也要对他做转化的工作,全部转换后如下所示:

image-20220510155727534

全部的单树转换为二叉树后,我们只需连接所有单树的根节点的右分支即可,如下所示:

image-20220510155827892

同理,如果想要将二叉树还原为森林,只需将右分支删掉,然后看看单树的根节点的右分支是否为空,非空则继续删掉右分支,为空则将所有单树还原为传统树即可。

408科目09年06题

将森林转换为对应的二叉树,若在二叉树中,结点u是结点v的父结点的父结点,则在原来的森林中,u和v可能具有的关系是?

我们可以画出可能的二叉树,如下所示:

然后将按上述的方法还原,结果u和v可能的关系有父子关系和兄弟关系。

8.5 遍历

遍历一词在如今才出现,为何前面我们不提遍历?因为前面一对一的线性结构的遍历过于简单,只需从头到尾走一遍即可。

但是对于一对多的树,我们要如何去遍历它呢?

以二叉树为例,我们需要制定一定的规则来访问。第一个遍历的想法是,我们从上到下,从左到右进行遍历,如图所示:

image-20220510211009369

以上提到的这种思路我们叫做层次遍历,也叫广度优先遍历

我们还有第二种想法,我们称为深度优先遍历。其又细分为先序、中序和后序遍历。

让我们来体会一下这个想法是怎么实现的。如下图所示:

image-20220510211403701

在这个过程中,我们可以发现A2这个结点被走过三次,如A1-A2-A4-A5-…,这样就可以算一次遍历。但是,我们如果说遍历是A4-A2-A5-A1-A6-A3…,实际上也没有任何毛病。同样地,A4-A5-A2-A6-A3-A1…也是可以的。

也就是说,如果是先访问根节点,然后先序遍历左子树,最后遍历右子树,则称为先序遍历;如果是先遍历左子树,然后访问根节点,最后遍历右子树则称为中序遍历;如果是先遍历左子树然后遍历右子树,最后遍历根节点则称为后序遍历

我们也可以换一种思考方式。如果一个结点第一次走过时就遍历它,那么就是先序遍历,如果走过第二次再遍历则称中序遍历,如果走过第三次才遍历则称后序遍历

408科目09年03题

image-20220510214513046

答案明显是D。这里就不多解释了。

树的层次遍历和二叉树的差不多,有差别的是深度优先遍历。如下所示:

image-20220510214846695

在树的遍历中,每个节点就不一定是经过三次了,故相对于前面的三种深度优先遍历方式,这里只有两种,即先序遍历后序遍历

我们前面说过树可以转换为二叉树。树的先序遍历和转换后的二叉树先序遍历是一样的,而树的后序遍历和转换后的二叉树的中序遍历是一样的。

image-20220510215547943

对于森林的遍历也很简单,先序遍历指的就是从左到右对每一棵树进行先序遍历;而后序遍历指的就是从左到右对每一棵树进行后序遍历。

同样的,如果将森林转为二叉树,其遍历的改变和树的改变是一样的。即森林的先序等效于转换后二叉树的先序,森林的后序等效于二叉树的中序

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

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

相关文章

Maven(一)——快速上手Maven

文章目录Maven概述Maven简介Maven的安装Maven的基本使用IDEA配置Maven依赖管理依赖范围Maven概述 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; &#xff08;一&#xff09;提供了一套标准化的项目结构 不同的IDE项目结构是不一样的&…

EntityFramework_MVC4中EF5 新手入门教程之三 ---3.排序、 筛选和分页

在前面的教程你实施了一套基本的 CRUD 操作&#xff0c;为Student实体的 web 页。在本教程中&#xff0c;您将添加排序、 筛选和分页到 StudentsIndex的功能。您还将创建一个页面&#xff0c;并简单分组。 下面的插图显示页面当你完成时的样子。列标题是链接&#xff0c;用户可…

fluentd主从配置

fluentd是一个免费的、完全开源的日志管理工具&#xff0c;可以对日志进行收集、处理、存储。对于一些高流量的网站或者特殊的架构&#xff0c;需要fluentd高可用配置。 以下是在测试环境搭建模拟fluentd主从配置&#xff0c;模拟主从切换。 服务器 服务 192.168.199.1 elas…

Hadoop总结

目录 大数据概述 Hadoop大数据开发平台 资源管理YARN 分布式文件系统HDFS 非关系型数据库NOSQL 分布式数据库HBASE 批处理和MapReduce 数据仓库查询分析和Hive 基于内存计算的Spark 流计算和Flink 图计算和PREGEL Hadoop常用命令总结 大数据概述 大数据的4V&#x…

HDFS的常用操作

1、HDFS文件的权限以及读写操作 HDFS文件的权限&#xff1a; •与Linux文件权限类似 •r: read; w:write; x:execute&#xff0c;权限x对于文件忽略&#xff0c;对于文件夹表示是否允许访问其内容 •如果Linux系统用户zhangsan使用hadoop命令创建一个文件&#xff0c;那么这个文…

并行计算总结

作者&#xff1a;ArimaMisaki 目录 1 并行计算概述. 2 1.1 基本概念. 2 1.2 存储器的层次结构. 3 1.3 并行计算. 3 1.4 动态互连网络. 4 1.5 并行计算机结构模型. 5 1.6 并行算法的基本设计策略. 6 1.7 并行编程风范. 6 1.8 单核多线程和并发执行. 7 1.9 拓展&#x…

基础总结篇之中的一个:Activity生命周期

子曰&#xff1a;溫故而知新&#xff0c;能够為師矣。《論語》 学习技术也一样&#xff0c;对于技术文档或者经典的技术书籍来说&#xff0c;指望看一遍就全然掌握&#xff0c;那基本不大可能&#xff0c;所以我们须要常常回过头再细致研读几遍&#xff0c;以领悟到作者的思想精…

数据结构杂谈(九)——二叉树的遍历

9 二叉树的遍历 文章目录9 二叉树的遍历9.1 递归函数基础9.2 深度优先遍历的实现9.3 二叉树层次遍历9.1 递归函数基础 什么是递归&#xff1f;调用自身就是叫递归&#xff0c;如下所示&#xff1a; void r(){r(); }我们习惯借用阶梯图来帮助我们理解这些知识。如果是同一层函数…

洛谷 P3750 [六省联考2017]分手是祝愿

传送门 题解 //Achen #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<vector> #include<cstdio> #include<queue> #include<cmath> const int N100000,mod100003; #define For(i,a,b)…

解决 error: command 'swig' failed with exit status 1

2019独角兽企业重金招聘Python工程师标准>>> # pip install docker-registry 分析&#xff1a;观察出现的错误&#xff0c;发现最开始报错的地方提示不能找到openssl的.h头文件。一般.h头文件都是放到/usr/inclue目录下的&#xff0c;而且头文件所在的安装包一般叫…

Android安全-SO动态库注入

2019独角兽企业重金招聘Python工程师标准>>> 关于这方面技术&#xff0c;网上已经有大把的实现。在此&#xff0c;我只是记录下自己的学习过程。 0x1 原理 所谓的SO注入就是将代码拷贝到目标进程中&#xff0c;并结合函数重定向等其他技术&#xff0c;最终达到监控或…

BUG日志-2022.7.12——关于VSCode感叹号无法生成HTML骨架问题

解决办法&#xff1a;放弃使用多年的!&#xff0c;而采用html:5的形式。 原因&#xff1a;好像是因为VScode已经更新了 好多扩展也失效了。

hadoop 入门实例【转】

原文链接&#xff1a;http://www.cnblogs.com/xia520pi/archive/2012/06/04/2534533.html 1、数据去重 "数据去重"主要是为了掌握和利用并行化思想来对数据进行有意义的筛选。统计大数据集上的数据种类个数、从网站日志中计算访问地等这些看似庞杂的任务都会涉及数据…

AWS安装CDH5.3-CentOS6.4中关键操作步骤

1、在AWS masternode 上下载cloudera-manager-installer.bin安装包 [rootip-172-21-42-114 ~]# wget http://archive.cloudera.com/cm5/installer/latest/coludera-manager-installer.bin 此时会提示&#xff1a;-bash: wget: command not find 所以要现安装wget命令 [rootip-1…

You have an error in your SQL syntax; check the manual that corresponds to...

问题缘由&#xff1a; 使用datagrip插入数据时发生报错 使用插入语句为&#xff1a; insert into ev_name (‘username’,‘password’) values (‘admin’,‘123456’); 错误提示&#xff1a; You have an error in your SQL syntax; check the manual that corresponds to …

getaddrinfo ENOTFOUND 127.0.0.1:3306

错误缘由&#xff1a; 在写nodejs项目时&#xff0c;连接数据库发现找不到数据库。 解决&#xff1a; 发现数据库连接池没有设置端口号&#xff0c;需设置端口号3306。 解决效果&#xff1a; 成功解决

一幅长文细学node.js——一幅长文系列

文章目录1 Node.js概述1.1 初识Node.js1.2 Node.js简介1.3 Node.js安装1.4 使用Node.js运行JS代码2 fs文件系统模块2.1 读取文件2.2 写入文件2.3 路径问题3 Path路径模块3.1 Path模块概述3.2 路径拼接3.3 获取路径的文件名4 Http模块4.1 Http概述4.2 服务器相关的概念4.3 创建W…

一幅长文细学GaussDB(一)——数据库介绍

文章目录1 数据库介绍1.1 数据库技术1.2 数据库技术发展史数据库技术产生和发展数据库三个阶段比较数据库系统优势层次模型网状模型关系模型关系数据库产品历史结构化查询语言SQL面向对象数据模型&#xff08;OO模型&#xff09;数据管理技术的新挑战NoSQL技术特点和类型主要No…

Unity 通过Unity Admob Plugin插件集成admob教程

原创&#xff1a;officemaster.cn下载Unity Admob Demo&#xff0c;插件里面包含Admob_Unity_Demo.unitypackage 插件文件AdmobPluginRes 是Admob 的ios sdk和插件使用样例代码打开样例代码可以看到代码里面如何使用Unity Admob插件把Admob Unity插件添加进unity工程1. 打开Un…