C语言简单上手二叉树的线索化

本文详细解析了二叉线索树的构建过程,包括中序线索化的实现步骤和代码分析。线索化二叉树允许在非递归方式下进行遍历,通过利用空指针存储前驱或后继节点信息,形成类似链表的结构。文中给出了中序遍历线索二叉树的非递归算法,便于理解和操作。
摘要由CSDN通过智能技术生成

线索二叉树的建立与二叉树的线索化是一个很重要的内容,它充分体现了二叉树的实际应用,构思巧妙,在实现上也存在一些细节。

究其本质,二叉树的线索化就是充分利用二叉树里的空闲的指针空间(每个结点的左右孩子指针),如果为空的话就让它重新指向(可能是前驱或后继),这个过程就叫做线索化。而线索化后的二叉树就形成了类似链表的回环式结构,非常方便在遍历中寻觅前驱后继(主要极大地便利了寻找父亲结点的操作)。

可能用语言描述会略显抽象,那么我们不妨插入一张非常经典的教科书上的图例。

这里面我们需要理解一个概念,那就是在线索化里面会存在所谓的顺序。为啥叫中序线索化?主要是最后参考的遍历中输出的结点序列是中序遍历一样的。

下面进行代码的分析阶段,对于这种还稍微有一些抽象的算法,想要马上熟练写出是有些困难的。作为初学者,我们更应该做到的是从原理出发,先明白算法的整个逻辑,在以伪代码的形式在脑海或者演草纸上实现,然后手操写出对应代码。

对于他人的代码,我们可能一时半会会难以读懂,但是这本身就是一个历练的过程,我们必须将每一个细节都融会贯通,才能打通任督二脉。

接下来我就来粗浅的分析一下个人的一些见解。

代码大致可以分成三部分。

第一部分是线索化操作,将二叉树的每个结点的空指针都赋值,这一步也是核心操作。

先贴出代码吧。

void InThreading(BiThrTree p)
{
	/***************************Begin*************************/
    if(p)
    {
    	InThreading(p->lchild);
    	if(!p->lchild)
    	{
    		p->LTag=1;
    		p->lchild=pre;
		}
		else
		p->LTag=0;
		if(!pre->rchild)
		{
			pre->RTag=1;
			pre->rchild=p;
		}
		else if(pre->rchild!=pre)//这是根节点的特征 
		pre->RTag=0;
		pre=p;
		InThreading(p->rchild);
	}
    /***************************End***************************/
    
}//InThreading

哦,对了,相比于普通二叉树,线索二叉树的结点结构稍微有所变化。原因是我们需要区分对于二叉树的一个结点,它的左右指针到底指向什么(线索化后不存在所谓的空指针,但是指针可以是正常地指向左右孩子,也可以是前驱后继,这时候需要一个标志符)。

typedef struct BiThrNode
{				
	char data;							//结点数据域
	struct BiThrNode *lchild,*rchild;	//左右孩子指针
	int LTag,RTag;
}BiThrNode,*BiThrTree;

LTag与RTag的值,0为正常,1为线索化的标志,分别区分左右子树。

我们规定,左子树为空,将指针指向父亲结点,右子树为空,将其指向后续结点。

那么,很正常的,我们就需要两个前后步调一致的结点指针,通过它完成线索化的赋值。

这就是pre与p。

P是当前结点指针,pre是前驱结点指针,我们为了能遍历整棵树,就用到了递归。

InThreading(p->lchild),一直向下探索到左边的叶子结点,将路径上所有结点压栈,统一释放由最深处开始执行线索化。在叶子结点线索化后,InThreading(p->rchild)是将所有结点的右子树也包含其中,完成整棵树的遍历。

一个形象化的说法,就是在中序遍历过程中,将访问结点的操作换成了线索化,执行顺序完全不变。

以此为参考,我们也可以设计出其他遍历顺序的线索化。

线索化的操作应该很易懂,看看结点的左右指针是否为空,若空,对应的Tag标志域改变,指针指向改变。

这个函数还是很好理解的。

第二个函数,就是构造线索化链表了,在这里面,出现了很多耐人寻味的细节,需要我们细细体悟。

void InOrderThreading (BiThrTree &Thrt,BiThrTree T)
{
	/*****************************Begin*********************************/
    Thrt=new BiThrNode;
    Thrt->LTag=0;//一开始默认头结点有左孩子 
    Thrt->RTag=1;
    Thrt->rchild=Thrt;//右孩子指针指向自身(构成一个循环) 
    if(!T)
    Thrt->lchild=Thrt;//只有一个头结点时,左孩子指针也指向自身。 
    else
    {
    	Thrt->lchild=T;
    	pre=Thrt;//设定前驱后继 
    	InThreading(T);
    	pre->rchild=Thrt;//跳出线索化函数时,pre指向最后的叶子结点 
    	pre->RTag=1; 
    	Thrt->rchild=pre;//将头结点的右孩子接过来,形成一个闭环 
	}


    /***************************End**********************************/
} 								

我们需要注意的是Thrt作为头结点的使用,原因有二,其一是在线索化过程中,需要pre与p,那么对第一个根节点进行线索化时,谁作为pre呢?其二是增加了头结点后,整个线索链表就变得结构清晰起来。在最后,Thrt的左指针指向根节点,右指针指向最后的叶子结点,构成了一个循环。

