C语言-线索化二叉树(中序)

  1. 二叉树的创建及初始化
  2. 线索化
  3. Next函数(根据线索找后继)
  4. Prior函数(根据线索找前驱)
  5. 非递归中序遍历(根据线索遍历)

为什么要将二叉树线索化?

  • 首先就是我们在建立二叉树的时候提到的存储空间浪费的问题,建立n个结点的二叉树,总共有2n个指针域(左右孩子),被浪费的足足有n+1的节点是空节点,我们怎么能利用上这些空间呢?
  • 还有一个问题是,在创建二叉树的时候,大部分算法都是用递归实现,实现起来简单,然而系统运行起来却很吃力,很占用系统资源。当我们在建立好二叉树之后我们需要频繁的查找某个单元,就得频繁的调用遍历递归去查找,这显然并不高效,那么我们如何去优化这个问题呢?
  • 结合这两个问题,我们的解决办法是:用这些空指针域,存放一些有用的指向信息,从而尽量的将一棵二叉树线性化成双向链表,我们把这些指向信息称之为线索也就是说线索二叉树等效成双向链表
  • 那么我们在将一颗二叉树线索化了之后,就可以根据线索来找到每一个结点,就不必调用递归算法,大大提升了效率,同时还能高效的利用了别浪费的空指针。
    在这里插入图片描述

如何实现线索化?

1. 结点的定义:
  • 为了实现线索化,我们将原来的结点空间由三个域扩充到五个域,多了左、右标志(ltag,rtag)。
    在这里插入图片描述
  • 定义方法:
typedef struct node
{
	dataType data; //根节点的值 
	struct node *lchild; //左孩子 
	struct node *rchild; //右孩子 
	int ltag; //左标记
	int rtag; //右标记
}BiTree; 
2. 创建二叉树并初始化:
BiTree *creat() //二叉树的创建及初始化(初始化左右标记为0) 
{
	dataType value;
	BiTree *t;
	
	scanf("%c",&value);
	
	if(value=='#')
	{
		t=NULL;
	}
	else
	{
		t=(BiTree *)malloc(sizeof(BiTree));
		t->data=value;
		t->ltag=0;//初始化左标记为0 
		t->rtag=0;//初始化右标记为0 
		t->lchild=creat();
		t->rchild=creat();
	}
	return t;
}
  • 与二叉树的创建方法一致,只需要加入对左、右标志初始化为0。
3. 加入线索:
  • 利用两个指针p和pre,让其在中序遍历的过程中分别指向一前一后,也就是说始终保持p是pre的后继,pre是p的前驱;那么在处理到p结点的时候,我们可以根据p节点是否有左孩子来判断,p->lchild是否要指向后继,若p指向的节点没有左孩子,则直接让p->lchild=pre;并将p->ltag赋值为1,即可完成将结点的左孩子指向前驱结点。pre也一个道理,若pre所指向的节点没有右孩子,将pre->rchild=p;并即将pre->rtag赋值为1,即可完成该结点的右孩子指向后继节点
  • 整体思路就是:通过p来处理左孩子指向前驱(左孩子为空的情况下),通过pre来处理右孩子指向后继(右孩子为空的情况下)。
pre如何定义?
  • pre与p指针功能相同,但定义却不相同,p是线索化函数内的局部变量,而pre为了保证在每一次递归序列中能够正确返回当前值,必须定义成: ①全局变量 ②静态变量
  • 因为我们是以中序遍历的顺序给二叉树填上线索,所以,整个线索化的函数是套用在中序遍历的框架下的。
注意:
  • if(pre&&pre->rchild) 中,判断pre不为空的条件一定不能少,否则第一个结点pre指向空到此处会出错!
    (<数据结构C语言第二版(严蔚敏,吴伟民)> 中算法5.7 没有判断pre不空是因为有设置头结点,pre一开始就指向头结点,也一直不为空,所以不需要)
5. Next与Prior函数
  • Next函数是已知结点tt的后继节点。若rtag的的值为1,则说明rchild指向后继结点,那我们即可直接找到;如果rtag的值为0,则不能直接找到,此时需要查找到右子树下的最左端的结点。
    在这里插入图片描述
  • Prior函数也是一个道理,已知结点tt的前驱节点,若ltag的的值为1,则说明lchild指向前驱结点,那我们即可直接找到;如果ltag的值为0,则不能直接找到,此时需要查找到左子树下的最右端的结点。
    在这里插入图片描述
