二叉树的二叉线索存储(第六章 P132 算法6.5-6.7)

产生背景

 

为了更方便、快捷地遍历二叉树,最好在二叉树的结点上增加 2 个指针,它们分别指向遍历二叉树时该结点的前驱和后继结点。这样,从二叉树的任一结点都可以方便地找到其他结点。但这样做大大降低了结构的存储密度。

另一方面,如下图,现有一棵结点数目为n的二叉树,采用二叉链表的形式存储。对于每个结点均有指向左右孩子的两个指针域,而结点为n的二叉树一共有n-1条有效分支路径。那么,则二叉链表中存在2n-(n-1)=n+1个空指针域。那么,这些空指针造成了空间浪费。

如果能利用这 n+1 个空指针,使它们指向结点的前驱(当左孩子指针空)或后继(当右孩子指针空),则既可不降低结构的存储密度,又可更方便、快捷地遍历二叉树。

不过,这样就无法区别左右孩子指针所指的到底是结点的左右孩子,还是结点的前驱后继了。为了有所区别,另增加 2 个域  LTag RTag 。当所指的是孩子,其值是 0 (Link);当所指的是前驱后继,其值为 1(Thread)。这样做,结构的存储密度也有所降低,但不大。因为 LTag 和 RTag 分别只需要 1 个比特(二进制位)即可。

 

 

 

构造线索二叉树

 

构造线索二叉树的方法和构造以二叉链表存储的二叉树方法相似,都是按先序输入结点的值来构造二叉树的。
它们的区别有两点:
  • (1) 二叉树结点的结构不同;
  • (2) 构造线索二叉树时,若有左右孩子结点,还要给左右标志赋值 0(Link)
 
上图所示二叉树调用构造线索二叉树的方法函数产生的二叉树结构如右图所示。和调用以二叉链表存储的二叉树的 CreateBiTree() 函数产生的二叉树结构 ( 见左图 ) 相比,前者只是多了 LTag 和 RTag 两个域。并且当其相应孩子指针不空时,赋值 0
 
   
 
 
注意:调用构造线索二叉树的方法(即 CreateBiThrTree () 函数),只是构造了一棵可以线索化的二叉树,还没有完成线索化。
 

 

 

 

线索化

 

因为对于一棵给定的二叉树,其先序、中序、后序和层序遍历的顺序是不同的。显然,其线索化的操作和遍历的操作也是不同的。

 

中序线索二叉树

 
下图 是上图 所示二叉树的中序线索二叉树存储结构示例。和二叉链表 存储结构相比,
  • 第一,它多了一个头结点。其左孩子指针指向根结点,右孩子指针 (线索)指向中序遍历所访问的最后一个结点。
  • 第二,它每个结点的左右孩子指针都不是空指针。在没有孩子的情况下,分别指向该结点中序遍历的前驱或后继。
  • 第三,中序遍历的第 1 个结点(最左边的结点,它没有左孩子,本例是结点 3)的左孩子指针(线索)和最后 1 个结点(最右边的结点,它没有右孩子,本例是结点 1)的右孩子指针(线索)都指向头结点。其目的是标志遍历的起点和终点。
 
中序线索二叉树:
 

 

下面程序中的 InThreading()函数和 InOrderThreading()函数 共同完成了对二叉树的中序遍历线索化。其算法是:设置全局指针变量 pre( 之 所 以 设 为 全 局 变 量 , 是 因 为 在 递 归 函 数 InThreading()InOrderThreading()中都要用到,设为全局变量就不必频繁传递变量的值),令 pre 总是指向遍历的前驱结点,p 指向当前结点;在中序遍历过程中,如果 p 所指结点没有左孩子,则结点的左孩子指针指向 pre 所指结点,结点的 LTag 域的值为 1(Thread);如果 pre 所指结点没有右孩子,则结点的右孩子指针指向 p,结点的 RTag 域的值为 1(Thread)

 

对于上图所示的中序线索二叉树,我们能不能在找到遍历的第 1 个结点后,顺着右孩子指针一直找到遍历的最后 1 个结点呢?这是不一定的。因为结点的右孩子指针并不 一定指向后继结点,它可能指向右孩子,而右孩子并不一定恰好是后继结点。 程序中的 InOrderTraverse_Thr()函数完成了对中序线索二叉树的中序遍历操作。其算法是:当树不空时,由树根向左找,一直找到没有左孩子的结点(最左结点)。这就是中序遍历的第 1 个结点。若该结点没有右孩子,则右孩子指针指向其后继结点;否则,以其右孩子为子树的根,向左找,一直找到没有左孩子的结点。这就是后继结点。当结点的右孩子指针指向头结点,遍历结束。

 

 

先序线索二叉树

 

