数据结构——线索化二叉树

这里主要是想写一下自己对于线索化过程中的递归理解

在讲解之前,先介绍几个概念
  • 前驱,通俗的讲,就是在某种遍历条件下,A在B前面输出,所以,B的前驱就是A。
  • 后继,同样的道理,A的后继也就是B了。(这里一定要理解清楚,因为后面程序的思路就是根据这里来写出的)
  • 头节点,这里可以类比链表中的头节点,可以说是与树中的数据没有什么直接的联系,即用户是不知道有这么一个节点存在的。但是,为了方便后面代码的实现,我们在这里还是引入了这个概念。
  • pre,这个是代码中指定的全局指针变量,始终指向刚刚访问过的节点。什么意思捏,就是说,p若刚刚访问过A,然后p就离开了,(这里的离开,指的是去访问别的节点了。若不懂没有关系,之后我们对着代码来讲。)那么,pre在p离开之前,pre也会指向A,然后p离开。注意,这里不是说是在函数内部,不会随着递归函数的进行,而被赋予新值。

进入主题啦,加油!

  1. 线索化二叉树。(至于,这里怎么定义二叉树的类型,我就不多说了,可以看书本上的定义即可)首先看一下这部分代码:

     void InOrderThreading(BiThrTree& Thrt, BiThrTree T)
     {
     	Thrt = (BiThrTree)malloc(sizeof(BiThrNode));
     	if (Thrt == NULL)
     	{
     		printf("内存分配失败\n");
     		exit(-1);
     	}
     	Thrt->data = -1;//头指针数据域为无效值-1
     	Thrt->RTag = Thread;//头指针有其后继
     	Thrt->rchild = Thrt;//暂时令其后继为自己
     	Thrt->LTag = Link;//头指针没有前驱
     	if (!T)//如果T为空树时,令其左指针指向自己
     	{
     		Thrt->lchild = Thrt;
     	}
     	else
     	{
     		Thrt->lchild = T;
     		pre = Thrt;
     		InThreading(T);
     		//线索化最后一个节点
     		pre->rchild = Thrt;
     		pre->RTag = Thread;
     		Thrt->rchild = pre;
     	}
     	
     }
     //end**
     
     //start**
     void InThreading(BiThrTree P)
     {
     	if (P)
     	{
     		InThreading(P->lchild);//先线索化左子树
     		/*
     			如果满足该if语句,说明已经到了某个子树的左边的尽头
     		*/
     		if (!P->lchild)
     		{
     			P->LTag = Thread;
     			P->lchild = pre;
     		}
     		/*
     			不管上面if语句是否满足,相对P来说,其都是有一个前驱元素X存在的
     			从另一方面来说,就是X的后继就是P
     			而pre的定义就是P刚刚访问后的节点,对于中序遍历来说,pre就是P的前驱
     			所以,下一步应该计算pre的后继元素
     		*/
     		if (!pre->rchild)
     		{
     			pre->RTag = Thread;
     			pre->rchild = P;
     		}
     		pre = P;//因为P马上就要==P->rchild了,在此之前,用pre来来保存P访问节点
     		InThreading(P->rchild);//再线索化左子树
     	}
     }
     //end**
    

    我认为难点就在 void InThreading(BiThrTree P)函数中,他是怎么就写出来的,我最近也一直在“研究”递归,被弄得云里雾里的。但是,我还是在这里写一哈自己的想法,一是为了方便之后的复习,二是希望万能的网友能和我一起交流这个问题,共同进步。

  2. 首先明白一点,我在这里实现的是中序遍历的线索化,所以,第一个要设置为Thread的节点必然是最左边的那个节点,即P->lchild==NULL。对应的代码就是if§这个判断语句,因为只有当该节点不为NULL时才可以继续走下去。InThreading(P->lchild);这条语句明显是一个递归语句,那么什么时候才能执行下面的语句捏。根据递归的定义可知,只有当最后一个InThreading(P->lchild)执行完毕后,就会返回到上一个InThreading(P->lchild)这里,然后接着往下执行。

  3. 现在的p是什么捏?是根的左子树中第一个没有左孩子的节点。所以,这个是我们要线索化的节点。自然的就会去写

    if (!P->lchild)
             		{
             			P->LTag = Thread;
             			P->lchild = pre;
             		}
     // 这个代码。因为pre的定义可知,所以p->lchild应该赋值为pre。
    
  4. 那么,你找了p是否会有前驱(因为在之后的递归中,可能不满足if(!P->lchild)这个条件),那么自然你现在得去找pre的后继。这里就得用到我之前所讲的那个后继这个概念了。

  5. 所以现在去找pre的后继。执行代码

     if (!pre->rchild)
    		{
    			pre->RTag = Thread;
    			pre->rchild = P;
    		}
    
  6. 后继线索也寻找完毕后,那么就要去看p有没有右孩子了。但是,在此之前,根据pre的定义可知,pre要始终指向p之前访问过的节点。所以,在p=p->rchild之前,先执行

    pre=p;
    

    然后执行

     InThreading(P->rchild);
    

