【数据结构】5.5 遍历二叉树和线索二叉树

news2024/11/24 17:21:19

5.5.1 遍历二叉树

遍历定义

  • 顺着某一条搜索路径巡访二叉树中的每个结点,使得每个结点均被访问依次,而且仅被访问一次(又称周游)。
  • 访问的含义很广,可以是对结点作各种处理,如:输出结点的信息,修改结点的数据值等,但是要求这种访问不能破坏原来的数据结构。
    • 如:数组的遍历就是从第一个元素一直访问到最后一个元素,此处输出数组内的每个值。
int a[5]={1,2,3,4,5};
int i;
for(i = 0;i < 5;i++ )
{
		printf("%d ",a[i]);
		//依次访问数组中的每个元素,就叫遍历。
}
return 0;
1 2 3 4 5

遍历目的

在这里插入图片描述

  • 得到树中所有结点的一个线性排列
  • 这样一棵二叉树的每个结点是有分支的,访问完一个结点之后,再去访问下一个,下一个结点又是哪一个呢,此时就需要确定一个顺序,要把树这样一个非线性结构变成一个线性排列。

遍历用途

  • 它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。所以,二叉树的遍历是本章的重中之重

遍历二叉树算法描述

在这里插入图片描述

遍历方法

  • 回顾二叉树的递归定义可知,二叉树是由 3 个基本单位构成的:根节点、左子树和右子树。若能依次遍历这三部分,就是遍历了整个二叉树。
  • 假设:L:遍历左子树,D:访问根结点,R:遍历右子树。L、D、R 分别遍历左子树、访问根结点和遍历右子树,则可以有 DLR、LDR、LRD、DRL、RDL、RLD 这 6 种遍历二叉树的方案。
    • 若规定先左后右,则只有前三中情况。
      • DLR——先(根)序遍历。
      • LDR——中(根)序遍历。
      • LRD——后(根)序遍历。
    • 第一个访问根结点就称为先序遍历,第二个访问根节点称为中序遍历,后序遍历同理。

在这里插入图片描述

算法描述

在这里插入图片描述

由二叉树的递归定义可知,遍历左子树和遍历右子树可如同遍历二叉树一样 递归 进行。

先序遍历二叉树

若二叉树为空,则空操作;否则执行以下操作:

  1. 访问根结点
  2. 先序遍历左子树
  3. 先序遍历右子树

牢记按照 根左右 的顺序来进行遍历。每个结点的左子树的所有结点遍历完了之后才能轮到右子树。当一个结点的左右子树都为空的时候表示访问完毕。

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

中序遍历二叉树

按照 左根右 的顺序进行遍历,根节点之下的每一棵二叉树都按照左根右的顺序遍历。
左子树按照 左根右 的顺序访问完所有结点之后才能访问根结点,最后右子树同样按照 左根右 的顺序访问所有结点。

若二叉树为空,则空操作;否则执行以下操作:

  1. 中序遍历左子树。
  2. 访问根结点。
  3. 中序遍历右子树。

每个结点的左子树按照左根右的顺序访问完了之后,才能访问根该结点,最后按照 左根右 的顺序访问该结点的右子树的所有结点。

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

后序遍历二叉树

每个结点都按照左右根的顺序访问结点,每个结点的左子树按照左右根的顺序访问完所有结点之后,再到该结点的右子树按照左右根的顺序放油所有结点,最后访问该结点。

若二叉树为空,则空操作;否则执行以下操作:

  1. 后序遍历左子树。
  2. 后序遍历右子树。
  3. 访问根结点。

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

例题

将所有非叶子结点看成根结点,然后按照各种每种遍历顺序进行遍历,就很轻松了。

例1

  • 写出下图二叉树的各种遍历顺序。

在这里插入图片描述

  • 先序遍历:ABDG CEHF
  • 中序遍历:DGBA EHCF
  • 后序遍历:GDBH EFCA

例2

  • 用二叉树表示算术表达式
  • 请写出下图所示二叉树的各种遍历顺序。

在这里插入图片描述

  • 先序遍历:-+ax b-cd /ef,先序的遍历结果称为前缀表达式(波兰式)
  • 中序遍历:a+bx c-d- e/f,运算符在两个操作数中间的,称为中缀表达式
  • 后序遍历:abcd -x+e f/-,后序的遍历结果称为后缀表达式(逆波兰式)。

