前面两篇博客介绍了线性表的顺序存储与链式存储以及对应的操作,并且还聊了栈与队列的相关内容。本篇博客我们就继续聊数据结构的相关东西,并且所涉及的相关Demo依然使用面向对象语言Swift来表示。本篇博客我们就来介绍树结构的一种:二叉树。在之前的博客中我们简单的聊了一点树的东西,树结构的特点是除头节点以外的节点只有一个前驱,但是可以有一个或者多个后继。而二叉树的特点是除头结点外的其他节点只有一个前驱,节点的后继不能超过2个。

本篇博客,我们只对二叉树进行讨论。在本篇博客中,我们对二叉树进行创建,然后进行各种遍历,最后将二叉树进行线索化。在Demo实现之前,我们先对二叉树的概念及其特性进行介绍,然后在给出具体的代码实现。

一、二叉树的特性

上面我们已经提到过,一个除头结点外,每个节点只有一个前驱,有零到两个后继的树即为二叉树。在二叉树中,一个节点可以有左节点或者左子树,也可以有右节点或者右子树。一些特殊的二叉树,比如斜二叉树、满二叉树、完全二叉树等等就不做过多赘述了。说这么多,不如看一张图来的直观。下方就是一个典型的二叉树。

  

了解二叉树,理解其特性还是比较重要的。基于二叉树本身的逻辑结构,下方是二叉树这种数据结构所具备的特性。

  • 特性1:在二叉树的第i层上至多有2^(i-1)(i >= 1)个节点。

    • 这一特性比较好理解,如果层数是从零开始数的话,那么低i层上的节点数就是2^i,因为二叉树层与层之间的节点数是以2的指数幂进行增长的。如果根节点算是第0层的话,那么第n层的节点数就是2^n次幂。
  • 特性2:深度为k的二叉树至多有2^k-1(k>=1)个节点。

    • 这一特性也是比较好理解的, 由数学上的递加公式就可以很容易的推出来。由特性1易知每层最多有多少个节点,那么深度为k的话,说明一共有k层,那么共有节点数为:2^0 + 2^1 + 2^2 + 2^(k-1) = 2^k - 1。
  • 特性3:二叉树的叶子节点数为n0, 度为2的节点数为n2, 那么n0 = n2 + 1。

    • 这一特性也不难理解,推出n0 = n2 + 1这个公式并不难。我们假设叶子节点,也就是度数为0的节点的个数为n0, 度数为1的节点为n1, 度数为2的节点n2。那么二叉树的节点总数 n = n0 + n1 + n2。因为除了根节点外其余的节点入度都为1,所以二叉树的度数为n-1,当然度的个数可以使用出度来算,即为2*n2+n1,所以n-1=2*n2+n1。以n=n0+n1+n2与n-1=2*n2+n1这两个公式我们很容易的推出n0 = n2 + 1。
  • 特性4:具有n个结点的完全二叉树的深度为log2n + 1 (向下取整,比如3.5,就取3)。

    • 这个特性也是比较好理解的,基于完全二叉树的特点,我们假设完全二叉树的深度为k, 那么二叉树的结点个数的范围为2(k-1)-1 <= n <= 2k-1。由这个表达式我们很容易推出特性4。

 

二、二叉树的创建

上面介绍完二叉树的特性后,接下来我们要做的就是将二叉树进行存储。当然一般存储二叉树的结构是以二叉链表的形式来存储的。二叉链表的结构类似于双向链表,二叉链表的节点也是有两个结点指针的,一个指向左子树,一个指向右子树。接下来我们要使用二叉链表的形式来存储我们的二叉树。

1.先序创建二叉树

在创建二叉树之前,我们先了解一个什么是先序遍历。先序遍历就是先遍历根结点,然后遍历左子树,最后遍历右子树。我们就以此规则来创建二叉树,换句话说,我们有一个数据序列,将依照这个序列按照先序创建二叉树的原则来创建该二叉树,先创建二叉树的根节点,然后再创建二叉树的左子树,然后再创建右子树。而这个创建的二叉树的先序遍历的结果就是我们之前输入的数据序列。下方就是先序创建二叉树的原理图。

  