那么,这样这个 void InThreading(BiThree p)函数分析完毕。
可能还不是特别清楚,我现在拿一颗树来做个演示。下面还有我的实例代码,可以参考一下,对着这个图分析一下。(好吧,这个图画的太丑了,大家还是尽力理解吧,可以自己尝试画图,这样才可以深入理解递归)
这个是第一张图片

这个是第二张图片

这个是完整代码,可能对着分析一下

	//start**
void InOrderTraverse_Thr(BiThrTree T)
{
	BiThrTree P = T->lchild;//P指向根节点
	while (T != P)
	{
		while (P->LTag == Link)
		{
			P = P->lchild;//中序遍历的老套路
		}
		printf("%5d", P->data);//所以此时该输出数据了
		/*
		进入这个while循环,代表该节点左边没有子树了,
		右边也没有子树了,并且不是中序遍历的最后一个节点
		因为最后一个节点的rchild指向头指针
		*/
		while (P->RTag == Thread && P->rchild != T)
		{
			P = P->rchild;
			printf("%5d", P->data);
		}
		P = P->rchild;
	}

}
//end**


//start**
void InOrderThreading(BiThrTree& Thrt, BiThrTree T)
{
	Thrt = (BiThrTree)malloc(sizeof(BiThrNode));
	if (Thrt == NULL)
	{
		printf("内存分配失败\n");
		exit(-1);
	}
	Thrt->data = -1;//头指针数据域为无效值-1
	Thrt->RTag = Thread;//头指针有其后继
	Thrt->rchild = Thrt;//暂时令其后继为自己
	Thrt->LTag = Link;//头指针没有前驱
	if (!T)//如果T为空树时,令其左指针指向自己
	{
		Thrt->lchild = Thrt;
	}
	else
	{
		Thrt->lchild = T;
		pre = Thrt;
		InThreading(T);
		//线索化最后一个节点
		pre->rchild = Thrt;
		pre->RTag = Thread;
		Thrt->rchild = pre;
	}
	
}
//end**

//start**
void InThreading(BiThrTree P)
{
	if (P)
	{
		InThreading(P->lchild);//先线索化左子树
		/*
			如果满足该if语句,说明已经到了某个子树的左边的尽头
		*/
		if (!P->lchild)
		{
			P->LTag = Thread;
			P->lchild = pre;
		}
		/*
			不管上面if语句是否满足,相对P来说,其都是有一个前驱元素X存在的
			从另一方面来说,就是X的后继就是P
			而pre的定义就是P刚刚访问后的节点,对于中序遍历来说,pre就是P的前驱
			所以,下一步应该计算pre的后继元素
		*/
		if (!pre->rchild)
		{
			pre->RTag = Thread;
			pre->rchild = P;
		}
		pre = P;//因为P马上就要==P->rchild了,在此之前,用pre来来保存P访问节点
		InThreading(P->rchild);//再线索化左子树
	}
}
//end**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值