以后如果遇到要求将中缀表达式转换成后缀表达式的,就是将二叉树的中序遍历变成后序遍历。

根据遍历序列确定二叉树

  • 若二叉树中各结点的值均不相同,则二叉树结点的先序序列、中序序列和后序序列都是唯一的。
  • 由二叉树的先序序列和中序序列,或者由二叉树的后序序列和中序序列可以确定唯一一棵二叉树
  • 由先序序列和后序序列不能确定一棵树的原因是,不能确定哪个结点时根。

已知先序序列和中序序列求二叉树**

已知二叉树的先序和中序序列,构造出相应的二叉树。

  • 先序(根左右):A B C D E F G H I J
  • 中序(左根右):C D B F E A I H G J
  1. 对于一棵大树来说,先序遍历的第一个结点 A 肯定就是根节点。
  2. 在中序遍历中找到根结点 A 了之后,就能确定,根节点A左边的结点 CDBFE,一定在左子树上,右子树同理为 IHGJ。

在这里插入图片描述

  1. 左子树按照先序排列的话第一个结点B一定是根。
    • 同理,知道B是根的话,B左边的结点一定在B的左子树位置,FE则在B的右子树位置。

在这里插入图片描述

  1. 同理,按照先序排列的右子树的第一个结点就是G,G左边的结点HI为左子树,J为G的右子树。

在这里插入图片描述

  1. 按照先序来看,对于CD来说,C在前面,所以C是根,又按照中序看,D在根C的后面(右边),所以D为C的右子树。

在这里插入图片描述

  1. 同理,由FE构成的树
    • 按先序看,E在前,所以E为根。
    • 按中序看,F在根E的前面,所以F在E的左子树。

在这里插入图片描述

  1. 在HI这棵子树上,
    • 由先序看:H在前,所以H为根。
    • 有中序看:I在前,所以 I 为H的左子树

最后就剩个 J 结点,所以不用再往后分了。

在这里插入图片描述

至此,这棵二叉树构造完成,不放心的同学可以用先序和中序的方法遍历一下这棵树。

在这里插入图片描述

已知中序序列和后序序列求二叉树

已知一棵二叉树的中序和后序序列,请画出这棵二叉树。

  • 中序(左根右)序列:B D C E A F H G
  • 后序(左右根)序列:D E C B H G F A
  1. 后序遍历可知,最后一个结点 A 一定是整棵树的 根节点。
    • 中序当中,知道A是根的话,那么A左边的结点BDCE就是左子树,右子树就是FHG。
  2. 中序(左根右)可知,第一个结点B一定是左子树的根,由后序(左右根)可知,找到结点B,B结点左边的结点DEC,就是以B为根的这棵树底下的所有结点。
  3. 由中序判断DCE都在B的右子树上,在后序中又能判断出C是B的右子树的根,由中序判断 根C 的左、右子树分别是D、E。

在这里插入图片描述

  1. 后序知,右子树的根是F,由中序知,HG在根F的后边(左根右,在根的后边的肯定都在右子树上),此时就剩个HG了,由后序可知,G在H的后面,说明G为根,且由中序判断,H在G的左边,说明 H 是 G 的左子树。

在这里插入图片描述

遍历的算法实现 - 先序遍历

  • 二叉树的定义实际是个递归的定义,二叉树的左、右子树任然是二叉树。
  • 如果能够遍历一棵二叉树的话,在遍历它的左、右子树的时候仍采用和遍历二叉树时相同的方法
    • 如:如果用先序来遍历一棵二叉树,那么这棵二叉树下的每个子树都能采用这个方法。

在这里插入图片描述

举个栗子

  • 按照先序遍历的方式访问上图这棵树,首先访问它的根节点A。
  • 然后去遍历它的左子树B(左子树按照同样的方式遍历),如果左子树不为空,先访问它的根结点B,然后左子树,然后右子树。
    • 此时以B为结点的左子树就访问完毕了。

