数据结构与算法——二叉树(四) 学习笔记

二、遍历

2.4 迭代后序遍历

解决办法:

  1. 找到第一个被访问的节点
  2. 将沿途各节点的祖先及右兄弟(若存在)用栈保存

从根出发下行,尽可能沿左分支,实不得已,才沿右分支。最后一个节点必是叶子,也是递归版中visit()首次执行处这叶子将首先接受访问

第一个被访问的节点
从左侧水平向右看去,未被遮挡的最高叶节点v——称作最高左侧可见叶节点(HLVFL)——即为后序遍历首先访问的节点。请注意,该节点既可能是左孩子,也可能是右孩子,

图摘自清华大学《数据结构(C++语言版)》

在这里插入图片描述

后序遍历的过程也可分为模式雷同的若干段:

  1. 访问当前节点
  2. 遍历以其右兄弟(若存在)为根的子树
  3. 向上回溯至其父节点(若存在)并转入下一片段。

代码摘自清华大学《数据结构(C++语言版)》

template <typename T> //在以S栈顶节点为根的子树中,找到最高左侧可见叶节点
static void gotoLeftmostLeaf(Stack<BinNodePosi(T)>& S) { //沿途所遇节点依次入栈
	while (BinNodePosi(T) x = S.top()) //自顶而下,反复检查当前节点(即栈顶)
		if (HasLChild(*x)) //尽可能向左
		{
			if (HasRChild(*x))
				S.push(x->rc); //若有右孩子,优先入栈
			S.push(x->lc); //然后才转至左孩子
		}
		else //实不得已
			S.push(x->rc); //才向右
	S.pop(); //返回之前,弹出栈顶的空节点
}

template <typename T, typename VST>
void travPost_I(BinNodePosi(T) x, VST& visit) { //二叉树的后序遍历(迭代版)
	Stack<BinNodePosi(T)> S; //辅助栈
	if (x) S.push(x); //根节点入栈
	while (!S.empty()) //x始终为当前节点
	{
		if (S.top() != x->parent) 若栈顶非x之父(而为右兄)
			gotoLeftmostLeaf(S); //则在其右兄子树中找到HLVFL(相当于递归深入)
		x = S.pop(); visit(x->data); //弹出栈顶(即前一节点之后继),并访问之
	}
}

辅助函数:gotoLeftmostLeaf(),自顶而下反复检查栈顶节点,尽可能地向左深入,若有右孩子,则右孩子优先入栈,左孩子后入栈(这两个孩子就是兄弟关系,原节点为二者的父节点),然后转向左孩子;实不得已(没有左孩子),才转向右孩子。由于这里的循环迭代以栈顶为空结束,所以辅助函数返回之前,要弹出栈顶的空节点

主算法:建立辅助栈。根节点首先入栈(注意前面两种算法没有此步骤)。若栈顶节点并非当前访问节点之父(而为右兄,因为从入栈的过程来看,只有两种可能),则调用gotoLeftmostLeaf()在其右兄子树中,找到最靠左的叶子HLVFL(相当于递归深入其中)。然后弹出当前栈顶(即前一节点的后继),并访问之。

算法终止条件:辅助栈为空(注意后序遍历的算法终止条件是在while的入口条件中,而不像前面两种遍历是在调用完辅助函数后判断栈是否为空,为空再break,而while(true))

每个节点出栈后,以之为根的子树已经完全遍历,且其右兄弟r若存在,则必在栈顶(因为栈顶只可能是其兄或其父)。此时正可以从r出发开始遍历子树r

2.5 层次遍历

2.5.1 算法实现

此前的三种遍历,都不能保证所有的节点严格地按照深度次序接受访问,即存在逆序,需要借助栈Stack。
在层次遍历中,所有节点都严格按照深度次序,由高至低地接受访问,同样深度的节点由左至右访问。由于严格按照次序,因此借助队列Queue(先入先出FIFO)

图截取自清华大学数据结构慕课

在这里插入图片描述
在这里插入图片描述

  1. 引入辅助队列
  2. 根节点入队
  3. 每次取出队首节点,并访问
  4. 若有左孩子,则左孩子入队
  5. 若有右孩子,则右孩子入队
  6. 反复循环3~5步

算法终止条件:队列变空
注意:此处4、5步的顺序是先左孩子入队,再右孩子入队,是顺序的,而不像先序遍历的版本1那样是逆序的(因为层次遍历用队列,FIFO,先序遍历用栈,LIFO)

出于栈的这种特性,任意时刻队列中的节点满足深度相差不超过1

代码摘自清华大学《数据结构(C++语言版)》

/*DSA*/#include "queue/queue.h" //引入队列
template <typename T> template <typename VST> //元素类型、操作器
void BinNode<T>::travLevel(VST& visit) //二叉树层次遍历算法
{
	Queue<BinNodePosi(T)> Q; //辅助队列
	Q.enqueue(this); //根节点入队
	while (!Q.empty()) //在队列再次变空之前,反复迭代
	{
		BinNodePosi(T) x = Q.dequeue(); visit(x->data); //取出队首节点并访问之
		if (HasLChild(*x)) Q.enqueue(x->lc); //左孩子入队
		if (HasRChild(*x)) Q.enqueue(x->rc); //右孩子入队
	}
}

每次迭代,都恰好有一个节点出队并接受访问,同时有不超过两个节点入队。每个节点入、出队恰好各一次,故整体只需O(n)时间

2.5.2 完全二叉树

若在对某棵二叉树的层次遍历过程中,前n/2次迭代中都有左孩子入队,且前n/2 - 1次迭代中都有右孩子入队,则称之为完全二叉树(complete binary tree)。
完全二叉树的结构特征:叶节点只能出现在最底部的两层,且最底层叶节点均处于次底层叶节点的左侧。

图摘自清华大学《数据结构(C++语言版)》

在这里插入图片描述

由图,高度为h的完全二叉树,规模n应该介于 2 h 2^h 2h 2 h + 1 − 1 2^{h+1}-1 2h+11之间;反之,规模为n的完全二叉树, h = O ( l o g n ) h=O(logn) h=O(logn)

高度为h的完全二叉树节点最多的情况即满二叉树,最少的情况比高度为h-1的满二叉树多1。

2.5.3 满二叉树

每一层的节点数都应达到饱和,故将称其为满二叉树(full binary tree)。满二叉树所有叶节点同处于最底层(非底层节点均为内部节点)。
高度为h的满二叉树由 2 h + 1 − 1 2^{h+1}-1 2h+11个节点组成,其中叶节点总是恰好比内部节点多出一个(叶节点树 2 h 2^h 2h,内部节点数 2 h − 1 2^h-1 2h1

图摘自清华大学《数据结构(C++语言版)》

在这里插入图片描述

2.6 重构

已知某棵数的遍历序列,还原出该树的拓扑结构
情况1:[ 先序 | 后序 ] + 中序
情况2:[ 先序 | 后序 ] + 真(真二叉树)

1、并查集中的树最适合用什么方法表示:
父节点法能够高效定位父亲而不能高效地定位孩子,而并查集中的树只需要能够定位父节点。

2、每上溯一层,深度减小1,但高度的增加可能大于1,因为节点的高度由其左、右子树中较高者决定。

3、借助队列对二叉树进行层次遍历时,任意时刻队列中的节点满足:深度相差不超过1

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值