数据结构学习笔记 --- 线索二叉树

1. 引言


为了更方便、快捷地遍历二叉树,最好在二叉树的结点上增加2个指针,它们分别指向遍历二叉树时该结点的前驱和后继。这

样,从二叉树的任一结点都可以方便的找到其他结点。但这样做大大降低了结构的存储密度。


另外,根据二叉树的性质,有:n0 = n2 + 1。空链域=2n0 + n1(叶子结点有两个空链域,度为1的结点有一个空链域)=n0+n2+1+n1

=n+1。也就是说,在由n个结点组成的二叉树中,有n+1个指针是空指针。如果能利用这n+1个空指针,使它们指向结点的前驱(当左孩子指针空)或后继(当有孩子指针空),则即可以不降低结点的存储密度,又可以方便的遍历二叉树。不过,这样就无法区别左右孩子指针所指的到底是结点的左右孩子,还是结点的前驱后继了。为了有所区别,另增两个域(LTag和RTag)。


2. 线索二叉树

#include "ds.h"

#define CHAR 1 // 字符型
 // #define CHAR 0 // 整型(二者选一)
#if CHAR
   typedef char TElemType;
   TElemType Nil=' '; // 字符型以空格符为空
   #define form "%c" // 输入输出的格式为%c
#else
   typedef int TElemType;
   TElemType Nil=0; // 整型以0为空
   #define form "%d" // 输入输出的格式为%d
#endif
#define DEBUG
#ifdef DEBUG
int 	count = 1;
#endif


enum PointerTag	// 枚举
{
	Link,		// 指针
	Thread		// 线索
};

struct BiThrNode
{
	TElemType 	data;
	BiThrNode	*lchild, *rchild;	// 左右孩子指针
	PointerTag	LTag, RTag;			// 左右标志
};

typedef 	BiThrNode*		BiThrTree;

BiThrTree pre; // 全局变量,始终指向刚刚访问过的结点


void CreateBiThrTree(BiThrTree &T);
void InThreading(BiThrTree p);
void InOrderThreading(BiThrTree &Thrt,BiThrTree T);
void InOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType));

void PreThreading(BiThrTree p);
void PreOrderThreading(BiThrTree &Thrt,BiThrTree T);
void PreOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType));

void PostThreading(BiThrTree p);
void PostOrderThreading(BiThrTree &Thrt,BiThrTree T);
void PostOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType));

void DestroyBiTree(BiThrTree &T);
void DestroyBiThrTree(BiThrTree &Thrt);

// 二叉树的二叉线索存储的基本操作,包括算法

// 按先序输入线索二叉树中结点的值,构造线索二叉树T。0(整型)/空格(字符型)表
void CreateBiThrTree(BiThrTree &T)
{
	TElemType ch;
	scanf(form, &ch);
	
	if (ch == Nil)
	{
		T = NULL;
	}
	else
	{
		T = (BiThrTree)malloc(sizeof(BiThrNode));	// 生成根结点(先序)
		if (!T) exit(OVERFLOW);
		T->data = ch;
#ifdef DEBUG
		printf(form, ch);
		printf("------ %d ------\n", count++);
#endif
		CreateBiThrTree(T->lchild);	// 递归构造左子树
		if (T->lchild)				// 有左孩子
			T->LTag = Link;			// 给左标志赋值(指针)
		CreateBiThrTree(T->rchild);	// 递归构造右子树
		if (T->rchild)				// 有右孩子
			T->RTag = Link;			// 给右标志赋值(指针)
	}
}

// 通过中序遍历进行中序线索化,线索化之后pre指向最后一个结点。算法6.7
void InThreading(BiThrTree p)
{
	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); // 递归右子树线索化
	}
}

// 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点。
void InOrderThreading(BiThrTree &Thrt,BiThrTree T)
{
	if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点不成功
    	exit(OVERFLOW);
    
    Thrt->LTag = Link;		// 建头结点,左标志为指针
    Thrt->RTag = Thread;	// 右标志为线索
    
    Thrt->rchild = Thrt;	// 右指针回指
    if (!T)					// 若二叉树空,则左指针回指
	{
		Thrt->lchild=Thrt;
	}
	else
	{
		Thrt->lchild = T;	// 头结点的左指针指向根结点
		pre = Thrt;			// pre(前驱)的初值指向头结点
		InThreading(T);		// 中序遍历进行中序线索化,pre指向中序遍历的最后一个结点
		pre->rchild = Thrt;	// 最后一个结点的右指针指向头结点
		pre->RTag = Thread;	// 最后一个结点的右标志为线索
		Thrt->rchild = pre;	// 头结点的右指针指向中序遍历的最后一个结点
	}
}