在这里插入图片描述

  • 左子树访问完毕之后就到右子树了,遍历方法同大二叉树的一致。
    • 先访问右子树的根C,然后访问C的左子树为空,最后再访问C的右子树,也为空,此时访问结束。

在这里插入图片描述

二叉树的先序遍历递归算法

在这里插入图片描述

  • 先访问这棵树的根节点 T 。
  • 然后用同样的方法去递归访问左子树,将左子树的根结点T ->lchild 传给函数PreOrderTraverse
  • 当子树的某个结点为空的时候,返回上一层
  • 当左子树遍历完毕的时候,然后再继续递归遍历右子树。

执行过程

假设有这么一棵二叉树,指针 T 指向二叉树的根节点 A 。对以A为根的二叉树进行遍历。

在这里插入图片描述

//前趋(先序)遍历
void Pre(BiTree* T)
{
		//二叉树及二叉树底下的所有子树中,某一棵树不为空时,执行以下操作
		if(T != NULL)
		{
				printf("%d\n",T -> data);//输出根节点的值
				pre(T -> lchile);//以同样的先序遍历的方法遍历左子树
				pre(T -> rchile);//以同样的先序遍历的方法遍历右子树
				//当左、右子树的某个结点为空时,返回该结点的递归的上一层
		}
}

在这里插入图片描述

  • 反复的执行根左右的顺序,遍历每个小树。
  • 当有不为空的树时,就得执行根左右
    • 这样一层一层的深入,当执行完毕的时候再一层一层的返回。
  • 直到第一次调用的根左右执行完毕的时候,才返回到主函数

先序序列:A B D C

有了二叉树的先序遍历算法之后,中序以及后序算法起始也差不多了。

中序遍历算法

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

后序遍历算法

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

二叉树的递归遍历算法及分析

在这里插入图片描述

  • 以上三种算法非常相似,只有输出根结点的位置不一样。
  • 在先序遍历中将访问根结点的位置放在第一位,中序就放在中间,后序最后。
  • 如果将以上三种算法中访问根节点的这段语句拿走,这三个算法是完全相同的。
  • 或者说三中算法的访问路径是相同的,只是访问结点的时机不同而已。

在这里插入图片描述

从上图看:

  • 从虚线出发到终点的路径上,每个结点经过三次
    • 第一次经过时访问 = 先序遍历。
    • 第二次经过时访问 = 中序遍历。
    • 第三次经过时访问 = 后序遍历

第一次路过某个根结点的时候直接访问它,就相当于是先序遍历,如果将该结点的左子树访问完了之后,再回来访问该结点就是中序,后序同理。

时间复杂度

  • 这三中算法的时间复杂度都是相同的,有 n 个结点的话就要遍历 n 个结点。
  • 所以这三种算法的时间复杂度都为 O(n)

空间复杂度

  • 当遇到某个结点的时候,如果不访问它,就得找个空间将它记下来(这个结点没被访问),等回来的时候再来访问。
  • 在最坏的情况下(单支二叉树),每个路过的结点都不访问都要存起来。
  • 所以这三中算法最坏请况下的空间复杂度O(n)

遍历二叉树的非递归算法

中序遍历的非递归算法

二叉树中序遍历的非递归算法的关键

  • 在中序遍历过某结点的整个左子树后如何找到该结点的以及右子树

算法步骤

  1. 建立一个
  2. 结点进栈,遍历左子树
  3. 结点出栈,输出根结点,遍历右子树

举个例子

如下图所示的一棵二叉树的非递归遍历。

在这里插入图片描述

  1. 首先,遇到根节点 A 的时候不能访问它,必须先存到栈里头。

在这里插入图片描述

  1. 然后去访问 A 的左子树,同样的,B 结点为它所在的这棵树的根,先不能访问,得存进栈里。
    • 将 B 存进栈里头了之后,再去访问它的左子树,发现左子树问空,这时候就该访问B结点了,所以将根结点 B 出栈。

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

  1. 接下来就去访问 B 这个根结点的右子树 D 了。
    • 到了右子树之后还是先遇到了根结点 D(存起来)。

在这里插入图片描述

  1. 然后继续访问 D 的左子树,左子树为空,将 D 出栈,继续访问当前出栈元素(D)的右子树,右子树为空,此时 A 的左子树遍历完毕,将 A 出栈。

