算法与数据结构(三) 二叉树的遍历及其线索化(Swift版)

前面两篇博客介绍了线性表的顺序存储与链式存储以及对应的操作,并且还聊了栈与队列的相关内容。本篇博客我们就继续聊数据结构的相关东西,并且所涉及的相关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

 

转载于:https://www.cnblogs.com/ludashi/p/5976682.html

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

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

相关文章

关于android开发时,发生Error infalting classa com.baidu.mapapi.map.MapView的解决办法

1.问题描述&#xff1a;百度地图SDK中 Error&#xff1a; infalting classa com.baidu.mapapi.map.MapView 。 2.解决办法&#xff1a;通过1个多小时的上网搜索&#xff0c;最终发现很多网友之所以出现这方面的问题有以下几种原因&#xff1a; &#xff08;1&#xff09;.忘…

c++动态绑定的技术实现

1 什么是动态绑定 有一个基类&#xff0c;两个派生类&#xff0c;基类有一个virtual函数&#xff0c;两个派生类都覆盖了这个虚函数。现在有一个基类的指针或者引用&#xff0c;当该基类指针或者引用指向不同的派生类对象时&#xff0c;调用该虚函数&#xff0c;那么最终调用的…

linux替换某个文件夹下所有文件,Linux 批量查找并替换文件夹下所有文件的内容...

1.批量查找某个目下文件的包含的内容cd etcgrep -rn "查找的内容" ./2.批量替换某个目下所有包含的文件的内容cd etcsed -i "s/查找的内容/替换后的内容/g" grep -rl "查找的内容" ./3.批量查找并替换任意文件夹下的文件内容。sed -i "s/要…

Day09-递归

#模拟栈结构 stack [] #压栈&#xff08;想栈里存数据&#xff09; stack.append("A") print(stack) stack.append("B") print(stack) stack.append("C") print(stack)#出栈&#xff08;在栈里取数据&#xff09; res stack.pop() print("…

java中String相等问题

判断两个字符串是否相等的问题。在编程中&#xff0c;通常比较两个字符串是否相同的表达式是“”,但在java中不能这么写。在java中&#xff0c;用的是equals(); 例&#xff1a;A字符串和B和字符串比较: if(A.equals(B)){ } 返回true 或false. String 的equals 方法用于比较两个…

linux proc文件 write的原子性,linux - Linux中writev()系统调用的原子性 - 堆栈内存溢出...

在fs.h找到它&#xff1a;static inline void file_start_write(struct file *file){if (!S_ISREG(file_inode(file)->i_mode))return;__sb_start_write(file_inode(file)->i_sb, SB_FREEZE_WRITE, true);}然后在super.c&#xff1a;/** This is an internal function, p…

关于对发送HTTP请求以及解析服务器返回的数据操作的提取到一个公共类中进行封装

创建一个名为HttpUtil的类并提供名为sendHttpRequest静态方法.相关代码如下&#xff1a; package com.hzy.networktest;import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.URL;p…

初始化CSS

不同的浏览器默认样式不一样,所以容易出现兼容性问题,每次写网页时都应该都网页的css或HTML标签进行初始化 这样可以节约代码,节约网页下载时间,是网页内容更加简洁, 大致需要初始化的地方有 H1-H4标签,table标签,文字大小,文字没有链接,超链接样式,DIV,居中,ol,ul,li,img等等的…

Day10-时间

UTC(世界协调时间)&#xff1a;格林尼织天文时间 在中国来说是UTC8 DST&#xff08;夏令时&#xff09;&#xff1a;是一种节约能源而人为规定时间制度&#xff0c;在夏季调快一个小时时间的表示形式&#xff1a; 1、时间戳 以整形或浮点型表示时间的一个以秒为单位的时间间隔 …

WebForm 分页与组合查询

1.封装实体类 2.写查询方法 //SubjectData类 public List<Subject> Select(string name){List<Subject> list new List<Subject>();cmd.CommandText "select *from Subject where SubjectName like a ";cmd.Parameters.Clear();cmd.Parameters.A…

linux如何输出当前时间,如何在linux下输出当前时间

用localtime可直接分解出年月日时分秒QUOTE:struct tm *ptm;long ts;int y,m,d,h,n,s;ts time(NULL);ptm localtime(&ts);y ptm->tm_year1900; //年m ptm->tm_mon1; //月d ptm->tm_mday; //日h ptm->tm_hour; //时n ptm->tm_min; //分s ptm->tm_…

node.js简单爬虫

这里假设你已经安装好node.js和npm&#xff0c;如果没有安装&#xff0c;请参阅其他教程安装。 配置首先是来配置package.json文件&#xff0c;这里使用express,request和cheerio。package.json如下&#xff1a; {"name": "node-scrape","version&quo…

Day11-递归性能测试

import time time.clock() sum 0 for i in range (1000000000):sumi print(time.clock()) 慎用 慎用 慎用

关于在新建的package中用SetContentView()函数时无法找到已创建的R.layout的布局文件的的问题的解决办法

问题描述如下&#xff1a; 解决途径&#xff1a;是在导入包的过程中&#xff0c;错误的将系统自动将Android.R这个包导入最终导致用setContenView()加载布局时只能显示系统自带的布局&#xff0c;无法显示自己已经创建的布局。只需将相应活动中导入的Android.R包删除&#xff0…

Struts2入门(二)——配置拦截器

一、前言 之前便了解过&#xff0c;Struts 2的核心控制器是一个Filter过滤器&#xff0c;负责拦截所有的用户请求&#xff0c;当用户请求发送过来时&#xff0c;会去检测struts.xml是否存在这个action&#xff0c;如果存在&#xff0c;服务器便会自动帮我们跳转到指定的处理类中…

linux固态机械分区吗,不再疑惑!实测数据后才知道固态硬盘究竟要不要分区

不再疑惑&#xff01;实测数据后才知道固态硬盘究竟要不要分区2019-12-10 20:52:00162点赞594收藏177评论前几年的固态硬盘价格昂贵&#xff0c;一般用户会选择128G或256G的固态作为系统盘&#xff0c;由于单盘空间不大&#xff0c;一般都会配合机械硬盘使用&#xff0c;无需考…

关于无法加载已创建的布局文件的问题的解决方案以及已布局在对应的R文件中未生成相应ID的问题的解决

先来说下创建后的Layout布局文件在对应的R文件中不能生成相应的ID问题&#xff0c;一般情况下之所以出现这种问题是应为自己的res文件中有错误的文件&#xff1a;对应的是错误的文件格式名称&#xff0c;以及错误的文件内容等。博主就遇到过为drawable文件起了一个非法的名称&a…

安卓手机的后门控制工具SPADE

SPADE&#xff0c;一款安卓手机的后门控制工具&#xff0c;安全研究人员可以以此了解和研究安卓后门原理。 首先&#xff0c;我们从网站www.apk4fun.com下载apk文件&#xff0c;如ccleaner。然后&#xff0c;我们安装spade git clone https://github.com/suraj-root/spade.git …

Day12-date time

import datetimedatetime比time高级了不少&#xff0c;可以理解为datetime基于time进行了封装&#xff0c;提供了&#xff0c; 更为实用的函数&#xff0c;并且datetime模块的接口更直观更容易调用模块中的类&#xff1a; datetime 同时又时间和日期 imedelta 主…