第五章 树和二叉树(5.5.2-5.6.1)

5.5.2线索二叉树

1.线索二叉树的基本概念

image-20220210170647748

在二叉树线索化的过程中会把树中的空指针利用起来作为寻找当前结点前驱或后继的线索,这样就出现了一个问题,即线索和树中原有指向孩子结点的指针无法区分。

ltag和rtag就是为了区分这两类指针,它们为标志域,

具体意义如下:

若ltag=0,则表示lchild为指针,指向结点的左孩子;若ltag=1,则表示lchild为线索,指向结点的直接前驱。
若rtag=0,则表示rchild为指针,指向结点的右孩子;若rtag=1,则表示rchild为线索,指向结点的直接后继。

二叉树的二叉线索类型定义:

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

以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树 (Threaded Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。

2.构造线索二叉树

为了记下遍历过程中访问结点的先后关系,附设一个指针pre始终指向刚刚访问过的结点,而指针p指向当前访问的结点,由此记录下遍历过程中访问结点的先后关系。算法5.7是对树中任意一个结点p为根的子树中序线索化的过程,算法5.8通过调用算法5.7来完成整个二叉树的中序线索化。

算法5.7 以结点p为根的子树中序线索化

算法步骤

  1. 如果p非空,左子树递归线索化。
  2. 如果p的左孩子为空,则给p加上左线索,将其LTag置为1• 让p的左孩子指针指向pre(前驱);否则将p的LTag置为0。
  3. 如果pre的右孩子为空,则给pre加上右线索,将其RTag置为1' 让pre的右孩子指针指向p (后继);否则将pre的RTag置为0。
  4. 将pre指向刚访问过的结点p, 即pre=p。
  5. 右子树递归线索化。

算法描述

void InThreading(BiThrTree p) 
{ //pre是全局变址,初始化时其右孩子指针为空,便于在树的最左点开始 建线索
 if(p) 
 {
  InThreading (p-> lchild) ;             //左子树递归线索化
  if (! p-> lchild)                      //p的左孩子为空
  {
   p->LTa_g=l;                           //给p加上左线索
   p-> lchild=pre;                       //p的左孩子指针指向pre (前驱)
  }
  else p->LTag=O; 
  if(! pre-> rchild)                       //pre的右孩子为空
  { 
  pre-> RTag=l;                              //给pre加上右线索       
  pre-> rchld=p;                            //pre的右孩子指针指向p (后继)
  }
  else p->RTag=O; 
  pre=p;                                   //保持pre指向p的前驱
  InThrending (p-> rchild) ;               //右子树递归线索化
 }
}

算法5.8 带头结点的二叉树中序线索化

算法描述

void InOrderThreading(BiThrTree &Thrt,BiThrTree T) 
{// 中序遍历二叉树 T, 并将其中序线索化,Thrt指向头结点
Thrt=new Bi ThrNode; / /建 头结点
Thrt->LTag=O; // 头结点有左孩子, 若树非空,则其左孩子为树 根
Thrt-> RTag=l; // 头结点的右孩子指针为 右线索
Thrt-> rchild=Thrt; //初始化时右指针指向自己
if (! T) Thrt-> lchild=Thrt; //若树为空,则左指针也指向自己
else 
Thrt-> lchild=T; pre=Thrt; //头结点的左孩子指向根,pre 初值指向头结点
InThreading(T); //调用算法5. 7, 对以T为 根的二叉树进行中序线索化
pre-> rchild=Thrt; //算法5.7结束后,pre为 最右结点,pre的右线索指向头结点
pre-> RTag=l; 
Thrt- > rchild=pre; //头结点的右线索指向 pre
}
}

3.遍历线索二叉树

1.在中序线索二叉树中查找

①查找p指针所指结点的前驱:

• 若p->LTag为1' 则p的左链指示其前驱;

• 若p->LTag为0, 则说明p有左子树,结点的前驱是遍历左子树时最后访间的一个结点(左子树中最右下的结点)。

②查找p指针所指结点的后继:

• 若p->RTag为1'则p的右链指示其后继,以图5.16所示的中序线索树为 例来看,结点b的后继为结点*;

• 若p->RTag为 0, 则说明p有右子树。 根据中序遍历的规律可知,结点的后继应是遍 历其右子树时访间的第一个结点,即右子树中 最左下的结点。例如在找结点*的后继时,首先沿右指针找到其右子树的根结点-,然后顺其左指针往下直至其左标志为1的结点, 即为结点*的后继,在图中是结点c。

