6.8树、森林和二叉树的关系

6.8树、森林和二叉树的关系

一、 树的存储结构

树的主要存储方法有以下三种:

1.双亲表示法

这种方法用一组连续的空间来存储树中的结点,在保存每个结点的同时附设一个指示器来指示其双亲结点在表中的位置,其结点的结构如下图所示:
在这里插入图片描述
整棵树用含有 MAX 个上述结点的一维数组来表示,如下图所示。

这种存储法利用了树中每个结点(根结点除外)只有一 个双亲结点的性质,使得查找某个结点的双亲结点非常容易。 反复使用求双亲结点的操作,也可以较容易地找到树根。但是,在这种存储结构中,求某个结点的孩子时需要遍历整个数组。
在这里插入图片描述
双亲表示法的形式说明如下:

#define MAX 100 
typedef struct TNode 
{  
	DataType data;  
	int parent; 
}TNode; 

树可以定义为:

typedef struct  
{ 
TNode tree[MAX];  
int nodenum;      /*结点数*/ 
}ParentTree; 

2.孩子表示法

这种方法通常是把每个结点的孩子结点排列起来,构成一个单链表,称为孩子链表。n 个结点共有 n 个孩子链表(叶子结点的孩子链表为空表),而 n 个结点的数据和 n 个孩子链表的头指针又组成一个顺序表。

树采用这种存储结构时,其结果如下图所示。
在这里插入图片描述
孩子表示法的存储结构定义如下:

typedef struct ChildNode       /* 孩子链表结点的定义 */ 
{  
	int Child;                   /* 该孩子结点在线性表中的位置 */  
	struct ChildNode * next;     /*指向下一个孩子结点的指针 */ 
}ChildNode;   
 
typedef struct                 /* 顺序表结点的结构定义 */ 
{  
	DataType data;               /* 结点的信息 */  
	ChildNode * FirstChild ;     /* 指向孩子链表的头指针 */ 
}DataNode; 
 
typedef struct                 /* 树的定义 */ 
{  
	DataNode   nodes[MAX];       /* 顺序表 */ 
	int  root;                   /* 该树的根结点在线性表中的位置 */ 
	int  num;                    /* 该树的结点个数 */ 
}ChildTree;

3.孩子兄弟表示法

这种表示法又称为树的二叉表示法,或者二叉链表表示法,即以二叉链表作为树的存储结构。 链表中每个结点设有两个链域,分别指向该结点的第一个孩子结点和下一个兄弟(右兄弟)结点。
在这里插入图片描述

孩子兄弟表示法的类型定义如下:

typedef struct CSNode 
{ 
	DataType  data;                  /*结点信息*/ 
	Struct CSNode  *FirstChild;      /*第一个孩子*/ 
	Struct CSNode  *Nextsibling;     /*下一个兄弟*/ 
}CSNode, *CSTree;

这种存储结构便于实现树的各种操作,例如,如果要访问结点 x 的第 i 个孩子,则只要先从 FirstChild 域找到第一个孩子结点,然后沿着这个孩子结点的 Nextsibling 域连续走 i-1 步,便可找到 x 的第 i 个孩子。如果在这种结构中为每个结点增设一个 Parent 域,则同样可以方便地实现查找双亲的操作。

二、树、森林与二叉树的相互转换

前面介绍了树的存储结构和二叉树的存储结构,从中可以看到,树的孩子兄弟链表结构 与二叉树的二叉链表结构在物理结构上是完全相同的,只是它们的逻辑含义不同,所以树和森林与二叉树之间必然有着密切的关系。本节介绍树和森林与二叉树之间的相互转换方法

1.树转换为二叉树

对于一棵无序树,树中结点的各孩子的次序是无关紧要的,而二叉 树中结点的左、右孩子结点是有区别的。为了避免混淆,约定树中每一 个结点的孩子结点按从左到右的次序顺序编号,也就是说,把树作为有 序树看待。如右图所示的一棵树,根结点 A 有三个孩子 B、C、D,可以认为结点 B 为 A 的第一个孩子结点,结点 D 为 A 的第三个孩子结点。
在这里插入图片描述

将一棵树转换为二叉树的方法是:
⑴ 树中所有相邻兄弟之间加一条连线。
⑵ 对树中的每个结点,只保留其与第一个孩子结点之间的连线, 删去其与其他孩子结点之间的连线。
⑶ 以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。

可以证明,树做这样的转换所构成的二叉树是唯一的。

下图给出了右上图中的树转换为 二叉树的转换过程示意。
在这里插入图片描述
通过转换过程可以看出,树中的任意一个结点都对应于二叉树中的一个结点。树中某结点的第一个孩子在二叉树中是相应结点的左孩子,树中某结点的右兄弟结点在二叉树中是相 应结点的右孩子。也就是说,在二叉树中,左分支上的各结点在原来的树中是父子关系,而 右分支上的各结点在原来的树中是兄弟关系。由于树的根结点没有兄弟,所以变换后的二叉树的根结点的右孩子必然为空。

事实上,一棵树采用孩子兄弟表示法所建立的存储结构与它所对应的二叉树的二叉链表存储结构是完全相同的,只是两个指针域的名称及解释不同而已,通过下图直观地表示了树与二叉树之间的对应关系和相互转换方法。
在这里插入图片描述
因此,二叉链表的有关处理算法可以很方便地转换为树的孩子兄弟链表的处理算法。

2.森林转换为二叉树