在这里插入图片描述

  1. 根节点访问完毕(出栈)之后,就该去访问该结点的右子树了。
    • 同样,遇到根(C)的时候不能访问,得先存起来,直到它的左子树为空的时候,就可以回头来访问(出栈)该结点了。

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

  1. 最后再访问刚出栈元素(C)的右子树,右子树为空的时候,回去看看栈里头还有没有元素栈空的话则说明整趟遍历完成

非递归遍历的算法描述

注:此算法仍然是在二叉链表上实现的。

//中序遍历二叉树 T 的非递归算法
Status InOrderTraverse(BiTree T)
{
		BiTree p;//用指针 p 指向要操作的结点(出入栈)
		InitStack(S);
		p = T;//先将二叉树的根结点赋给 p
		q = new BiTNode;//申请一个结点空间 q 用来存放栈顶弹出的元素,q的定义在另一个函数

		while(p || !StackEmpty(s))//当p指向的结点为空,并且栈也为空的时候,退出循环
		{
				if(p)// p 指向的当前的根结点不为空
				{
						push(S,p);//将当前的根结点入栈
						p = p -> lchild;//去访问该根结点的左子树
				}
				else// p 当当前结点的左孩子为空时
				{
						Pop(S,p);//首先将栈顶元素弹出,
						//这样做的目的是能够比较好的对当前遍历树的位置进行定位,
						//以至于能做到对该节点右子树的后续遍历操作
						cout << q -> data;//访问根结点
						p = q -> rchild;//遍历右子树
				}
		}
}

二叉树的层次遍历算法

  • 层次遍历:顾名思义就是按照二叉树的层数来遍历,第一层遍历完了之后遍历第二层,接着第三次以此类推。
  • 对于一棵二叉树,从根结点开始,按照从上到下、从左到右的顺序访问每一个结点,且每个结点只访问一次。

如:下图所示的二叉树的遍历结果就是:a b f c d g e h

在这里插入图片描述

算法思路

使用一个队列

  • 将根结点进队
  • 队不为空时执行循环:不断从队伍中出队一个结点 *p,访问这个结点。
    • 若该结点有左孩子结点,将左孩子结点进队
    • 若该结点有右孩子结点,将右孩子结点进队

举个栗子

  1. 先将根结点 a 入队

在这里插入图片描述

  1. 然后将当前队列当中的根结点 a 出队,再出队的同时,判断该结点时候有左右孩子,若有,则存进队中

在这里插入图片描述

  1. 若队列当中还有元素,则继续出队,将 b 出队指向上述步骤,将 b 结点的左右孩子入队

在这里插入图片描述

  1. 将 f 结点出队,将 f 的左孩子 g 入队

在这里插入图片描述

  1. 将 c 出队,c 无左右孩子,所以不管。d 出队,e 入队。g出队,h入队,然后依次出队,直到队空位置

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

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

算法实现

这个队列依然用咱们用的最多的顺序循环队列

//定义一个顺序循环队列
typedef struct
{
		BTNode data[MAX];//存放队中元素
		int front,rear;//队头和队尾指针
}SqQueue;//顺序循环队列类型

层次遍历算法

在这里插入图片描述

二叉树遍历算法的应用

遍历是二叉树各种操作的基础,假设访问结点的具体操作不仅仅局限于输出结点数据域的值,而把访问延伸到对结点的判别、计数等其他操作,可以解决一些关于二叉树的其他实际问题。如果在遍历过程中生成结点,这样便可建立二叉树的存储结构。

先序遍历的顺序建立二叉链表

按照先序遍历序列建立二叉树的二叉链表。
例:已知先序序列为:A B C D E G F

  1. 从键盘中输入二叉树的结点信息,建立二叉树的存储结构。
  2. 在建立二叉树的过程中按照二叉树先序的方式建立。先建立根节点,然后建立左子树,最后建立右子树

只知道先序序列的话,构造的树是不唯一的,下面两种树都有可能。

在这里插入图片描述

如果想建立的是第一棵树而不是第二棵,可以给这两棵树补充一些空节点,补充完之后这两棵树就不一样了。

在这里插入图片描述
空节点可以用空格符或者其他符号来表示

