二叉树遍历与线索化

本文详细介绍了二叉树的遍历方法,包括先序、中序和后序遍历,以及它们的递归定义和在输出节点、统计叶子节点、输出叶子节点、计算树深度等方面的应用。此外,还探讨了非递归算法实现,如使用栈进行遍历,以及线索二叉树的概念和构建。最后,展示了如何通过遍历序列重建二叉树,并提供了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

遍历:按一定的规律对二叉树中的每个结点进行访问且仅访问一次。
访问:输出结点信息、打印树、统计数目、各种计算等操作。
目的:将非线性结构变成线性化的访问序列。

二叉树的遍历

二叉树的遍历方式

DLR
DRL
LDR
LRD
RDL
RLD
规定先左后右的顺序的三种方案
根据对根访问的先后命名遍历方案
DLR 先序遍历
LDR 中序遍历
LRD 后序遍历
在这里插入图片描述

三种遍历的递归定义

(1) 先序遍历(DLR)操作过程
若二叉树为空,则为空操作,否则依次执行如下三个操作:
① 访问根结点;
② 按先序遍历左子树;
③ 按先序遍历右子树。
在这里插入图片描述
先序遍历:ABDFGCEH
(2) 中序遍历(LDR)操作过程
若二叉树为空,则为空操作,否则依次执行如下三个操作:
① 按中序遍历左子树;
② 访问根结点;
③ 按中序遍历右子树。
在这里插入图片描述
先序遍历:BFDGACEH
(3) 后序遍历(LRD)操作过程
若二叉树为空,则为空操作,否则依次执行如下三个操作:
① 按后序遍历左子树;
② 按后序遍历右子树;
③ 访问根结点。
在这里插入图片描述
先序遍历:FGDBHECA

最早应用:存储在计算中的表达式求值

存储方式:先序
例如:(a+bc)-d/e
前缀:-+a
bc/de
中缀:a+bc-d/e
后缀:abc
+de/-
逆波兰式易于求值
在这里插入图片描述

二叉树的遍历算法

(1) 先序遍历二叉树

void PreOrder(BiTree root) {
	if(NULL!=root) {
		Visit(root->data);
		PreOrder(root->LChild);
		PreOrder(root->RChild);
	}
}

(2) 中序遍历二叉树

void InOrder(BiTree root) {
	if(NULL!=root) {
		InOrder(root->LChild);
		Visit(root->data);
		InOrder(root->RChild);
	}
}

(3) 后序遍历二叉树

void PostOrder(BiTree root) {
	if(NULL!=root) {
		PostOrder(root->LChild);
		PostOrder(root->RChild);
		Visit(root->data);
	}
}

在这里插入图片描述

遍历算法应用

输出二叉树中的结点

(1) 前序遍历输出结点

void PreOrder(BiTree root) {
	if(root!=NULL) {
		Visit(root->data);  /* 先序访问 */
		PreOrder(root->LChild);
		PreOrder(root->RChild);
	}
}
void PreOrder(BiTree root) {
	if(root!=NULL) {
		Printf(root->data);  /* 先序输出*/
		PreOrder(root->LChild);
		PreOrder(root->RChild);
	}
}

(2) 中序遍历输出结点

void InOrder(BiTree root) {
	if(root!=NULL) {
		InOrder(root->LChild);
		Visit(root->data);  /* 中序访问 */
		InOrder(root->RChild);
	}
}
void InOrder(BiTree root) {
	if(root!=NULL) {
		InOrder(root->LChild);
		Printf(root->data);  /* 输出结点 */
		InOrder(root->RChild);
	}
}

(3) 后序遍历输出结点

void PostOrder(BiTree root) {
	if(root!=NULL) {
		PostOrder(root->LChild);
		PostOrder(root->RChild);
		Visit(root->data);  /* 中序访问 */
	}
}
void PostOrder(BiTree root) {
	if(root!=NULL) {
		PostOrder(root->LChild);
		PostOrder(root->RChild);
		Printf(root->data);  /* 输出结点 */
	}
}

输出二叉树中的叶子结点

(1) 前序遍历输出叶子结点

void PreOrder(BiTree root) {
	if(root!=NULL) {

		Visit(root->data);  /* 先序访问 */

		PreOrder(root->LChild);
		PreOrder(root->RChild);
	}
}

(2) 中序遍历输出叶子结点

void InOrder(BiTree root) {
	if(root!=NULL) {
		InOrder(root->LChild);

		Visit(root->data);  /* 中序访问 */

		InOrder(root->RChild);
	}
}