森林是若干棵树的集合。树可以转换为二叉树,森林同样也可以转换为二叉树。因此, 森林也可以方便地用孩子兄弟链表表示。森林转换为二叉树的方法如下:

  • (1) 将森林中的每棵树转换成相应的二叉树。
  • (2) 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连在一起后,所得到的二叉树就是由森林转换得到的二叉树。

森林转换为二叉树的过程,还可以用递归的方法描述上述转换过程:
将森林 F看作树的有序集 F={T1,T2,…,TN},它对应的二叉树为 B(F): (1)若 N=0,则 B(F)为空。
(2)若 N>0,二叉树 B(F)的根为森林中第一棵树 T1 的根; B(F)的左子树为 B ({T11,…,T1m}),其中{T11,…,T1m}是 T1的子树森林;B(F)的右子树是 B({T2,…, TN})。

根据这个递归的定义,可以很容易地写出递归的转换算法。

例:给出将下图(a)给出了森林转换为二叉树的过程。
在这里插入图片描述

3.二叉树还原为树或森林

树和森林都可以转换为二叉树,二者的不同是:树转换成的二叉树,其根结点必然无右孩子,而森林转换后的二叉树,其根结点有右孩子。将一棵二叉树还原为树或森林,具体方法如下:

(1) 若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子、……都与 该结点的双亲结点用线连起来。
(2) 删掉原二叉树中所有双亲结点与右孩子结点的连线。
(3) 整理由(1)、(2)两步所得到的树或森林,使之结构层次分明。

例 6-4 给出将下图(a)的一棵二叉树还原为森林的过程示意图
在这里插入图片描述

同样,可以用递归的方法描述上述转换过程。

若 B 是一棵二叉树,T 是 B 的根结点,L是 B 的左子树,R 为 B 的右子树,且 B 对应的森 林 F(B)中含有的 n 棵树为 T1,T2, …,Tn,则有:
(1) B 为空,则 F(B)为空的森林(n=0)。
(2) B 非空,则 F(B)中第一棵树 T1的根为二叉树 B 的根 T;T1中根结点的子树森林由 B 的左子树 L 转换而成,即 F(L)={T11,…,T1m};B 的右子树 R 转换 为 F(B)中其余树组成的森林,即 F®={ T2, T3, …,Tn}。

根据这个递归的定义,同样可以写出递归的转换算法。

4.树与森林的遍历

1.树的遍历

在这里插入图片描述
树的遍历方法主要有以下两种:
(1)先根遍历 若树非空,则遍历方法为:
① 访问根结点。
② 从左到右,依次先根遍历根结点的每一棵子树。
例如,右图中树的先根遍历序列为 ABECFHGD。

(2)后根遍历 若树非空,则遍历方法为:
①从左到右,依次后根遍历根结点的每一棵子树。
②访问根结点。 例如,右图中树的后根遍历序列为 EBHFGCDA。

树的遍历结果与由树转化成的二叉树有如下对应关系:
树的先根遍历 转化二叉树的前序遍历
树的后根遍历 转化二叉树的中序遍历

2.树的遍历的算法实现

在选定了存储结构后可按上述对应规则写出相应实现算法。

例如:用孩子兄弟链表实现树的先根遍历。

  • [方法一]:
void  RootFirst(CSTree  root)  
{   
	if (root!=NULL) 
	{  
		Visit(root ->data);        /* 访问根结点 */ 
		p= root-> FirstChild; 
		while (p!=NULL)   
		{  
			RootFirst( p );      /* 访问以 p 为根的子树 */      
			p = p -> Nextsibling; 
		} 
	} 
} 
  • [方法二]:
void  RootFirst(CSTree  root)  
{ 
	if (root!=NULL) 
	{ 
		Visit (root ->data);           /*访问根结点*/ 
		RootFirst (root->FirstChild);   /*先根遍历首子树*/ 
		RootFirst (root->Nextsibling);  /*先根遍历兄弟树*/ 
	} 
}
3. 森林的遍历

在这里插入图片描述
森林的遍历方法主要有以下三种:

  • (1)先序遍历 若森林非空,则遍历方法为:
    ①访问森林中第一棵树的根结点。
    ②先序遍历第一棵树的根结点的子树森林。
    ③先序遍历除去第一棵树之后剩余的树构成的森林。 例如,右图中森林的先序遍历序列为 ABCDEFGHIJ。

  • (2)中序遍历 若森林非空,则遍历方法为:
    ①中序遍历森林中第一棵树的根结点的子树森林。
    ②访问第一棵树的根结点。
    ③中序遍历除去第一棵树之后剩余的树构成的森林。 例如,右图中森林的中序遍历序列为 BCDAFEHJIG。

  • (3)后序遍历 若森林非空,则遍历方法为:
    ①后序遍历森林中第一棵树的根结点的子树森林。
    ② 后序遍历除去第一棵树之后剩余的树构成的森林。
    ③访问第一棵树的根结点。

例如,右图中森林的后序遍历序列为 DCBFJIHGEA。

对照二叉树与森林之间的转换关系可以发现,森林的先序遍历、中序遍历和后序遍历与 其相应二叉树的先序遍历、中序遍历和后序遍历是对应相同的,因此可以用相应二叉树的遍 历结果来验证森林的遍历结果。另外,树可以看成只有一棵树的森林,所以树的先根遍历和后根遍历分别与森林的先序遍历和中序遍历对应。森林的遍历算法可以采用其对应的二叉树的遍历算法来实现。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值