算法步骤

  1. 扫描字符序列,读入字符 ch
  2. 如果 ch 是一个 # 字符,则表示该二叉树为空树,即 T 为 NULL;否则执行以下操作。
    • 申请一个结点空间 T。
    • 将输入的字符 ch 符给结点的数据域 T->data。
    • 递归创建 T 的左子树。
    • 递归创建 T 的右子树。

在这里插入图片描述

算法描述

//按照先序次序输入二叉树中结点的值(一个字符),创建二叉树链表表示的二叉树 T
Status CreateBiTree(BiTree &T)
{
		scanf(&ch);//将输入的值放到ch里
		if('#' == ch);//如如果遇到了 # 字符就将当前的树构造为空树
		{
				T = NULL;
		}
		else//递归创建二叉树
		{
				T = (BiTNode*)malloc(sizeof(BiTNode));//向内存中申请一块结点空间,并将该空间的首地址赋给T
				if(!T)//如果开辟空间成功则执行以下操作
				{
						exit(OVERFLOW);//生成根结点
				}
				T -> data = ch;//将根节点的数据域置为输入的字符 ch
				CreateBiTree(T -> lchild);//以当前结点的左孩子域为参数,递归构造左子树
				CreateBiTree(T -> rchild);//以当前结点的右孩子域为参数,递归构造右子树
		}
		return OK;
}

在这里插入图片描述

复制二叉树

算法步骤

如果要复制的树为空树,则递归结束,反之执行以下操作。

  • 申请一个新结点空间,复制根结点。
  • 递归复制左子树。
  • 递归复制右子树。

.算法描述

//复制一棵和 T 完全相同的二叉树
void Copu(BiTree T,BiTree &NEWT)
{
		if(NULL == T)
		{
				//如果原来的树T是空树,递归结束
				NewT = NULL;
				return 0;
		}
		else
		{
				NewT = (BiTNode*)malloc(sizeof(BiTNode));
				NewT -> data = T -> data;//将根结点数据域的内容复制到新树的根结点
				Copy(T -> lChile,NewT -> lchile);//递归复制左子树
				Copy(T -> rChile,NewT -> rchile);//递归复制右子树
		}
}

计算二叉树的深度

在这里插入图片描述

算法步骤

如果是空树,则递归结束,且返回深度为0,反之执行以下操作。

  • 递归计算左子树的深度记为 m。
  • 递归计算右子树的深度记为 n 。
  • 如果 m 大于 n,二叉树的深度为 m + 1,反之为 n + 1,加的这个1是根结点的那一层。

算法描述

//计算二叉树 T 的深度(这棵二叉树有几层)
int Depth(BiTree T)
{
		if(NUll == T)//如果是空树,则深度为0,递归结束
		{
				return 0;
		}
		else
		{
				m = Depth(T -> lchild);//递归计算左子树的深度记为 m
				n = Depth(T -> rchile);//递归计算右子树的深度为 n
				if(m > n)//返回二叉树的深度 m 与 n 的较大的那个值+1
				{
						return m + 1;
				}
				else//m小于或等于n的时候都可以返回n+1
				{
						return n + 1;
				}
		}
}

统计二叉树中结点的个数

  • 如果是空树,则结点个数为 0。
  • 反之,结点个数为左子树的结点个数加上右子树的结点个数再加上 1(根节点)

算法描述
先求左子树的左子树,再加上左子树的右子树,最后加上左子树的根。

//统计二叉树 T 中结点的个数
int NodeCount (BiTree T)
{
		//如果是空树,则结点个数为 0 ,递归结束
		if(NULL == T)
		{
				return 0;
		}
		//反之返回结点个数为左子树的结点个数+右子树的结点个树再+1
		else
		{
				return NodeCount(T -> lchile) + NodeCount(T - rchild)+1
		}
}

举个栗子

在这里插入图片描述

  1. 刚开始时指针是指向根节点 a 的,如果根节点不为空,则去统计它的左子树右多少个结点。
  2. 以 a 的左孩子结点 b 作为参数再次调用这个函数,
  3. 此时进入第三层调用,用当前根节点的左孩子 c 来调用这个函数。
  4. 再次以 c 为根节点调用它的左孩子,发现为空为0,然后去算c的右子树,发现也为空(0),然后以c为根节点的这个左子树的结点数为 0 + 0 + 1 = 1。