(3) 后序遍历输出叶子结点

void PostOrder(BiTree root) {
	if(root!=NULL) {
		PostOrder(root->LChild);
		PostOrder(root->RChild);

		Visit(root->data);  /* 中序访问 */

	}
}

统计二叉树中的叶子结点数目

(1) 前序统计叶子结点数目

void PreOrder(BiTree root) {
	if(root!=NULL) {

		Visit(root->data);  /* 先序访问 */

		PreOrder(root->LChild);
		PreOrder(root->RChild);
	}
}

(2) 中序统计叶子结点数目

void InOrder(BiTree root) {
	if(root!=NULL) {
		InOrder(root->LChild);

		Visit(root->data);  /* 中序访问 */

		InOrder(root->RChild);
	}
}

(3) 后序统计叶子结点数目

void PostOrder(BiTree root) {
	if(root!=NULL) {
		PostOrder(root->LChild);
		PostOrder(root->RChild);

		Visit(root->data);  /* 中序访问 */

	}
}

(4) 递归算法统计叶子结点数目
空树:0 仅有根结点的树:1
其他:左子树叶子数+右子树叶子数

int Leaf(BiTree root) {
    if(root==NULL)
         return 0;
    else if(root->LChild==NULL&&root->RChild==NULL)
         return 1;
     else
         return Leaf (root->LChild)+Leaf(root->RChild);
}

创建二叉树

(1) 扩展的遍历序列
在遍历序列中,不忽略空子树,而用特定的元素表示空子树。
在这里插入图片描述

先序遍历序列:
ABCD

扩展先序遍历序列:
AB…CD…
(2) 用扩展的先序遍历序列创建二叉树

void CreateBiTree(BiTree *bt) {
	char ch;
	ch=getchar();
	if(ch=='.')     /* 创建空树 */
		*bt=NULL;
	else {     /* 创建树 */
		*bt=(BiTree)malloc(sizeof(BiTNode));
		(*bt)->data=ch;
		CreateBiTree(&((*bt)->LChild));
		CreateBiTree(&((*bt)->RChild));
	}
}

在这里插入图片描述

深度

在这里插入图片描述

(1) 后序遍历求深度
空树:0
非空树:max(L的深度,R的深度)+1

int PostTreeDepth(BiTree bt) {
	int hl,hr,max;
	if(bt!=NULL) {
		hl=PostTreeDepth(bt->LChild);  /* 左子树深度 */
		hr=PostTreeDepth(bt->RChild);  /* 右子树深度 */
		max=hl>hr?hl:hr;
		return max+1;  /* 树深度 */
	} else
		return 0;  /* 空树深度 */
}

(2) 前序遍历求深度
深度=结点的最大层次
孩子结点层次:双亲层次+1

int depth=0;  /* 初始深度为0 */
void PreTreeDepth(BiTree bt,int h) {  /* 层次h初值为1 */
	if(bt!=NULL) {
		if(h>depth) depth=h;   /* 前序新深度 */
		PreTreeDepth(bt->LChild,h+1);
		PreTreeDepth(bt->RChild,h+1);
	} 
}

二叉树的横向打印

(1) 输出结果:是竖向树的90度旋转
在这里插入图片描述

逆中序遍历RDL:CFEADB。
每行输出一个结点。
结点在行中的位置与所在的层深有关。

void PrintTree(BiTree bt,int nLayer) {  /* 层号决定空格数 */
	int  i;
	if(bt!=NULL)  {

		PrintTree(bt->RChild,nLayer+1); /* 先右 */
	
		Visit(bt->data);
		
	
		PrintTree(bt->LChild,nLayer+1); /* 后左 */
	}
}

基于栈的递归消除

递归消除的基本思想:
在遍历过程中,用栈来记录未访问的结点的指针。

中序遍历的非递归算法

[算法思想1] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 从当前结点开始,进栈并遍历左子树,直到左子树为空;(循环)
② 退栈并访问;
③ 遍历右子树。
在这里插入图片描述
[算法思想2] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 如果当前结点存在,则进栈并遍历左子树;
② 否则退栈并访问,然后遍历右子树。
(1) 直接实现栈操作
定义存储结点地址的顺序栈空间
#define m 50
BiTree s[m+1];
直接按下标操作栈
在这里插入图片描述
(2) 调用栈操作的函数
IniStatic(&S)
IsEmpty(S)
Push(&S,p)
Pop(&S,&p)

后序遍历的非递归算法