在这里面,因为pre是实时更新的,随着InThreading函数的进行不断迭代,所以我们定义的是全局变量,初始为Thrt。

执行完InThreading函数后,我们需要收尾,收尾就是指将整个链表首尾连接,形成循环结构。此刻的pre已经指向了叶子结点,它的右孩子指针指向了后继就是Thrt,并且我们把Thrt的右孩子指针改变指向pre(最开始初始化为指向自身)。

至此,一个结构鲜明清晰的线索化后的二叉树链表就此产生,在结构上就像之前展示的图片一样。

void InOrderTraverse_Thr(BiThrTree T)
{ 
    //中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
	/*******************************Begin***************************/
	BiThrTree p;
    p=T->lchild;//T指向头结点,头结点的左链lchild指向根结点
    while(p!=T)//退出条件是p再次回到头结点 
    {
    	while(p->LTag==0)//沿着根节点从左子树一直往下深入 
    	p=p->lchild;
    	cout<<p->data;//找到第一个结点输出 
    	while(p->RTag==1 &&p->rchild!=T)
		//转向右子树,终止时指向最后一个结点
		//只对那些没有右孩子的结点执行此步操作,若有右孩子跳出 
    	{
    		p=p->rchild;
    		cout<<p->data;
		}
		p=p->rchild;//有右孩子时直接访问右孩子 
    	
	}

第三步,给出遍历线索化二叉树的函数。

这个参考注释,解释的已经很清楚了,就是先找到第一个结点(不断深入左子树),再转向右子树(Tag为1往后继走,否则继续深入右子树),直到走到最后一个结点,遍历结束。

在这里贴出完整的代码,大家可以进行用例测试进行理解。

//算法5.9 遍历中序线索二叉树
#include<iostream>
using namespace std;

//二叉树的二叉线索类型存储表示
typedef struct BiThrNode
{				
	char data;							//结点数据域
	struct BiThrNode *lchild,*rchild;	//左右孩子指针
	int LTag,RTag;
}BiThrNode,*BiThrTree;

//全局变量pre
BiThrNode *pre=new BiThrNode;

//用算法5.3建立二叉链表
void CreateBiTree(BiThrTree &T)
{	
	//按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树T
	char ch;
	cin >> ch;
	if(ch=='#')  T=NULL;			//递归结束,建空树
	else
	{							
		T=new BiThrNode;
		T->data=ch;					//生成根结点
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}								//else
}									//CreateBiTree

//用算法5.7以结点P为根的子树中序线索化
void InThreading(BiThrTree p)
{
	/***************************Begin*************************/
    if(p)
    {
    	InThreading(p->lchild);
    	if(!p->lchild)
    	{
    		p->LTag=1;
    		p->lchild=pre;
		}
		else
		p->LTag=0;
		if(!pre->rchild)
		{
			pre->RTag=1;
			pre->rchild=p;
		}
		else if(pre->rchild!=pre)//这是根节点的特征 
		pre->RTag=0;
		pre=p;
		InThreading(p->rchild);
	}
    /***************************End***************************/
    
}//InThreading

//用算法5.8带头结点的中序线索化
void InOrderThreading (BiThrTree &Thrt,BiThrTree T)
{
	/*****************************Begin*********************************/
    Thrt=new BiThrNode;
    Thrt->LTag=0;//一开始默认头结点有左孩子 
    Thrt->RTag=1;
    Thrt->rchild=Thrt;//右孩子指针指向自身(构成一个循环) 
    if(!T)
    Thrt->lchild=Thrt;//只有一个头结点时,左孩子指针也指向自身。 
    else
    {
    	Thrt->lchild=T;
    	pre=Thrt;//设定前驱后继 
    	InThreading(T);
    	pre->rchild=Thrt;//跳出线索化函数时,pre指向最后的叶子结点 
    	pre->RTag=1; 
    	Thrt->rchild=pre;//将头结点的右孩子接过来,形成一个闭环 
	}


    /***************************End**********************************/
} 										//InOrderThreading

void InOrderTraverse_Thr(BiThrTree T)
{ 
    //中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
	/*******************************Begin***************************/
	BiThrTree p;
    p=T->lchild;//T指向头结点,头结点的左链lchild指向根结点
    while(p!=T)//退出条件是p再次回到头结点 
    {
    	while(p->LTag==0)//沿着根节点从左子树一直往下深入 
    	p=p->lchild;
    	cout<<p->data;//找到第一个结点输出 
    	while(p->RTag==1 &&p->rchild!=T)
		//转向右子树,终止时指向最后一个结点
		//只对那些没有右孩子的结点执行此步操作,若有右孩子跳出 
    	{
    		p=p->rchild;
    		cout<<p->data;
		}
		p=p->rchild;//有右孩子时直接访问右孩子 
    	
	}

    /*******************************End*****************************/
}													//InOrderTraverse_Thr

int main()
{
	pre->RTag=1;
	pre->rchild=NULL;
	BiThrTree tree,Thrt;
	
	CreateBiTree(tree);
	InOrderThreading(Thrt,tree);
	InOrderTraverse_Thr(Thrt);
    
	cout<<endl;
	return 0;
}

贴出一张运行样例。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

向前进吧

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

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

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

打赏作者

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

抵扣说明:

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

余额充值