计算二叉树中叶子结点数

可以模仿计算二叉树的深度这个算法,写出统计二叉树中叶子结点(度为0)的个数

算法步骤

  • 如果是空树,则叶子结点的个数为 0 。
  • 否则,为左子树的结点个数 + 右子树的结点个数(不用加上根节点)。

算法描述

//统计二叉树㕜的叶子结点的个数
int LeafCount(BiTree T)
{
		//如果T为空树,则叶子结点为0
		if(NULl == T)
		{
				return 0;
		}
		//如果该结点的左右孩子都为空,则此结点为叶子结点,是叶子结点那就发现了一个叶子结点返回1
		if(T -> lchild == NULL && T -> rchild == NULL)
		{
				return 1;
		}
		else
		{
				return LeafCount(T -> lchild)+LeafCount(T -> rchild);
		}
}

举个栗子

在这里插入图片描述

  1. 如果T指向的当前的根节点不为空,则去看看该结点的左右孩子是否为空。
    • 如果左右孩子都是控的,则返回1,表示只有一个叶子结点。
  2. 如果不是左右孩子都为空,则执行程序的else语句里的内容。
    • 统计一下左子树的叶子,然后去统计右子树的叶子,最后加起来。

5.5.2 线索二叉树

当用二叉链表作为二叉树的存储结构时,可以很方便的找到某个结点的左右孩子,但一般情况下,无法直接找到该结点在某种遍历序列中的前趋和后继结点

在这里插入图片描述

如何寻找特定遍历序列中二叉树结点的前趋和后继?

解决方法

  1. 通过遍历寻找——费时间。
  2. 给每个结点再增加两个指针域,用来存放该结点的前趋、后继结点——增加了存储负担。
  3. 利用二叉链表中的空指针域

在这里插入图片描述

利用二叉链表中的空指针域

  • 如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前趋。如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继。-

    • 这种改变指向的指针称为线索
    • 加上了线索的二叉树称为线索二叉树(Threaded Binary Tree)。
  • 对二叉树按某种遍历次序使其变位线索二叉树的过程叫做线索化

举个栗子

有个以下图这棵二叉树为原型存储的二叉链表

在这里插入图片描述

  1. 根节点 A 没有右孩子,又因为 A 属于中序遍历的最后一个结点,它没有后继,所以继续空着。
  2. B 的左、右孩子指针域不为空,不管。
  3. C 结点没有左右孩子
    • 又发现 C 是中序遍历的第一个结点,没有前趋结点,左孩子域继续为空。
    • 后继结点是 B,将右孩子域改为指向 B 这个结点。
  4. D 结点没有空指针域,不管。
  5. E 没有左孩子,它的前趋是 B 结点,所以将左指针域内的指针改为指向 B 结点。
  6. F 没有左右孩子,将左指针指向 D结点,右指针指向 A结点。
  7. G 没有左右孩子,左指向 E,右指向 D。

在这里插入图片描述

为区分 lrchild 和 rchild 指针到底是指向孩子的指针,还是指针前趋或者后继的指针,对二叉链表中每个结点增加连个标志域 ltagrtag其中:

  • ltag = 0,则 lchild 指向该结点的左孩子
  • ltag = 1,则 lchild 指向该结点的前趋
  • rtag = 0,则 rchild 指向该结点的右孩子
  • rtag = 1,则 rchild 指向该结点的后继

这样,结点结构就变成了:

在这里插入图片描述

二叉线索类型定义

//二叉树的二叉线索存储表示
typedef struct BiThrNode
{
		int data;//数据域,存储数据元素本身
		int ltag,rtag;//左右标记域,存放 0 1
		struct BiThrNode* lchild,rchild;//左右孩子指针
}BiThrNode,*BiThrTree;

构造线索二叉树

先序线索二叉树
存储线索的时候,存储的是它先序遍历下的前趋、后继

