数据结构——线索二叉树

一、线索二叉树

        遍历二叉树实际上是将树中的结点按照一定的规则构成一个线性序列,不同的遍历方式得到的是不同的序列。对于需要经常遍历的二叉树,可以充分利用二叉树的空指针域,保存遍历中的信息,即左右指针分别记录结点的前驱和后继,即二叉树的线索化。

        重新定义二叉树结点如下,增加两个标志域:

typedef enum PointerTag{Link, Thread};
struct BiTreeThreadNode{
	int c;
	struct BiTreeThreadNode *left;
	struct BiTreeThreadNode *right;
	PointerTag ltag,rtag;
};


二、二叉树的线索化

1、带标志域的二叉树的先序输入递归创建如下:(注意要给标志域赋值,因为在遍历时需要判断标志域)

//先序输入递归创建二叉树
BiTreeThreadNode* createBiTreeThreadPreOrder()
{
	BiTreeThreadNode *ptree=NULL;
	char ch;
	ch=getchar();
	if(ch==' ')
		ptree=NULL;
	else
	{
		ptree=(struct BiTreeThreadNode *)malloc(sizeof(BiTreeThreadNode));
		ptree->c=ch;
		ptree->left=createBiTreeThreadPreOrder();
		ptree->right=createBiTreeThreadPreOrder();
		ptree->ltag=ptree->left?Link:Thread;
		ptree->rtag=ptree->right?Link:Thread;
	}
	return ptree;
}

2、二叉树的中序线索化

        二叉树的中序线索化可以通过中序遍历实现,遍历方式可以选用递归或者非递归,这里采用递归方式,非递归方式可以参考非递归中序遍历。

        在线索化二叉树时,先线索化其左子树,再处理其根结点,最后线索化其右子树。见函数InThreading。

        根结点处理的方式为:若其左子树为空,则使左指针指向其前驱pre;若前驱pre的右子树为空,则指向该根结点(pre的后继)。其中,pre为全局结构体指针,始终指向前一次访问过的结点。

        为了方便线索二叉树的遍历,给二叉树添加一头结点:其左指针指向线索二叉树的根结点,右指针指向线索二叉树的最后一个结点,且线索二叉树的第一个结点的左指针和最后一个结点的右指针均指向该头结点。为方便遍历,若二叉树为空树,将头结点的左指针指向自身。见函数InOrderThreading。

BiTreeThreadNode *pre=NULL;
BiTreeParentThreadNode *pre2=NULL;
//递归中序遍历实现中序线索化
void InThreading(BiTreeThreadNode *ptree)
{
	if(ptree)
	{
		InThreading(ptree->left);
		if(!ptree->left) { ptree->ltag=Thread; ptree->left=pre;}
		if(!pre->right) { pre->rtag=Thread; pre->right=ptree; }
		pre=ptree;
		InThreading(ptree->right);
	}
}
BiTreeThreadNode* InOrderThreading(BiTreeThreadNode *ptree)
{
	BiTreeThreadNode *pthr=NULL;
	pthr=(BiTreeThreadNode *)malloc(sizeof(BiTreeThreadNode));
	pthr->ltag=Link;
	pthr->rtag=Thread;
	pthr->right=pthr;
	if(!ptree) pthr->left=pthr;
	else
	{
		pthr->left=ptree;
		pre=pthr;
		InThreading(ptree);
		pre->right=pthr;
		pre->rtag=Thread;
		pthr->right=pre;
	}
	return pthr;
}

3、二叉树的先序线索化

        类似地,二叉树的先序线索化可以通过递归先序遍历实现。

        先处理根结点,再线索化其左子树,最后线索化其右子树,见函数PreThreading。

        根结点的处理方式为:若其左子树为空,则使左指针指向其前驱pre;若前驱pre的右子树为空,则指向该根结点(pre的后继)。其中,全局结构体指针pre始终指向前一次访问过的结点。此时应注意的是:当左子树为空时,左指针会指向其前驱,而不再为NULL,此时线索化左子树会无限递归,导致栈溢出;当右子树为空时,先进行的左子树的线索化会使此时的右指针指向其后继,不再为NULL,导致右子树的线索化无限递归以致栈溢出。考虑到这一点,增加一条件,即左/右子树为空时,不进行线索化。

        同样地,为了方便遍历,添加一头结点,见函数PreOrderThreading。