下图是上图所示二叉树的先序线索二叉树存储结构示例。程序中的先序线索化的递归函数 PreThreading()与中序线索化的递归函数 InThreading()很相像,都是利用递归进行线索化,只不过顺序不同。但由于 PreThreading()是先序线索化,所以判断 结点是否有左右孩子就不能由其左右孩子指针是否为空决定,而要由结点的 LTag RTag 域是否为 0(Link)来决定。

 
对于先序线索化二叉树的先序遍历算法是这样的:根结点是遍历的第 1 个结点;如果结点有左孩子,则左孩子是其后继;若结点没有左孩子,则右孩子指针所指的结点是其后 继( 无论该结点有没有右孩子 )
 
 
 
先序线索二叉树:
 

 

 

 
void PreThreading(BiThrTree p)
{ // PreOrderThreading()调用的递归函数
	if(!pre->rchild) // p的前驱没有右孩子
	{
		pre->rchild=p; // p前驱的后继指向p
		pre->RTag=Thread; // pre的右孩子为线索
	}
	if(!p->lchild) // p没有左孩子
	{
		p->LTag=Thread; // p的左孩子为线索
		p->lchild=pre; // p的左孩子指向前驱
	}
	pre=p; // 移动前驱
	if(p->LTag==Link) // p有左孩子
		PreThreading(p->lchild); // 对p的左孩子递归调用preThreading()
	if(p->RTag==Link) // p有右孩子
		PreThreading(p->rchild); // 对p的右孩子递归调用preThreading()
}
void PreOrderThreading(BiThrTree &Thrt,BiThrTree T)
{ // 先序线索化二叉树T,头结点的右指针指向先序遍历的最后1个结点
	if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点
		exit(OVERFLOW);
	Thrt->LTag=Link; // 头结点的左指针为孩子
	Thrt->RTag=Thread; // 头结点的右指针为线索
	Thrt->rchild=Thrt; // 头结点的右指针指向自身
	if(!T) // 空树
		Thrt->lchild=Thrt; // 头结点的左指针也指向自身
	else
	{ // 非空树(见图621)
		Thrt->lchild=T; // 头结点的左指针指向根结点
		pre=Thrt; // 前驱为头结点
		PreThreading(T); // 从头结点开始先序递归线索化
		pre->rchild=Thrt; // 最后一个结点的后继指向头结点
		pre->RTag=Thread;
		Thrt->rchild=pre; // 头结点的后继指向最后一个结点
	}
}
void PreOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType))
{ // 先序遍历线索二叉树T(头结点)的非递归算法
	BiThrTree p=T->lchild; // p指向根结点
	while(p!=T) // p没指向头结点(遍历的最后1个结点的后继指向头结点) 
	{
		Visit(p->data); // 访问根结点
		if(p->LTag==Link) // p有左孩子
			p=p->lchild; // p指向左孩子(后继)
		else // p无左孩子
			p=p->rchild; // p指向右孩子或后继
	} 
}

 

 

 

后序线索二叉树

 

程序中的后序线索化的递归函数 PostThreading()与中序线索化的递归函数 InThreading()也很相像,也是利用递归进行线索化,也只是顺序不同。

下图是上图所示二叉树的后序线索二叉树存储结构示例。对于后序线索化二叉树的后序遍历算法较复杂。因为根结点是在最后遍历,所以要采用带有双亲指针的三叉链表结构才行。下面没有给出它的程序。

 

 

 
后序线索二叉树:
 
 
 
 
 
void PostThreading(BiThrTree p)
{ // PostOrderThreading()调用的递归函数
	if(p) // p不空
	{
		PostThreading(p->lchild); // 对p的左孩子递归调用PostThreading()
		PostThreading(p->rchild); // 对p的右孩子递归调用PostThreading()
		if(!p->lchild) // p没有左孩子
		{
			p->LTag=Thread; // p的左孩子为线索
			p->lchild=pre; // p的左孩子指向前驱
		}
		if(!pre->rchild) // p的前驱没有右孩子
		{
			pre->RTag=Thread; // p前驱的右孩子为线索
			pre->rchild=p; // p前驱的后继指向p 
		}
		pre=p; // 移动前驱
	} 
}
void PostOrderThreading(BiThrTree &Thrt,BiThrTree T)
{ // 后序递归线索化二叉树
	if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点
		exit(OVERFLOW);
	Thrt->LTag=Link; // 头结点的左指针为孩子
	Thrt->RTag=Thread; // 头结点的右指针为线索
	if(!T) // 空树
		Thrt->lchild=Thrt->rchild=Thrt; // 头结点的左右指针指向自身
	else
	{ // 非空树
		Thrt->lchild=Thrt->rchild=T; // 头结点的左右指针指向根结点(最后一个结点)
		pre=Thrt; // 前驱为头结点
		PostThreading(T); // 从头结点开始后序递归线索化
		if(pre->RTag!=Link) // 最后一个结点没有右孩子
		{
			pre->rchild=Thrt; // 最后一个结点的后继指向头结点
			pre->RTag=Thread;
		} 
	}
}

 

 

 