2.在先序线索二叉树中查找

①查找p指针所指结点的前驱:

• 若p->LTag为1,则p的左链指示其前驱;

• 若p->LTag为0, 则说明p有左子树。此时p的前驱有两种情况:若*p是其双亲的左孩子,则其前驱为其双亲结点;否则应是其双亲的左子树上先序遍历最后访问到的结点。

②查找p指针所指结点的后继:

• 若p->RTag为 1' 则p的右链指示其后继;

• 若p->RTag为 0, 则说明p有右子树。按先序遍历的规则可知,*p的后继必为其左子树根(若存在)或右子树根。

3.在后序线索二叉树中查找

①查找p指针所指结点的前驱:

• 若p->LTag为 1' 则p的左链指示其前驱;

• 若p->LTag为 0, 当p->RTag也为 0 时, 则p的右链指示其前驱;若p->LTag为 0,而p->RTag为 1 时, 则p的左链指示其前驱。

②查找p指针所指结点的后继情况比较复杂,分以下情况讨论:

• 若*p是二叉树的根, 则其后继为空;

• 若*p是其双亲的右孩子, 则其后继为双亲结点;

• 若p是其双亲的左孩子, 且*p没有右兄弟, 则其后继为双亲结点;

• 若p是其双亲的左孩子,且*p有右兄弟,则其后继为双亲的右子树上按后序遍历列出的第一个结点( 即右子树中 “最左下” 的叶结点)。

5.6树和森林

5.6.1树的存储结构

1.双亲表示法

我们假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指向其双亲结点到链表中的位置。也就是说每个结点除了知道自己之外还需要知道它的双亲在哪里。

它的结构特点是如图所示:

以下是我们的双亲表示法的结构定义代码:

/*树的双亲表示法结点结构定义  */
#define MAXSIZE 100
typedef int ElemType;		//树结点的数据类型,暂定为整形 
typedef struct PTNode		//结点结构
{
	ElemType data;			//结点数据
	int parent;				//双亲位置
}PTNode;

typedef struct
{
	PTNode nodes[MAXSIZE];	//结点数组
	int r,n;				//根的位置和结点数
}PTree;

2.孩子表示法

换一种不同的考虑方法。由于每个结点可能有多棵子树,可以考虑使用多重链表,即每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们把这种方法叫做多重链表表示法。不过树的每个结点的度,也就是它的孩子个数是不同的。所以可以设计两种方案来解决。

方案一:

一种是指针域的个数就等于树的度(树的度是树的各个结点度的最大值)

其结构如图所示:

不过这种结构由于每个结点的孩子数目不同,当差异较大时,很多结点的指针域就都为空,显然是浪费空间的,不过若树的各结点度相差很小时,那就意味着开辟的空间都被利用了,这时这种缺点反而变成了优点。

方案二:

第二种方案是每个结点指针域的个数等于该结点的度,我们专门取一个位置来存储结点指针域的个数。

其结构如图所示:

这种方法克服了浪费空间的缺点,对空间的利用率是很高了,但是由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。

能否有更好的方法呢,既可以减少空指针的浪费,又能是结点结构相同。

说到这大家肯定就知道是有的麦,那就是孩子表示法。

具体办法是,把每个结点的孩子排列起来,以单链表做存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针有组成一个线性表,采用顺序存储结构,存放进入一个一维数组中。

为此,设计两种结点结构,

一个是孩子链表的孩子结点,如下所示:

/*树的孩子表示法结点结构定义  */
#define MAXSIZE 100
typedef int ElemType;		//树结点的数据类型,暂定为整形 
typedef struct CTNode		//孩子结点
{
	int child;
	struct CTNode *next;
}*ChildPtr;

typedef struct				//表头结构
{
	ElemType data;
	ChildPtr firstchild;
}CTBox;
typedef struct				//树结构
{
	CTBox nodes[MAXSIZE];	//结点数组
	int r,n;				//根结点的位置和结点数
}CTree;

3.孩子兄弟法

任意一颗树,它的结点的第一个孩子如果存在就是的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。

其结点结构如图所示:

以下是孩子兄弟表示法的结构定义代码:

/*树的孩子兄弟表示法结构定义 */
typedef struct CSNode
{
	ElemType  data;
	struct CSNode  *firstchild, *rightsib;
}CSNode, *CSTree;
posted @     阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示