所谓树, 其实跟链表有类似的地方,  就是都是由节点和指针构成的数据结构.

在链表中,  每1个节点(尾节点除外)只有1个指针指向下1个节点. 所以链表各个节点可以由一条线链接起来, 就是一种线性结构.

而在树中, 每1个节点可以有1个或多个指针指向下1个节点,  如下图:

所以树是一种非线性结构, 对于这种结构, 有很多算法都要依赖递归来实现, 否则就相当复杂, 所以很多教程都把递归作为1个专题放在树这章之前讲解, 就是这个原因.

其实树也可以利用连续存储来实现, 也就是说节点不需具有指向别的节点的指针, 但是这种树必须是完全二叉树。 下面会详细提到。

1. 树的定义

树的定义有两种, 一种是专业的定义, 跟递归有关.

        专业定义:

                 1. 只有1个称为根的节点.

                 2. 有若干个互不相交的子树, 这些子树本身也是树.

解析下,子树本身也是树, 就如上上图的树A, 根就是是A了, 它具有2个子树, 分别是B1 和 B2, 而B1和B2也是1棵树啊, 所以, 这个定义跟递归有关.

           通俗定义:

                 1. 树是有节点和边组成.(为什么不用指针这个字眼,用边?因为树是可以利用连续存储来实现的,下面会提到)

                 2. 每1个节点只有1个父节点, 但是可以有多个子节点.

                 3. 但是有1个节点例外, 该节点没有父节点, 此节点就是根节点.

2. 树的一些专业术语.

节点:

构成树的元素, 通常节点里会包含指向其他节点的指针.  在C语言中, 节点通常是由结构体来表示的.

父节点:

节点的上1级节点,  通常1个节点有且只有1个父节点, 根节点例外, 根节点没有父节点.

子节点:

节点的下一级节点, 1个节点可以有1个或多个子节点, 也可以没有子节点.

子孙:

1个节点的子节点或者子节点的子节点....,等所有下级节点都可以成为这个节点的子孙,  1棵树的所有节点都是根节点的子孙.   上图C2 是B1的子孙, 但不是B2的子孙.

兄弟:

拥有同1个父节点的若干个节点成为兄弟, 上图B1 就是 B2的兄弟

堂兄弟:

不属于同1个父节点, 但是它们的父节点属与同1个父节点的若干个节点就是堂兄弟. 上图C1 就是 C3的堂兄弟.

深度:

1棵树从根节点到最底层节点的层数称之为深度.   根节点是第1层.

叶子节点:

没有子节点的节点, 因为叶子不同于树干, 不能再分叉了.  当1个棵树只有1个节点, 那么这个节点是根节点. 也是叶子节点.

非终端节点:

实际就是非叶子节点,  就是具有子节点的节点.

:

1个节点的子节点个数成为这个节点的度

1个树中拥有最大度的节点的度就是这个树的度.

森林:

n个互不相交的树的集合, 成为森林.

3. 树的分类

一般的树:

任意1个节点的子节点的个数都不受限制的树.

二叉树: (binary tree)

任意1个节点的个数最多有连个, 且子节点的位置不能被更改, 这种树就是二叉树.

第一句容易理解, 关键是第二句, 子节点的位置不能更改?   其实意思就是二叉树是有序的, 它的节点最多只有2个子节点, 分别是左子节点和右子节点,  它们的顺序不能被更改. 也不能更改节点的父子点(移动子树),我们称为这种树是有序的树

一般的树没有这种要求, 可以是有序的树, 也可以是无序的树(子节点位置可更改),    但是2叉树必须是有序的树.

二叉树还有分类

满二叉树: (full binary tree)

在不增加层数的情况下,  无法再多添加1个节点的二叉树, 就是满二叉树. 例如上图那个树就是非满2叉树, 因为还可以添加1个C4节点在B2节点下.

添加了C4节点后, 就是1个满二叉树, 如下图:

假如1个满二叉树的层数是k, 那么它的节点个数必定是 (2^k-1).

这里顺便贴个等比数列求和的推导公式:


(1)Sn=a1+a2+a3+...+an(公比为q)
(2)q*Sn=a1*q+a2*q+a3*q+...+an*q =a2+a3+a4+...+a(n+1)
(3)Sn-q*Sn=a1-a(n+1)
(4)(1-q)Sn=a1-a1*q^n
(5)Sn=(a1-a1*q^n)/(1-q)
(6)Sn=(a1-an*q)/(1-q)
(7)Sn=a1(1-q^n)/(1-q)

完全二叉树: (Complete binary tree)

完全二叉树就厉害了,之前提到满二叉树的目的就是为了引出完全二叉树. 满二叉树是完全二叉树的一种。

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

又有1个通俗定义: 当1棵层数为k满二叉树, 从它的最底层的最右边的节点开始,连续向左删除n个节点(2k >= n >= 0), 得到的树就是1棵完全二叉树.

如下图, 左边两个是完全二叉树, 而右边那个不是,因为最下层的节点不是向右连续的啊!

这里补充一点:完全二叉树是效率很高的数据结构,堆是一种完全二叉树,所以效率极高,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化,几乎每次都要考到的二叉排序树的效率也要借助平衡性来提高,而平衡性基于完全二叉树。

这里为什么要特别提到完全二叉树呢?  因为完全二叉树是一种重要的数据结构, 跟栈,对列一样, 二叉树也可以用链式和数组内核来实现, 但是因为数组必须是连续存储的, 所以数组实现的二叉树必须是完全二叉树。

完全二叉树的性质:

1. 如果知道完全二叉树的节点个数, 就必然可以退出完全二叉树的形状(包括层数, 最后一层的节点排布)。  也就是说两个节点树相同的完全二叉树的形状是一样的。

2. 根据第1个性质, 只需要知道1个确定节点个数的完全二叉树的节点编号, 就可以推出这个节点的父节点和字节点! 这个是1个非常重要的优点!

3. 树的存储(内存)

3.1 二叉树的存储:

3.1.1. 连续存储

上面提过了, 要利用连续存储(数组)来实现的二叉树必须是完全二叉树, 因为它们的节点必须是连续的啊! 如果要利用连续存储来存储普通的二叉树, 就必须把这个二叉树转化为完全二叉树。

关键是如何转化? 很简单, 就是添加必要的空节点把1个非完全二叉树补成1个完全二叉树, 如下图:

蓝色的树就是1个普通的2叉树,  白色的节点就是必要的不存放实际数据的节点, 添加上去组成1个完全二叉树, 就可以利用连续存储了。

至于要转成完全二叉树的原因:

假如只按一定的顺序只存储有效的节点,  内存物理上是1个线性结构, 如果把非线性的树存放在线性的内存中,存放后就变成线性结构了,例如上图中的蓝色树在内存里存放: A,B1,C1,C2,D3,D4  的确是能存放, 但是不能将存放的数据还原成原来的树啊! 所以就必须添加一些无效的节点令它转化成1个完全二叉树!

缺点:

其实也可以看出, 如过利用连续存储来存放普通的二叉树, 首先需要一定量的连续内存空间,而且必然会造成1些空间浪费。 而且普通二叉树的层数越大, 耗费的内存越大!

优点:

就是上面完全二叉树的第2个性质,  只需要知道1个节点在数组的位置, 就可以推出它的父节点和子节点, 时间复杂度是0啊!

3.1.2. 链式存储

这个跟链表有点类似。

我们会把每1个节点分成4个域, 分别是数据域, 父节点指针域, 左子节点指针域, 右字节域指针域。(或者不包含父节点指针域,则只有3个域)

如下图:

可以见到,这种存储方式浪费的空间很少, 无非就是一些子节点的空指针, 但是这些空指针个数仍是线性增长的, 但是我们认为浪费的空间已经很少啦, 比起连续存储来讲, 因为链式存储不需存储多余的节点啊。

3.2 一般树的存储:

一般树的特点是节点的度(子节点个数)无限制, 用上面连续存储根本没可能, 如果用链式存储也很困难, 因为无法决定子节点的子节点指针域的个数啊!

3.2.1 双亲表示法(数组):

双亲表示法实际上也是利用连续存储的, 只不过数组内的元素必须分为两个域, 1个是数据域, 另1个是父节点的下标(注意不是指针啊). 根节点的父节点下标是·-1.