从上面的分析我们不难看出,我们要先创建根节点,然后创建左子树,最后创建右子树。因为左子树和右子树都是二叉树,所以创建左子树和右子树是原问题的子问题。也就是说子问题与原问题解决方案一致,这种情况下就可以使用递归的思想来解决。我们先将上述二叉树的结构转换成二叉链表的形式直观的感受一下,然后再将其使用代码的形式进行表示即可。下方这个截图就是上述二叉树的二叉链表的存储结构。每个节点都有左指针与右指针,分别自己的左子节点和右子节点。如果没有子节点就为空。

  

2.先序创建二叉树的代码实现

上面我们分析了二叉链表的结构,接下来我们就来创建二叉链表了。首先我们得创建二叉链表的节点类,之前我们用C语言来实现二叉树的时候,是使用的结构体来实现的二叉链表的节点,因为C语言是面向过程的语言,根本就没有类这个概念。因为此刻我们是使用的面向对象语言,所以我就可以使用一个类来表示我们二叉链表的节点了。下方这个GeneralBinaryTreeNote就是二叉链表的类。data属性存储的就是树节点中所存储的值,而leftChild就指向左节点的内存地址,而rightChild就指向右节点的内存地址。

  

上面我们已经说过,先序创建二叉树的过程是可以用递归来表示的,所以我们就递归的去创建我们想要创建的二叉树。下方就是先序创建二叉树的核心代码,self.items中存储的是二叉树的节点信息。经过下方函数的递归执行,就可以创建出我们想要的二叉树了。从下方的递归过程我们就明显的能看出是先序创建的二叉树。先创建的根节点,然后递归创建左子树,然后在递归创建右子树。

  

下方就是我们二叉树的初始化过程,下方在初始化过程中主要是调用上方的这个方法,将items数组中存储的值转换成二叉链表的存储结构。items数组中的空字符串,表明该节点为空。

  

其实上面实例中所创建的二叉树的结构就是下方的结构。

  

三、二叉树的遍历

聊二叉树怎么能没有二叉树的遍历呢,下方就会给出几种常见的二叉树的遍历方法。在遍历二叉树的方法中一般有先序遍历,中序遍历,后续遍历,层次遍历。本篇博客主要给出前三种遍历方式,而层次遍历会在图的部分进行介绍。二叉树的层次遍历其实与图的广度搜索是一样的,所以这部分放到图的相关博客中介绍。下方会给出几种遍历的具体方式,然后给出具体的代码实现。

二叉树的先、中、后遍历,这个先中后指的是遍历根节点的先后顺序。先序遍历:根左右,中序遍历:左根右,后序遍历:左右根。下方将详细介绍到。

1.先序遍历

关于先序遍历,上面已经介绍过一些了,接下来再进行细化一下。先序遍历,就是先遍历根节点然后再遍历左子树,最后遍历右子树。下图就是我们上面创建的二叉树的先序遍历的顺序,由下方的示例图就可以看出先序遍历的规则。一句话总结下方的结构图:根节点->左节点->右节点。下方先序遍历的顺序为:A B D 空 空 E 空 空 C 空 F 空 空 。

  

上面给出了原理,接下来又到了代码实现的时候了。在树的遍历时,我们依然是采用递归的方式,因为无论是左子树还是右子树,都是二叉树的范畴。所以在进行二叉树遍历时,可以使用递归遍历的形式。而先序遍历莫非就是先遍历根节点,然后递归遍历左子树,最后遍历右子树。下方就是先序遍历的代码实现。在下方代码中,如果左节点或者右节点为空,那么我们就输出“空”。

  

2.中序遍历

中序遍历,与先序遍历的不同之处在于,中序遍历是先遍历左子树,然后遍历根节点,最后遍历右子树。一句话总结:左子树->根节点->右子树。下方就是我们之前创建的树的中序遍历的结构图以及中序遍历的结果。

  

中序遍历的代码实现与先序遍历的代码实现类似,都是使用递归的方式来实现的,只不过是先递归遍历左子树,然后遍历根节点,最后遍历右子树。下方就是中序遍历的代码具体实现。

  

3.后序遍历

接下来聊一下二叉树的后序遍历。如果上面这两种遍历方式理解的话,那么后序遍历也是比较好理解的。后序遍历是先遍历左子树,然后再遍历右子树,最后遍历根节点。与上方的表示方法一直,首先我们给出表示图,如下所示:

  