// 中序遍历线索二叉树T(头结点)的非递归算法。算法6.5
void InOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType))
{
	BiThrTree	p;
	p = T->lchild; 		//
	
	// 空树或遍历结束时,p==T
	while (p != T)
	{
		while(p->LTag==Link) 	// 由根结点一直找到二叉树的最左结点
       		p=p->lchild;
		Visit(p->data);			// 访问此结点
		
		while(p->RTag==Thread&&p->rchild!=T) // p->rchild是线索(后继),且不是遍历的最后一个结点
     	{
     		p = p->rchild;
     		Visit(p->data);		// 访问后继结点
     	}
     	p = p->rchild; 			// 若p->rchild不是线索(是右孩子),p指向右孩子,返回循环,
   	}   // 找这棵子树中序遍历的第1个结点
}

// PreOrderThreading()调用的递归函数	
void PreThreading(BiThrTree p)
{
	if (p)	// 非空树
	{
		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()
	}
}

// 先序线索化二叉树T,头结点的右指针指向先序遍历的最后1个结点
void PreOrderThreading(BiThrTree &Thrt,BiThrTree T)
{
	if (!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))	// 生成头结点
		exit(OVERFLOW);
	
	Thrt->LTag = Link;		// 头结点的左指针为孩子
	Thrt->RTag = Thread;	// 头结点的右指针为线索
	Thrt->rchild = Thrt;	// 头结点的右指针指向自身
	
	if (!T)	// 空树
		Thrt->lchild = Thrt;// 头结点的左指针也指向自身
	else	// 非空树
	{
		Thrt->lchild = T;	// 头结点的左指针指向根结点
		pre = Thrt;			// 前驱为头结点
		PreThreading(T);	// 从头结点开始先序递归线索化
		pre->rchild = Thrt;	// 最后一个结点的后继指向头结点
		pre->RTag = Thread;
		Thrt->rchild = pre;	// 头结点的后继指向最后一个结点
	}
}

// 先序遍历线索二叉树T(头结点)的非递归算法
void PreOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType))
{
	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指向右孩子或后继
	}
}

// PostOrderThreading()调用的递归函数
void PostThreading(BiThrTree p)
{
	if (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->rchild = p;	// p前驱的后继指向p
			pre->RTag = Thread;	// pre的右孩子为线索
		}
		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 = Thrt;// 头结点的左右指针指向根结点(最后一个结点)
		pre = Thrt;			// 前驱为头结点
		PostThreading(T);	// 从头结点开始后序递归线索化
	
		if (pre->RTag != Link);	// 最后一个结点没有右孩子
		{
			pre->rchild = Thrt;	// 最后一个结点的后继指向头结点
			pre->RTag = Thread;
		}
	}
}

// DestroyBiThrTree调用的递归函数,T指向根结点
void PostOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType))
{
	// 对于后续线索化二叉树的后序遍历算法比较复杂,因为根结点是在最后访问,所以要采用带有双亲的三叉链表结构才行。本文没有给出其算法
}

// DestroyBiThrTree调用的递归函数,T指向根结点
void DestroyBiTree(BiThrTree &T)
{
	if (T)	// 非空树
	{
		if (T->LTag == 0)				// 有左孩子
			DestroyBiTree(T->lchild);	// 销毁左孩子子树
		if (T->RTag == 0)				// 有右孩子
			DestroyBiTree(T->rchild);	// 销毁右孩子子树
		free(T);	// 释放根结点
		T = NULL;	// 空指针赋0
	}
}

// 初始条件:线索二叉树Thrt存在。操作结果:销毁线索二叉树Thrt	
void DestroyBiThrTree(BiThrTree &Thrt)
{
	if (Thrt)	// 头结点存在
	{
		if (Thrt->lchild)	// 根结点存在
		{
			DestroyBiTree(Thrt->lchild);	// 递归销毁头结点lchild所指二叉树
		}
		free(Thrt);		// 释放头结点
		Thrt = NULL;	// 线索二叉树Thrt指针赋0
	}
}
	
void vi(TElemType c)
{
   	printf(form" ",c);
}

int main()
{
	BiThrTree H,T;
#if CHAR
   	printf("请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)\n");
#else
   	printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)\n");
#endif

	CreateBiThrTree(T);		// 按先序产生二叉树
	
#if 0
	PreOrderThreading(H,T); // 在先序遍历的过程中,先序线索化二叉树
   	printf("先序遍历(输出)线索二叉树:\n");
   	PreOrderTraverse_Thr(H,vi);
#endif
	
#if 0
	InOrderThreading(H, T);
	printf("中序遍历(输出)线索二叉树:\n");
	InOrderTraverse_Thr(H,vi); // 中序遍历(输出)线索二叉树
#endif

	PostOrderThreading(H,T); // 在后序遍历的过程中,后序线索化二叉树

	printf("program over\n");
	DestroyBiThrTree(H); // 销毁线索二叉树
	
	return 0;
}
	


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值