在这里插入图片描述

  1. A 的两个指针分别指向左、右孩子,所以两个标记都是 0.
  2. B 没有左孩子,所以左孩子域存储 A 的地址,A 是 B 的前趋,所以 ltag为 1。右孩子指针指向的是 B 结点的右孩子 C 结点,所以 rtag为 0。
  3. C 结点左孩子指针指向前趋 B,ltag为1,右孩子域指向后继 D,所以 rtag 为 1。
  4. D 结点左孩子域指向 E,E为D的左孩子,所以 ltag为 0,D的右孩子域为空,所以指向D的后继E,rtag 为1.
  5. E 结点没有左右孩子,左孩子指向前趋D,ltag 为1,又因为E结点既没有右孩子也没用后继,所以右指针为空,且 rtag为1.

有了先序构造线索二叉树之后,中序、后序也是同样的道理。

中序线索二叉树

在这里插入图片描述

后序线索二叉树

在这里插入图片描述

小试牛刀

  • 画出以下二叉树对应的中序线索二叉树。
  • 该二叉树中序遍历结果为:H D I B E A F C G

在这里插入图片描述

先将叶子结点都利用起来,将他们所指向的前趋和后继先画出来

在这里插入图片描述

增加头结点版本

在这里插入图片描述

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

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

相关文章

Centos7开启SSH连接配置

1、查看是否已安装openssh-server&#xff1a; [rootlocalhost ~]# yum list installed | grep openssh-server 如果有信息说明已安装了openssh-server&#xff0c;如果输出没有任何结果&#xff0c;说明没有安装。 2、安装openssh-server&#xff08;如果已安装&#xff0c…

微信小程序(学习笔记篇)

基本项目结构 pages用来存放所有小程序的页面utils 用来存放工具性质的模块&#xff08;例如:格式化时间的自定义模块)app.js小程序项目的入口文件app.json 小程序项目的全局配置文件app.wXss小程序项目的全局样式文件project.config.json项目的配置文件sitemap.json用来配置小…

买卖股票的最佳时机 II -数学推导证明贪心思路 -leetcode122

问题说明来源leetcode 一、问题描述: 122. 买卖股票的最佳时机 II 难度中等1941 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可…

Spark Core----RDD详解

为什么需要RDD 分布式计算需要&#xff1a; 分区控制&#xff08;多台机器并行计算&#xff0c;将一份数据分成多份&#xff0c;在不同机器上执行&#xff09;Shuffle控制&#xff08;不同分区数据肯定需要进行相关的关联&#xff0c;不同分区进行数据传输叫Shuffle控制&…

分享77个NET源码,总有一款适合您

NET源码 分享77个NET源码&#xff0c;总有一款适合您 NET源码下载链接&#xff1a;https://pan.baidu.com/s/1vhXwExVAye5YrB77Vxif8Q?pwdzktx 提取码&#xff1a;zktx 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xf…

Html 3D旋转相册制作

程序示例精选 Html 3D旋转相册制作 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<Html 3D旋转相册制作>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学习…

zabbix监控主机

zabbix官网 zabbix分为zabbix server&#xff08;zabbix服务端&#xff0c;用来展示监控的&#xff09;和zabbix-agent&#xff08;zabbix客户端用来收集数据的&#xff09; zabbix-agent客户端有两种工作模式&#xff0c;被动模式&#xff08;由zabbix服务来采集数据&#xff…

二十二、Kubernetes中Pod调度第四篇污点(容忍)调度详解、实例

1、概述 在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达某…

魔方爱好者快来康康,困难的平面魔方来了!

前言和效果图我今天看到一个网站&#xff0c;就是关于魔方的&#xff0c;里面二阶魔方引起了我的兴趣。https://rubiks-cube-solver.com/2x2/进去后你们可以看到&#xff0c;二阶魔方的平面展开图&#xff0c;复原也更加困难。虽然是英文的&#xff0c;但我还是玩得不亦乐乎。好…

查看GPU使用情况和设置CUDA_VISIBLE_DEVICES

文章目录一、简介二、查看GPU状态和信息三、使用3.1临时设置&#xff08;临时设置方法一定要在第一次使用 cuda 之前进行设置&#xff09;3.2python 运行时设置3.3永久设置四、参考资料一、简介 服务器中有多个GPU&#xff0c;选择特定的GPU运行程序可在程序运行命令前使用&am…