双亲表示法对于元素(节点)在数组内的顺序无要求。

如下图:

如上图右边的数组, 基本上可以把1棵树的结构表示出来了, 但是有个问题, 就是无法通过数组得到同1个父子点兄弟之间的顺序, 例如无法知道C1,C2,C3这个3兄弟在树中的顺序, 所以双亲表示法只适合表示无序的树。

优点:

几乎不浪费内存空间, 查找父节点很容易。

缺点:

不能表示有序的树, 查找子节点相对困难, 查找任何1个节点的子节点都必须遍历整个数组。 不能表示有序的树。

3.2.2 孩子表示法(数组):

孩子表示法也是利用连续存储来实现的, 它的节点同样具有两个域, 1个是数据域, 另1个是1个子节点下标链表的头节点地址。

关键就是这个链表了, 链表的节点有两个域, 1个是int类型,用于表示节点下标, 另1个是指针。

如下图:

可以见到, 因为可以利用子节点下标链表的顺序来表示子节点兄弟的顺序, 所以这种表示法是能表示有序树的。

优点:

查找子节点算法很方便, 可以表示有序树

缺点:

查找父节点算法很困难, 必须遍历每1个节点的子节点下标链表, 如上图, 甚至判断哪个是根节点都需要遍历啊(O(n^2))!

3.2.3 双亲孩子表示法(数组):

由上面的例子可以看出, 其实双亲表示法和孩子表示法的优缺点是互补的, 所以把它们合拼在一起就是双亲孩子表示法了。

所谓双亲孩子法就是指表示树的数组内的节点有3个域, 数组域, 父节点下标域,及子节点下标链表域。

如下图:

优点:

具有双亲表示法和孩子表示法的所有优点, 关键是方便地查找父节点和子节点。

缺点:

占用相对更多的内存空间。

3.2.4 二叉树表示法 :

这个的原理就是把一般树转化为二叉树, 然后再利用二叉树的存储方法来存储。

关键是如何把1个一般树转化为二叉树?  一句话:  每个子节点的左子节点指向它原来的第1个子节点, 右子节点指向它原来的下1个兄弟

所以这个方法又叫孩子兄弟链表表示法

如下图:

可以看到, 转换后层数增加了, 而且某个子树的度越大, 转化后所需的层数越多, 毕竟是利用层数来表示兄弟的个数啊。

而且有个特点, 就是转化后的二叉树, 对于根节点来讲, 是没有右字数的, 因为根节点没有兄弟嘛。

优点:

能使用一些二叉树的算法。

缺点:

查找真正的父节点和子节点的算法有点复杂,  例如上图转化后的二叉树, 如果求C3的父节点, 则必须反方向先求出C2和C1..

3.3 森林的存储:

所谓森林上面也提到过了, 就是互不相交(没有公共节点)的n棵树的集合。

例如下图中的三棵树就可以组成1个森林。

一般来讲,

首先: 我们会先把森林的所有树转化为二叉树(孩子兄弟链表表示法)

然后: 把转化后的二叉树合成1个新的二叉树, 怎么合成? 就是把各个二叉树视为兄弟, 然后新增1个根节点作为它们的父子点。

其实不难理解的啦, 转化后如图:

然后就按照二叉树的方法存储就ok了。