后序遍历的代码就不做过多赘述了,与之前两种依然类似,只是换了一下遍历的顺序。下方就是二叉树后序遍历的代码实现。

  

4、层次遍历

二叉树的层次遍历就不是二叉树这种数据结构所独有的了。后面的博客中我们会介绍到图这种数据结构,在图中有一个广度搜索,放到二叉树中就是层次遍历。也就是说二叉树的层次遍历,就是图中以二叉树的根节点为起始节点的广度搜索(BFS)。本篇博客就不给出具体的代码了,后面的博客会给出BFS的具体算法。当然在之前的博客中有图的BFS以及DFS。不过是C语言的实现。下方就是二叉树层次遍历的实例图。

    

四、二叉树的线索化

二叉树的线索化,起始就是利用二叉树中的空的节点来将二叉树转换成链表的结构。当然只针对中序遍历的序列。从上面中序遍历的结果中,我们不难看出,有节点的值与空指针是间隔的(空 D 空 B 空 E 空 A 空 C 空 F 空)。也就是说好多空的左指针与右指针浪费了。二叉树的线索化,就是在中序遍历中,将空的左子树的指针指向其中序遍历结果的前驱,而空的右子树指针指向中序遍历中该节点的后继。具体的示意图如下所示:

  

从上面的图中我们不难看出。在被线索化的二叉树中,左节点指针不止指向左节点,而且有可能指向节点的前驱。而右节点指针不仅仅是指向右节点的指针,还有可能指向该节点在中序遍历中的后继节点。为了标记指针是指向子节点还是指向前驱或者后继,所以我们要添加相应的标志位来标记指针指向的是那些节点。下方就是我们改造后的二叉树的节点:

  

改造完节点后,我们就可以将二叉树进行线索化了,下方就是被线索话的二叉树的代码。可以看出,下方的代码的整体步骤与二叉树的中序遍历类似。

  

被线索化的二叉树就可以根据我们添加的线索进行中序遍历了,效率要比递归的中序遍历要高的多,如下所示:

  

五、测试用例

上面的代码都是如何去实现了,接下来到了我们测试的时间了,下方这段代码段是我们的测试用例。首先给出二叉树的节点信息,然后先序的创建一棵二叉树。然后给出二叉树的先、中、后续遍历,最后给出二叉树线索话的结果。

  

下方截图就是我们测试用例的运行结果,一目了然,在此就不做过多的赘述了。

  

本篇博客的篇幅也够长的了,就先到这儿吧,上述实例的完整Demo会在github上进行分享, 下篇博客我们将要介绍图的邻接链表和邻接矩阵,以及图的BFS和DFS。

github链接地址:https://github.com/lizelu/DataStruct-Swift/tree/master/BinaryTree