企业舆情监控排查什么,TOOM讲解企业舆情监控工作方案?

互联网时代&#xff0c;企业舆情发生因素很多&#xff0c;如果不能及时监控解决&#xff0c;就会引发无限舆情发展&#xff0c;进而影响到企业品牌声誉&#xff0c;引发企业信用危机&#xff0c;所以就需要做好企业舆情监控&#xff0c;接下来我们简单了解企业舆情监控排查什么…

CMMI的五个级别及其特征简述

CMMI 一共分五个级别&#xff0c;一级最低&#xff0c;五级最高&#xff0c;一般企业初次认证CMMI从三级开始。 1、CMMI一级&#xff0c;完成级。在完成级水平上&#xff0c;企业对项目的目标与要做的努力很清晰。项目的目标得以实现。一般来说&#xff0c;公司的初始阶段就…

【C进阶】指针笔试题汇总

家人们欢迎来到小姜的世界&#xff0c;<<点此>>传送门 这里有详细的关于C/C/Linux等的解析课程&#xff0c;家人们赶紧冲鸭&#xff01;&#xff01;&#xff01; 指针笔试题前言一、题1&#xff08;一&#xff09;题目&#xff08;二&#xff09;答案及解析&#…

【Python】函数——模块与函数的导入

概述 为了方便使用函数&#xff0c;我们可以将函数存储在称为模块的独立文件中&#xff0c;再将模块导入到主程序中&#xff0c;导入一个模块需要使用import语句。 导入整个模块 模块是扩展名为.py的文件 1、导入某个模块 语法为&#xff1a; import module_name 2、导入…

[oeasy]python0043_八进制_oct_octal_october_octave

八进制(oct) 回忆上次内容 什么是 转义&#xff1f; 转义转义 转化含义\ 是 转义字符\n、\r是 转义序列 还有什么 转义序列 吗&#xff1f; \a是 响铃\b 退格键\t 水平制表符 tab键\v、\f 实现喂纸不回车 通过 16进制数值 转义 \xhh输出 (hh)16进制对应的ascii字符 如果我们不…

Portainer使用docker compose搭建nacos并初始化MySQL、Portainer stack搭建nacos并初始化MySQL

Portainer使用docker compose搭建nacos初始化MySQL、Portainer stack搭建nacos初始化MySQL新建stack(堆栈)添加stack(堆栈)名称添加docker-compose规则配置环境变量上传初始化sql文件找初始化sql文件nacos初始化mysql-schema.sql文件内容上传sql文件到初始化挂载目录部署stack(…

打印机不打印故障简单排除方法

日常工作中经常会遇到打印机不能打印的情况&#xff0c;那么又没有专业的技术人员在场帮忙的情况下我们也可以自己动手简单的处理一下故障&#xff0c;可以尝试以下的方法进行简单的故障排除&#xff1b; 一、使打印机处于联机状态&#xff0c;如果打印机没有处于联机状态&…

医疗影像工具LEADTOOLS 入门教程: 检测和提取 MICR - 控制台 C#

LEADTOOLS是一个综合工具包的集合&#xff0c;用于将识别、文档、医疗、成像和多媒体技术整合到桌面、服务器、平板电脑、网络和移动解决方案中&#xff0c;是一项企业级文档自动化解决方案&#xff0c;有捕捉&#xff0c;OCR&#xff0c;OMR&#xff0c;表单识别和处理&#x…

基于STM32或STC的手势控制MP3语音播放器的设计

一. 系统设计框图 区别于传统设计中的按键开关控制&#xff0c;本设计可以实现通过手势控制MP3播放器。采用STM32或STC15单片机和PAJ7620手势模块&#xff0c;能够识别九种手势&#xff0c;分别为上下左右前后&#xff0c;顺时针&#xff0c;逆时针&#xff0c;挥动。在本设计…

SpringBoot视图解析与模板引擎

目录 1、视图解析 1、视图解析原理流程 2、模板引擎-Thymeleaf 1、thymeleaf简介 2、基本语法 3、设置属性值-th:attr 4、迭代 5、条件运算 6、属性优先级 3、thymeleaf使用 1、引入Starter 2、自动配置好了thymeleaf 3、页面开发 4、构建后台管理系统 1、项目…