[算法思想] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 从当前结点开始,进栈并遍历左子树,直到左子树为空;(循环)
② 如果栈顶结点的右子树为空,或者栈顶结点的右孩子为刚访问过的结点,则退栈并访问;
③ 否则,遍历右子树。
在这里插入图片描述

前序遍历的非递归算法

[算法思想] 从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:
① 如果当前结点存在,则访问结点、右子树入栈、遍历左子树;
② 否则退栈遍历右子树。
在这里插入图片描述

线索二叉树

问题:n个结点的二叉树有多少个空链域?
在这里插入图片描述

基本概念

(1) 概念
线索:充分利用二叉树的空链域,用于记录遍历结果的前驱或后继信息。
线索化:就是加线索的过程。相应的二叉树称为线索二叉树。
(2) 线索二叉树结点的结构
在这里插入图片描述
Ltag=0,LChild指向左孩子
Ltag=1,LChild指向前驱结点
Rtag=0,RChild指向右孩子
Rtag=1,RChild指向后继结点
(3) 线索二叉树结点的定义

typedef struct Node {
	DataType data;
	int Ltag;
	int Rtag;
	struct Node *LChild;
	struct Node *RChild;
} BiTNode,*BiTree;

举例:中序线索二叉树的存储结构
中序遍历:DGBAEHCF
在这里插入图片描述
特殊结点的处理:加头结点
在这里插入图片描述
叶子结点有何特点?

二叉树的线索化

首先,创建二叉链表

void CreateBiTree(BiTree *bt) {
	char ch;
	ch=getchar();
	if(ch=='.')
		*bt=NULL;
	else {
		*bt=(BiTree)malloc(sizeof(BiTNode));
		(*bt)->data=ch;
		(*bt)->Ltag=0; (*bt)->Rtag=0;  /* 线索先都设为0 */
		CreateBiTree(&((*bt)->LChild));
		CreateBiTree(&((*bt)->RChild));
	}
}

在这里插入图片描述
其次,中序加线索

BiTree pre=NULL;  /* 初始前驱为空 */
void Inthread(BiTree root) {
	if(root!=NULL) {
		Inthread(root->LChild);  /* 遍历左子树 */	
		if(root->LChild==NULL) {  /* 置前驱线索 */
			root->Ltag=1; root->LChild=pre;
		}
		if(pre!=NULL&&pre->RChild==NULL) {/* 置后继线索 */
			pre->Rtag=1; pre->RChild=root;
		}		
		pre=root;   /* 记录下次遍历前驱结点 */
		Inthread(root->RChild); /* 遍历右子树 */
	}
}

在线索二叉树中找前驱、后继结点

(1) 结点的前驱结点
线索记录的前驱结点
或左子树“最右下端”的结点
在这里插入图片描述

BiTNode *InPre(BiTNode *p) {
	BiTNode *q,*pre;
	if(p->Ltag==1)
		pre=p->LChild;
	else {
		for(q=p->LChild; q->Rtag==0; q=q->RChild)
			;
		pre=q;
	}
	return pre;
}

(2) 结点的后继结点
线索记录的后继结点
或右子树“最左下端”的结点
注:中序最后一个结点没有右子树。

BiTNode *InNext(BiTNode *p) {
	BiTNode *q,*Next;
	if(p->Rtag==1)  /* 利用线索的后继 */
		Next=p->RChild;
	else if(p->RChild==NULL)  /* p是末结点 */
		Next=NULL;
	else {	/* 右子树“最左下端”结点 */
		for(q=p->RChild; q->Ltag==0; q=q->LChild) ;
		Next=q;
	}
	return Next;
}

遍历中序线索二叉树

(1) 先找到中序遍历的第一个结点
遍历左子树,直到没有左孩子的结点

BiTNode * InFirst(BiTree root) {
	BiTNode *p=root;
	if(!p) return NULL;
	while(p->Ltag==0) p=p->LChild;
	return p;
}

(2) 依次遍历下一个结点,直至结点为空

void TInOrder(BiTree root) {
	BiTNode *p;
	p=InFirst(root);
	while(p) {
		Visit(p->data);
		p=InNext(p);
	}
}

中序线索二叉树插入右结点

(1) 结点的右孩子为空时的插入
在这里插入图片描述
(2) 末结点
在这里插入图片描述
(3) 结点的右孩子为非空时的插入
在这里插入图片描述

由遍历序列确定二叉树

已知中序遍历:B D C E A F H G
已知后序遍历:D E C B H G F A
在这里插入图片描述
欢迎大家加我微信交流讨论(请备注csdn上添加)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程子的小段

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值