数据结构 非线性结构 树 介绍及存储方法相关推荐

  1. 数据结构——非线性结构(树与二叉树)

    文章目录 一. 非线性结构的概述 二. 树的基本概念 1. 树的定义 2. 专业术语 3. 树的性质 三. 树的分类 1. 一般树 2. 二叉树(是有序树) 2.1 概念 2.2 分类 1. 一般二叉 ...

  2. 数据结构——非线性结构(图)

    文章目录 一. 非线性结构的概述 二. 图的基本概念 1. 定义 2. 无向图.有向图 2.1 无向图 2.2 有向图 2.3 简单图 2.4 多重图 3. 顶点的度.出度.入度 3.1 对于无向图 ...

  3. 分层次的非线性结构——树(广义表)05

    包含子结构的线性结构,线性表的推广--广义表 广义表的定义 广义表定义 约定:为了区分原子和子表,书写时用大写字母表示子表,用小写字母表示原子. 广义表特性 广义表表示方法 用圆圈和方框分别表示表和单 ...

  4. 数据结构——非线性结构 之 二叉树,详细解析

    要认识二叉树,首先要先了解树,二叉树是树的一种特殊的结构. 目录 一.树 1.1树的概念和结构 1.2树的相关专业名词 1.3树和非树 二.二叉树 2.1二叉树的概念的结构 三.二叉树的功能实现 3. ...

  5. 线性结构和非线性结构简单介绍

    简 数据结构包含:线性结构和非线性结构. 线性结构: 线性结构是十分常用的数据结构,其特点是数据元素之间存在一对一的线性关系.如:arry[6] = 6 线性结构有两种不同的存储结构,分为:顺序存储结 ...

  6. OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算

    数字图像中的每个点都称为像素(对于图像元素),并且每个像素可以存储一个或多个值,这取决于它是否是仅存储一个值的黑白图像(也称为二进制图像,比如只存储0或1),还是存储两个值的灰度图像,或者是存储三个值 ...

  7. 数据结构——非线性结构

    简单地说,非线性结构就是表中各个结点之间具有多个对应关系.如果从数据结构的语言来描述,非线性结构应该包括如下几点: 1.非线性结构是非空集. 2.非线性结构的一个结点可能有多个直接前趋结点和多个直接后 ...

  8. 数据结构快速盘点 - 非线性结构

    PS:为了更好的阅读体验,推荐阅读原文,到我的博客中阅读. 那么有了线性结构,我们为什么还需要非线性结构呢? 答案是为了高效地兼顾静态操作和动态操作.大家可以对照各种数据结构的各种操作的复杂度来直观感 ...

  9. 数据结构与算法——树和二叉树***

    第五章 :树和二叉树 树和图是两种重要的非线性结构.线性结构中结点具有唯一前驱和唯一后继的关系,而非线性结构中结点之间的关系不再具有这种唯一性.其中,树形结构中结点间的关系是前驱唯一而后继不唯一,即元 ...

最新文章

  1. Nginx源码研究三:Epoll在NGINX中的使用
  2. UVa10911 Forming Quiz Teams(dp)
  3. Gerrit plugin安装和删除
  4. linux100day(day5)--编程原理和shell脚本
  5. Android BLE学习(三):编写自己的 BLE蓝牙读写工具(功能仿照nrf master control panel)
  6. jdk9与jdk11哪个好_JDK 9、10和11中的安全性增强
  7. ubuntu linux theme,如何在Ubuntu 20.04中启用全局暗黑主题
  8. 序列化与反序列化的简单认识
  9. Systemd入门教程:命令篇
  10. 使用JAVA爬取网页图片
  11. python的隐藏功能分享_【图片】分享一段功能非常简陋的python代码实现下载free种【pt吧】_百度贴吧...
  12. 【BZOJ2989】数列(CDQ分治,扫描线)
  13. Spring Cloud Alibaba Sentinel之热点参数限流篇
  14. 单片机c语言三角波采样点,单片机课程设计---信号发生器.doc
  15. 篮球计时计分器c语言程序,篮球赛计时计分器程序源代码.doc
  16. oracle 定时任务
  17. 基于机器学习算法的LTE高投诉小区预判方法
  18. 数学常识--标准差、方差、协方差三者的表示意义
  19. 【Android】使用后端云Bmob实现登录、注册
  20. 什么是服务降级和熔断(网络白话摘要)

热门文章

  1. 08-Measured Boot Driver (MBD)
  2. 后台服务显示右下角弹窗 -- system权限创建用户权限进程
  3. 2020-12-3(ESP定律脱壳理解)
  4. 2019信安国赛逆向easyGo,bbvvmm题解
  5. 【prometheus API】删除指定指标数据
  6. 5、删除存储过程(DROP PROCEDURE)
  7. 8、MySQL转义字符的使用
  8. 2016年蓝桥杯省赛题解
  9. 2021算法竞赛入门班第七节课【图论】练习题
  10. Linux的yum指令