代码

 
二叉树的二叉线索存储结构:
 
 
空线索二叉树:
 
 
 
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */

/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2 


#define CHAR 1 /* 字符型 */
//#define CHAR 0 /* 整型(二者选一) */
#if CHAR
typedef char TElemType;
TElemType Nil = ' '; /* 字符型以空格符为空 */
#else
typedef int TElemType;
TElemType Nil = 0; /* 整型以0为空 */
#endif


/* ---------------------------------  二叉树的二叉线索存储表示    --------------------------------*/

typedef enum { Link, Thread }PointerTag; /* Link(0):指针,Thread(1):线索 */
typedef struct BiThrNode
{
	TElemType data;
	struct BiThrNode *lchild, *rchild; /* 左右孩子指针 */
	PointerTag LTag, RTag; /* 左右标志 */
}BiThrNode, *BiThrTree;

/* ---------------------------------------------------------------------------------------------*/


/* --------------------------------     二叉树的二叉线索存储的基本操作    -------------------------------*/


Status CreateBiThrTree(BiThrTree *T)
{ /* 按先序输入二叉线索树中结点的值,构造二叉线索树T */
  /* 0(整型)/空格(字符型)表示空结点 */
	TElemType h;
#if CHAR
	scanf("%c", &h);
#else
	scanf("%d", &h);
#endif
	if (h == Nil)
		*T = NULL;
	else
	{
		*T = (BiThrTree)malloc(sizeof(BiThrNode));
		if (!*T)
			exit(OVERFLOW);
		(*T)->data = h; /* 生成根结点(先序) */
		CreateBiThrTree(&(*T)->lchild); /* 递归构造左子树 */
		if ((*T)->lchild) /* 有左孩子 */
			(*T)->LTag = Link;
		CreateBiThrTree(&(*T)->rchild); /* 递归构造右子树 */
		if ((*T)->rchild) /* 有右孩子 */
			(*T)->RTag = Link;
	}
	return OK;
}

BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */
void InThreading(BiThrTree p)
{ /* 中序遍历进行中序线索化。算法6.7 */
	if (p)
	{
		InThreading(p->lchild); /* 递归左子树线索化 */
		if (!p->lchild) /* 没有左孩子 */
		{
			p->LTag = Thread; /* 前驱线索 */
			p->lchild = pre; /* 左孩子指针指向前驱 */
		}
		if (!pre->rchild) /* 前驱没有右孩子 */
		{
			pre->RTag = Thread; /* 后继线索 */
			pre->rchild = p; /* 前驱右孩子指针指向后继(当前结点p) */
		}
		pre = p; /* 保持pre指向p的前驱 */
		InThreading(p->rchild); /* 递归右子树线索化 */
	}
}

Status InOrderThreading(BiThrTree *Thrt, BiThrTree T)
{ /* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点。算法6.6 */
	*Thrt = (BiThrTree)malloc(sizeof(BiThrNode));
	if (!*Thrt)
		exit(OVERFLOW);
	(*Thrt)->LTag = Link; /* 建头结点 */
	(*Thrt)->RTag = Thread;
	(*Thrt)->rchild = *Thrt; /* 右指针回指 */
	if (!T) /* 若二叉树空,则左指针回指 */
		(*Thrt)->lchild = *Thrt;
	else
	{
		(*Thrt)->lchild = T;
		pre = *Thrt;
		InThreading(T); /* 中序遍历进行中序线索化 */
		pre->rchild = *Thrt;
		pre->RTag = Thread; /* 最后一个结点线索化 */
		(*Thrt)->rchild = pre;
	}
	return OK;
}

Status InOrderTraverse_Thr(BiThrTree T, Status(*Visit)(TElemType))
{ /* 中序遍历二叉线索树T(头结点)的非递归算法。算法6.5 */
	BiThrTree p;
	p = T->lchild; /* p指向根结点 */
	while (p != T)
	{ /* 空树或遍历结束时,p==T */
		while (p->LTag == Link)
			p = p->lchild;
		if (!Visit(p->data)) /* 访问其左子树为空的结点 */
			return ERROR;
		while (p->RTag == Thread && p->rchild != T)
		{
			p = p->rchild;
			Visit(p->data); /* 访问后继结点 */
		}
		p = p->rchild;
	}
	return OK;
}

/* --------------------------------------------------------------------------------------------------*/



Status vi(TElemType c)
{
#if CHAR
	printf("%c ", c);
#else
	printf("%d ", c);
#endif
	return OK;
}

void main()
{
	BiThrTree H, T;
#if CHAR
	printf("请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)\n");
#else
	printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)\n");
#endif
	CreateBiThrTree(&T); /* 按先序产生二叉树 */
	InOrderThreading(&H, T); /* 中序遍历,并中序线索化二叉树 */
	printf("中序遍历(输出)二叉线索树:\n");
	InOrderTraverse_Thr(H, vi); /* 中序遍历(输出)二叉线索树 */
	printf("\n");
}

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值