6.根据线索实现遍历
  • 有了线索之后,我们就可以像链表一样找到每一个结点,实现遍历(中序线索化,中序遍历)。
  • 但是需要注意的一点是,我们creat函数中输入是以先序的次序输入端,故p一开始指向的节点是先序的第一个节点(a),而不是中序的第一个结点(d),所以要先查找第一个节点,在用Next中的线索查找后续结点。

完整源代码:

#include <stdio.h>
#include <stdlib.h>
 
typedef char dataType;
typedef struct node
{
	dataType data; //根节点的值 
	struct node *lchild; //左孩子 
	struct node *rchild; //右孩子 
	int ltag; //左标记,“ltag=0”表示当前节点有左孩子,“ltag=1”表示当前节点没有左孩子 
	int rtag; //右标记,“rtag=0”表示当前节点有右孩子,“rtag=1”表示当前节点没有右孩子 
}BiTree; 

BiTree *creat() //二叉树的创建及初始化(初始化左右标记为0) 
{
	dataType value;
	BiTree *t;
	
	scanf("%c",&value);
	
	if(value=='#')
	{
		t=NULL;
	}
	else
	{
		t=(BiTree *)malloc(sizeof(BiTree));
		t->data=value;
		t->ltag=0;//初始化左标记为0 
		t->rtag=0;//初始化右标记为0 
		t->lchild=creat();
		t->rchild=creat();
	}
	return t;
}

//BiTree *pre=NULL; //1.定义全局变量pre
void InThreaded(BiTree *p)
{
	static BiTree *pre=NULL;//2.定义静态变量 	 
	if(p)
	{
		InThreaded(p->lchild);
		
		if(!p->lchild)
		{
			p->ltag=1;
			p->lchild=pre;
		}
		
		if(pre&&!pre->rchild)
		{
			pre->rtag=1;
			pre->rchild=p;
		}
		pre=p;
		
		InThreaded(p->rchild); 
	}
}

BiTree *Next(BiTree *t) //已知节点t找t的"后继"结点位置 
{   
	if(t->rtag==1) //右标志为1,可以直接得到"后继"结点 
	{
		t=t->rchild;
	 } 
	 else /*右标志为0,不能直接的到"后继"结点,
	        则需要找到右子树最左下角的节点*/ 
	{
	 	t=t->rchild;
		 while(t->ltag==0)
		{
		 	t=t->lchild;
		 } //while
	}//else
	return t;
}

BiTree *Prior(BiTree *t)//已知节点t找t的"前驱"结点位置 
{
	if(t->ltag==1)//左标志为1,可以直接找到"前驱"结点的位置 
	{
		t=t->lchild;
	 }
	else /*右标志为0,不能直接的到"前驱"结点,
	       则需要找到左子树最右下角的节点*/ 
	{
		t=t->lchild;
		while(t->rtag==0)
		{
			t=t->rchild;
		 } //while
	} //else
	
	return t;
}

void InorderTraverse(BiTree *t)//利用线索实现中序遍历 
{
	if(!t)
	{
		return;
	}
	
	while(t->ltag==0)//查找第一个节点 
	{               //因为二叉树的创建creat是以先序遍历序列创建,所以t所指向的第一个结点并不是中序遍历所要访问的第一个结点 
		t=t->lchild;
	}
	printf("%c ",t->data);//访问第一个结 
	while(t->rchild)// 此处以"t的右孩子不为空"为循环条件,是因为,先前设定了最后一个结点的"后继"为空,表示结束 
	{               //根据线索访问后续结点 
		t=Next(t);
		printf("%c ",t->data); 
	}
} 

int main() 
{
	BiTree *root;

    printf("Input:"); 
	root=creat();
	printf("\n");

	printf("Threading Binary Tree!\n");
	InThreaded(root);
	printf("\n");
	
	printf("Inorder traverse:");
	InorderTraverse(root);
	printf("\n");
	
	
	return 0;
}

执行结果:

666

补充:

  1. pre的定义问题:除了 ①全局变量 ②静态变量;你可能还会想到用以下方法定义:
void InThread(BiTree *p,BiTree *pre)
{
	if(p!=NULL)
	{
		InThread(p->lchild,pre);
		if(p->lchild==NULL)
		{
			p->lchild=pre;
			p->ltag=1;
		}
		
		if(pre!=NULL&&pre->rchild==NULL)
		{
			pre->rchild=p;
			pre->rtag=1;
		}
		pre=p;
		InThread(p->rchild,pre);
    } 
}//错误算法!! 

或者是这样:

void InThread(BiTree *p,BiTree *pre)
{
	if(p!=NULL)
	{
		InThread(p->lchild,p);	
		if(p->lchild==NULL)
		{
			p->lchild=pre;
			p->ltag=1;
	    }
		
		if(pre!=NULL&&pre->rchild==NULL)
		{
			pre->rchild=p;
			pre->rtag=1;
		}
		InThread(p->rchild,p);
	} 
}//错误算法!! 

以上两种算法均错误!在递归的过程中均不能正常运行!


参考:

  1. 数据结构C语言第二版(严蔚敏,吴伟民)
  2. 懒猫老师
  • 20
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
线索化二叉树是指在二叉树的遍历中,将遍历到的每个节点的前驱和后继节点指针指向它们在遍历顺序中的前一个节点和后一个节点。这样可以提高遍历效率,同时也可以方便地对二叉树进行查找、删除等操作。 在C语言中,我们可以使用结构体来表示二叉树节点,结构体包含三个成员:data表示节点数据,lchild表示左子节点指针,rchild表示右子节点指针。除此之外,我们还可以添加两个标志位:ltag表示左子节点指针是指向左子节点还是指向前驱节点,rtag表示右子节点指针是指向右子节点还是指向后继节点。 序线索化二叉树的实现可以通过递归遍历二叉树来完成。具体步骤如下: 1.定义两个全局变量prev和head,分别表示当前节点的前驱节点和序线索化后的头节点。 2.定义一个函数threading,用来递归遍历二叉树,并将节点进行序线索化。 3.在函数threading中,先递归遍历左子树,如果当前节点的左子节点为空,则将其左子节点指针指向prev,并将ltag设置为1,表示指向前驱节点。 4.如果当前节点的前驱节点为空,则将head指向当前节点,表示序线索化后的头节点。 5.如果当前节点的前驱节点不为空,则将其前驱节点的右子节点指针指向当前节点,并将rtag设置为1,表示指向后继节点。 6.最后,将prev指向当前节点,继续递归遍历右子树。 下面是一个简单的序线索化二叉树C语言实现: ```c #include <stdio.h> #include <stdlib.h> //定义二叉树节点结构体 typedef struct Node { int data; struct Node *lchild, *rchild; int ltag, rtag; } Node; //定义全局变量prev和head Node *prev = NULL; Node *head = NULL; //递归遍历二叉树并进行序线索化 void threading(Node *p) { if (p) { threading(p->lchild); //递归遍历左子树 if (!p->lchild) { //左子节点为空,指向前驱节点 p->ltag = 1; p->lchild = prev; } if (prev && !prev->rchild) { //前驱节点的右子节点为空,指向后继节点 prev->rtag = 1; prev->rchild = p; } if (!head) { //前驱节点为空,当前节点为头节点 head = p; } prev = p; //将prev指向当前节点 threading(p->rchild); //递归遍历右子树 } } //创建二叉树 Node *createTree() { Node *p; int data; scanf("%d", &data); if (data == -1) { p = NULL; } else { p = (Node *)malloc(sizeof(Node)); p->data = data; p->ltag = p->rtag = 0; p->lchild = createTree(); p->rchild = createTree(); } return p; } //中序遍历序线索化二叉树 void inorderTraversal(Node *p) { Node *q = p; while (q) { while (q->ltag == 0) { //找到第一个左子节点为空的节点 q = q->lchild; } printf("%d ", q->data); //输出节点数据 while (q->rtag == 1) { //如果右子节点是后继节点,继续输出 q = q->rchild; printf("%d ", q->data); } q = q->rchild; //否则,继续遍历右子树 } } int main() { Node *root = createTree(); //创建二叉树 threading(root); //进行序线索化 inorderTraversal(head); //中序遍历序线索化二叉树 return 0; } ``` 注意,以上代码仅仅是序线索化二叉树的一个简单实现,实际使用中还需要考虑线索化的正确性和效率等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Attract1206

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

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

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

打赏作者

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

抵扣说明:

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

余额充值