//递归先序遍历实现先序线索化
void PreThreading(BiTreeThreadNode *ptree)
{
	if(ptree)
	{
		if(!ptree->left) { ptree->ltag=Thread; ptree->left=pre;}
		if(!pre->right) { pre->rtag=Thread; pre->right=ptree; }
		pre=ptree;
		if(ptree->ltag==Link) PreThreading(ptree->left);   
		if(ptree->rtag==Link) PreThreading(ptree->right);
	}
}
BiTreeThreadNode* PreOrderThreading(BiTreeThreadNode *ptree)
{
	BiTreeThreadNode *pthr=NULL;
	pthr=(BiTreeThreadNode *)malloc(sizeof(BiTreeThreadNode));
	pthr->ltag=Link;
	pthr->rtag=Thread;
	pthr->right=pthr;
	if(!ptree) pthr->left=pthr;
	else
	{
		pthr->left=ptree;
		pre=pthr;
		PreThreading(ptree);
		pre->right=pthr;
		pre->rtag=Thread;
		pthr->right=pre;
	}
	return pthr;
}

4、二叉树的后序线索化
        后序线索树的遍历需要知道节点的双亲(后面将看到),因而为结点添加一指向双亲的指针域,重新定义二叉树如下:

struct BiTreeParentThreadNode{
	int c;
	struct BiTreeParentThreadNode *parent;
	struct BiTreeParentThreadNode *left;
	struct BiTreeParentThreadNode *right;
	PointerTag ltag,rtag;
};

        相应的先序输入递归创建函数如下:(注意为自身的双亲指针赋初值NULL,将子树的双亲指针指向自身,这样除根结点外,每个结点均指向其双亲)

//先序输入递归创建三叉链表式二叉树
BiTreeParentThreadNode* createBiTreeParentThreadPreOrder()
{
	BiTreeParentThreadNode *ptree=NULL;
	char ch;
	ch=getchar();
	if(ch==' ')
		ptree=NULL;
	else
	{
		ptree=(struct BiTreeParentThreadNode *)malloc(sizeof(BiTreeParentThreadNode));
		ptree->c=ch;
		ptree->parent=NULL;
		ptree->left=createBiTreeParentThreadPreOrder();
		ptree->right=createBiTreeParentThreadPreOrder();
		ptree->ltag=ptree->left?Link:Thread;
		ptree->rtag=ptree->right?Link:Thread;
		if(ptree->left) ptree->left->parent=ptree;  //左子树根的双亲节点
		if(ptree->right) ptree->right->parent=ptree;  //右子树根的双亲节点
	}
	return ptree;
}

        二叉树的后序线索化可以通过递归后序遍历实现。

        先线索化其左子树,再线索化其右子树,最后处理根结点,见函数PostThreading。

        根结点的处理方式为:若其左子树为空,则使左指针指向其前驱pre;若前驱pre的右子树为空,则指向该根结点(pre的后继)。其中,全局结构体指针pre始终指向前一次访问过的结点。由于左右子树的线索化先于根结点处理,故不会出现先序线索化时的无限递归情况。

        同样地,添加一头结点,为了方便遍历,使根结点的双亲指针指向头结点,见函数PostOrderThreading。

//递归后序遍历实现后序线索化
BiTreeParentThreadNode *pre2=NULL;
void PostThreading(BiTreeParentThreadNode *ptree)
{
	if(ptree)
	{
		PostThreading(ptree->left);
		PostThreading(ptree->right);
		if(!ptree->left) { ptree->ltag=Thread; ptree->left=pre2;}
		if(!pre2->right) { pre2->rtag=Thread; pre2->right=ptree; }
		pre2=ptree;
		
	}
}
BiTreeParentThreadNode* PostOrderThreading(BiTreeParentThreadNode *ptree)
{
	BiTreeParentThreadNode *pthr=NULL;
	pthr=(BiTreeParentThreadNode *)malloc(sizeof(BiTreeParentThreadNode));
	pthr->ltag=Link;
	pthr->rtag=Thread;
	pthr->right=pthr;
	if(!ptree) pthr->left=pthr;
	else
	{
		pthr->left=ptree;
		ptree->parent=pthr;
		pre2=pthr;
		PostThreading(ptree);
		pre2->right=pthr;
		pre2->rtag=Thread;
		pthr->right=pre2;
	}
	return pthr;
}

 

