【程序人生】数据结构杂记(四)

说在前面

个人读书笔记

二叉树

树属于半线性结构——附加某种约束(比如遍历),也可以在树中的元素之间确定某种线性次序
树是一种分层结构

T T T中所有节点深度的最大值称作该树的高度 ( h e i g h t ) (height) (height),记作 h e i g h t ( T ) height(T) height(T)

不难理解,树的高度总是由其中某一叶节点的深度确定的。特别地,仅含单个节点的树高度为 0 0 0,空树高度为 − 1 -1 1
推而广之,任一节点 v v v所对应子树 s u b t r e e ( v ) subtree(v) subtree(v)的高度,亦称作该节点的高度,记作 h e i g h t ( v ) height(v) height(v)。特别地,全树的高度亦即其根节点 r r r的高度, h e i g h t ( T ) = h e i g h t ( r ) height(T) = height(r) height(T)=height(r)

二叉树的实现

作为图的特殊形式,二叉树的基本组成单元是节点与边;作为数据结构,其基本的组成实体是二叉树节点(binary tree node),而边则对应于节点之间的相互引用。

遍历

二叉树本身并不具有天然的全局次序,故为实现遍历,需通过在各节点与其孩子之间约定某种局部次序,间接地定义某种全局次序。
按惯例左兄弟优先于右兄弟,故若将节点及其孩子分别记作V、L和R,则如下图所示,局部访问的次序可有VLR、LVR和LRV三种选择。根据节点V在其中的访问次序,三种策略也相应地分别称作先序遍历、中序遍历和后序遍历
在这里插入图片描述

先序遍历

在这里插入图片描述为遍历(子)树 x x x,首先核对 x x x是否为空。若 x x x为空,则直接退出——其效果相当于递归基。
反之,若 x x x非空,则按照先序遍历关于局部次序的定义,优先访问其根节点 x x x;然后,依次深入左子树和右子树,递归地进行遍历。
在这里插入图片描述

迭代实现先序遍历

在这里插入图片描述
在二叉树 T T T中,从根节点出发沿着左分支一直下行的那条通路(以粗线示意),称作最左侧通路(leftmost path)。若将沿途节点分别记作 L k L_k Lk k = 0 , 1 , 2 , . . . , d k = 0, 1, 2, ..., d k=0,1,2,...,d,则最左侧通路终止于没有左孩子末端节点 L d L_d Ld。若这些节点的右孩子和右子树分别记作 R k R_k Rk T k T_k Tk k = 0 , 1 , 2 , . . . , d k = 0, 1, 2, ..., d k=0,1,2,...,d,则该二叉树的先序遍历序列可表示为:
在这里插入图片描述
也就是说,先序遍历序列可分解为两段:
沿最左侧通路自顶而下访问的各节点,以及自底而上遍历的对应右子树。
基于对先序遍历序列的这一理解,可以导出以下迭代式先序遍历算法。
在这里插入图片描述在这里插入图片描述在全树以及其中每一棵子树的根节点处,该算法都首先调用函数VisitAlongLeftBranch(),自顶而下访问最左侧通路沿途的各个节点。这里也使用了一个辅助栈,逆序记录最左侧通路上的节点,以便确定其对应右子树自底而上的遍历次序。

中序遍历

在这里插入图片描述各节点在中序遍历序列中的局部次序,与按照有序树定义所确定的全局左、右次序完全吻合

迭代版中序遍历

在这里插入图片描述参照迭代式先序遍历的思路,再次考查二叉树 T T T的最左侧通路,并对其中的节点和子树标记命名。于是, T T T的中序遍历序列可表示为:
在这里插入图片描述
在这里插入图片描述
在全树及其中每一棵子树的根节点处,该算法首先调用函数goAlongLeftBranch(),沿最左侧通路自顶而下抵达末端节点 L d L_d Ld 。在此过程中,利用辅助栈逆序地记录和保存沿途经过的各个节点,以便确定自底而上各段遍历子序列最终在宏观上的拼接次序。
在这里插入图片描述

中序遍历序列直接后继及其定位

这里,共分两大类情况。
若当前节点有右孩子,则其直接后继必然存在,且属于其右子树。此时只需转入右子树,再沿该子树的最左侧通路朝左下方深入,直到抵达子树中最靠左(最小)的节点。
反之,若当前节点没有右子树,则若其直接后继存在,必为该节点的某一祖先,且是将当前节点纳入其左子树的最低祖先。于是首先沿右侧通路朝左上方上升,当不能继续前进时,再朝右上方移动一步即可。
作为后一情况的特例,出口时s可能为NULL。这意味着此前沿着右侧通路向上的回溯,抵达了树根。也就是说,当前节点全树右侧通路的终点——它也是中序遍历的终点,没有后继。
在这里插入图片描述

迭代版中序遍历(无需辅助栈)

在这里插入图片描述可见,这里相当于将原辅助栈替换为一个标志位backtrack。每当抵达一个节点,借助该标志即可判断此前是否刚做过一次自下而上的回溯。若不是,则按照中序遍历的策略优先遍历左子树。反之,若刚发生过一次回溯,则意味着当前节点的左子树已经遍历完毕(或等效地,左子树为空),于是便可访问当前节点,然后再深入其右子树继续遍历。
每个节点被访问之后,都应转向其在遍历序列中的直接后继。按照以上的分析,通过检查右子树是否为空,即可在两种情况间做出判断:
该后继要么在当前节点的右子树(若该子树非空)中,要么(当右子树为空时)是其某一祖先。后一情况即所谓的回溯。请注意,由succ()返回的直接后继可能是NULL,此时意味着已经遍历至中序遍历意义下的末节点,于是遍历即告完成。

后序遍历

在这里插入图片描述
先序遍历序列与后序遍历序列并非简单的逆序关系

层次遍历

在所谓广度优先遍历或层次遍历(level-order traversal)中,确定节点访问次序的原则可概括为“先上后下、先左后右”——先访问树根,再依次是左孩子、右孩子、左孩子的左孩子、左孩子的右孩子、右孩子的左孩子、右孩子的右孩子、…,依此类推。

当然,有根性和有序性是层次遍历序列得以明确定义的基础。正因为确定了树根,各节点方可拥有深度这一指标,并进而依此排序;有序性则保证孩子有左、右之别,并依此确定同深度节点之间的次序。
在这里插入图片描述此前介绍的迭代式遍历,无论先序、中序还是后序遍历,大多使用了辅助栈,而迭代式层次遍历则需要使用与栈对称的队列结构
在这里插入图片描述
示例:
在这里插入图片描述

完全二叉树

叶节点只能出现在最底部的两层,且最底层叶节点均处于次底层叶节点的左侧

满二叉树

所有叶节点同处于最底层

结语

如果您有修改意见或问题,欢迎留言或者通过邮箱和我联系。
手打很辛苦,如果我的文章对您有帮助,转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值