算法与数据结构(三) 二叉树的遍历及其线索化(Swift版)的更多相关文章

  1. 数据结构与算法之PHP实现二叉树的遍历

    一.二叉树的遍历 以某种特定顺序访问树中所有的节点称为树的遍历,遍历二叉树可分深度优先遍历和广度优先遍历. 深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.可以细分 ...

  2. 算法与数据结构基础 - 二叉树(Binary Tree)

    二叉树基础 满足这样性质的树称为二叉树:空树或节点最多有两个子树,称为左子树.右子树, 左右子树节点同样最多有两个子树. 二叉树是递归定义的,因而常用递归/DFS的思想处理二叉树相关问题,例如Leet ...

  3. 【PHP数据结构】二叉树的遍历及逻辑操作

    上篇文章我们讲了许多理论方面的知识,虽说很枯燥,但那些都是我们今天学习的前提,一会看代码的时候你就会发现这些理论知识是多么地重要了.首先,我们还是要说明一下,我们学习的主要内容是二叉树,因为二叉树是最 ...

  4. 【数据结构】二叉树的遍历(前、中、后序及层次遍历)及leetcode107题python实现

    文章目录 二叉树及遍历 二叉树概念 二叉树的遍历及python实现 二叉树的遍历 python实现 leetcode107题python实现 题目描述 python实现 二叉树及遍历 二叉树概念 二叉 ...

  5. python数据结构之二叉树的遍历实例

    遍历方案   从二叉树的递归定义可知,一棵非空的二叉树由根结点及左.右子树这三个基本部分组成.因此,在任一给定结点上,可以按某种次序执行三个操作:   1).访问结点本身(N)   2).遍历该结点的 ...

  6. c_数据结构_二叉树的遍历实现

    #include<stdio.h> #include<stdlib.h> #define OK 1 #define ERROR 0 #define OVERFLOW -2 #d ...

  7. 算法与数据结构(十) 二叉排序树的查找、插入与删除(Swift版)

    在上一篇博客中,我们主要介绍了四种查找的方法,包括顺序查找.折半查找.插入查找以及Fibonacci查找.上面这几种查找方式都是基于线性表的查找方式,今天博客中我们来介绍一下基于二叉树结构的查找,也就 ...

  8. 【数据结构&算法】12-线索二叉树

    目录 前言 线索二叉树的概念 线索二叉树的实现 线索二叉树的寻点思路二 类双向链表参考图 参考代码 中序遍历线索化 前言 在<大话数据结构>P190 页中有一句话:其实线索二叉树,就等于是 ...

  9. 算法与数据结构基础 - 深度优先搜索(DFS)

    DFS基础 深度优先搜索(Depth First Search)是一种搜索思路,相比广度优先搜索(BFS),DFS对每一个分枝路径深入到不能再深入为止,其应用于树/图的遍历.嵌套关系处理.回溯等,可以 ...

随机推荐

  1. W3CSchool闯关笔记(Bootstrap)

    该闯关内容与JS闯关衔接. 每一题的答案均在注释处, 第一关:把所有的HTML内容放在一个包含有container-fluid的class名称的div下(注意,是所有的HTML内容,style标签属于 ...

  2. 在django中uwsgi的使用,以及安装

    首先了解wsgi是一个python web服务器,uwsgi实现了wsgi所有的功能,性能稳定,效率高的服务器: 1.安装uwsgi pip install uwsgi 2.配置uwsgi [uwsg ...

  3. js中valueOf方法的使用

    今天一位刚毕业的同事问了我一个问题,为什么这段代码执行结果是-1.代码如下: var o = { valueOf: function(){ return -1; } }; o = +o; 当时我也是懵 ...

  4. Mysql exists 与 in

    今天公司同事反馈一个SQL语句删除数据删除了一个小时,还没有删除完,强制中断. 第一眼看到 exists 的时候,脑子里要有这么个概念: Oracle exists 的效率比in 高.而Mysql 则 ...

  5. 饮冰三年-人工智能-Python-26 Django 学生管理系统

    背景:创建一个简单的学生管理系统,熟悉增删改查操作 一:创建一个Django项目(http://www.cnblogs.com/wupeiqi/articles/6216618.html) 1:创建实 ...

  6. [FJWC2018]全排列

    题解: 考虑长度为k的时候的贡献 即取出一些元素然后给他们排个顺序然后问你有多少排法 假设排法为ans 那么应该就是$C(n,k)*C(n,k)*(n-k)!*(n-k)!*(n-k+1)*ans$ ...

  7. with原理__enter__、__exit__

    Python对with的处理还很聪明.基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法. 紧跟with后面的语句被求值后,返回对象的__enter__( ...

  8. Linux的vim编辑器

    vim编辑器常用的三种模式 vim /文件路径 进入编辑器,编辑该文件 例:vim /etc/passwd 命令行模式:在编辑器中直接输入命令 dd:删除光标所在的一行 ndd:删除光标所在的向下的n ...

  9. 2018-2019-2 网络对抗技术 20165319 Exp3 免杀原理与实践

    免杀原理及基础问题回答 免杀原理: 免杀指的是一种能使病毒木马免于被杀毒软件查杀的技术.由于免杀技术的涉猎面非常广,其中包含反汇编.逆向工程.系统漏洞等黑客技术,所以难度很高,一般人不会或没能力接触这 ...

  10. Linux学习之文件系统权限及表示

    三类人 用户主(user:u):文件的所有者 同组人(group:g):与文件主同组的用户 其他人(other:o):除用户主和同组人外的其他所有人 三种权限 读权限(r):指用户对文件或目录的读许可 ...