三、线索二叉树的遍历

        二叉树线索化后,原来的左/右空指针指向了结点的前驱或后继,形成了一个线性序列,其遍历可以从最首结点开始,依次寻找“后继”,直到最末结点。但是,对于右指针不为空的结点,无法通过后继指针直接索引到其后继结点。这就需要根据不同的线索化顺序,以不同的方式找到其后继结点。线索二叉树的遍历中,寻找后继结点是关键。

1、中序线索二叉树的中序遍历

        对于中序线索二叉树:树的最首结点为“最左下”的结点;若当前结点不存在右子树,则其后继结点可通过右指针直接找到;否则,其后继为其右子树的“最左下”结点。代码如下:

//中序线索二叉树的中序遍历--实现方式一
int InOrderTraverseBiTreeThread(BiTreeThreadNode *pthr,int (*visit)(int))
{
	BiTreeThreadNode *pt=NULL;
	pt=pthr->left;
	if(pt!=pthr)
		while(pt->ltag==Link) pt=pt->left;
	while(pt!=pthr)
	{
		visit(pt->c);
		if(pt->rtag==Thread) pt=pt->right;
		else
		{
			pt=pt->right;
			while(pt->ltag==Link) pt=pt->left;
		}
	}
	return 1;
}
//中序线索二叉树的中序遍历--实现方式二
int InOrderTraverseBiTreeThread2(BiTreeThreadNode *pthr,int (*visit)(int))
{
	BiTreeThreadNode *pt=NULL;
	pt=pthr->left;
	while(pt!=pthr)
	{
		while(pt->ltag==Link) pt=pt->left;
		visit(pt->c);
		while(pt->rtag==Thread && pt->right!=pthr)
		{
			pt=pt->right;
			visit(pt->c);
		}
		pt=pt->right;
	}
	return 1;
}

2、先序线索二叉树的先序遍历

        对于先序线索二叉树:树的最首结点为树的根结点;若当前结点不存在右子树,则其后继结点可通过右指针直接找到;否则,若存在左子树则其后继为其左子树的根结点,若不存在左子树则其后继为其右子树的根结点。代码如下:

//先序线索二叉树的先序遍历
int PreOrderTraverseBiTreeThread(BiTreeThreadNode *pthr,int (*visit)(int))
{
	BiTreeThreadNode *pt=NULL;
	pt=pthr->left;
	while(pt!=pthr)
	{
		visit(pt->c);
		if(pt->rtag==Thread) pt=pt->right;
		else if(pt->ltag==Link) pt=pt->left;
		else pt=pt->right;
	}
	return 1;
}

3、后序线索二叉树的后序遍历

        对于后序线索二叉树:树的最首结点为“最左下”的结点;若当前结点不存在右子树,则其后继结点可通过右指针直接找到;否则考虑其双亲,若当前结点为其双亲的右子树或其双亲不存在右子树,则其后继结点为其双亲,若当前结点为其双亲的左子树且双亲存在右子树,则其后继结点为其双亲右子树的“最左下”结点。代码如下:

//后序线索二叉树的后序遍历
int PostOrderTraverseBiTreeParentThread(BiTreeParentThreadNode *pthr,int (*visit)(int))
{
	BiTreeParentThreadNode *pt=NULL;
	pt=pthr->left;
	if(pt!=pthr)
		while(pt->ltag==Link) pt=pt->left;
	while(pt!=pthr)
	{
		visit(pt->c);
		if(pt->rtag==Thread) pt=pt->right;
		else if(pt==pt->parent->right || pt->parent->rtag==Thread)
			pt=pt->parent;
		else
		{
			pt=pt->parent->right;
			while(pt->ltag==Link) pt=pt->left;
		}
	}
	return 1;
}

 

【完】

你可能感兴趣的:(数据结构与算法,C/C++/VC)