1.树和森林转换为二叉树
- 树转换为二叉树
树的孩子兄弟表示和二叉树的二叉链表在存储方式上是相同的,即从它们的相同的物理结构可以得到一棵树,也可以得到一颗二叉树。
将一颗树转换为二叉树的步骤如下:
- 加线:在所有兄弟结点之间加一条连线。
- 去线:对树中的每个结点,只保留每个结点与它的第一个孩子节点间的连线,删除它与其他孩子的连线。
- 调整:以树的根结点为轴心,将整棵树顺时针旋转一定的角度, 使之结构层次分明。第一个孩子为二叉树中结点的左孩子,兄弟转换过来的孩子为右孩子。
任何一棵和树对应的二叉树,其右子树必为空。
- 森林转换为二叉树
森林是若干颗树组成的集合。森林也可以转换为对应的二叉树。方法为:
- 把森林中的每棵树都转换为二叉树。
- 第一颗二叉树不动,从第二颗二叉树开始,一次把后一颗二叉树的根结点作为前一颗二叉树的根结点的右孩子,用线连接起来。当所有的二叉树连接起来以后就得到了由森林转换来的二叉树,最后进行相应的调整,使其层次分明。
森林转换为二叉树的形式化描述:
如果
F={T1,T2,…,Tm}
是森林,则可按如下规则转换成一棵二叉树
B=(root,LB,RB)
:
- 若F为空,即 m=0 ,则B为空树;
- 若F非空,即 m≠0 ,则B的根root即为森林中第一棵树的根 ROOT(T1) ;B的左子树 LB 是从 T1 中根结点的子树森林 F1={T11,T12,Tm1} 转换而成的二叉树;其右子树 RB 是从森林 F′={T2,T3,Tm} 转换而成的二叉树。
2.二叉树转换为树和森林
把一颗二叉树转换为树的方法如下:
- 加线:若某结点的左孩子结点存在,则将该结点的左孩子的右孩子结点、右孩子的右孩子结点……都与该结点用线条连接。
- 去线:删除原二叉树中所有结点与右孩子结点的连线。
- 调整:使结构层次分明。
与二叉树转换树的方法类似,二叉树转换为森林的过程如下图所示。
二叉树转换为森林的形式化描述:
如果
B=(root,LB,RB)
是一棵二叉树,则可按如下规则转换成森林
F={T1,T2,…,Tm}
:
- 若B为空,则F为空;
- 若B非空,则F中第一棵树 T1 的根 ROOT(T1) 即为二叉树B的根root; T1 中的根结点的子树森林 F1 是由B的左子树 LB 转换而成的森林;F中除 T1 之外其余树组成的森林 F′={T2,T3,Tm} 是由B的右子树 RB 转换而成的森林。
3.森林的遍历
森林的遍历有两种:先根遍历和后根遍历。
- 先根遍历
- 访问第一颗树的根结点。
- 按先根顺序遍历第一棵树的全部子树。
- 按先根顺序遍历其余的树。
- 后跟遍历
- 按后根顺序遍历第一棵树的全部子树。
- 访问第一颗树的根结点。
- 按后根顺序遍历其余的树。
从森林与二叉树的自然对应关系可以看到,对森林遍历的先根顺序和后跟顺序的定义正好与这种自然的对应关系相符合。这是因为第一棵树的所有子树对应于二叉树的左子树,而其余的树则对应于二叉树的右子树。将这些定义和关于二叉树结点访问顺序的相应定义相比较可知,将先根顺序对一个森林进行遍历操作,与按先根顺序对二叉树进行遍历操作完全一致。而按后根顺序对一个森林进行遍历操作,与按中根顺序对相应的二叉树进行遍历操作完全一致。
此外可以把树看成是森林的特殊情况(即森林中仅有一棵树)时,可以发现对树的先根遍历顺序是对森林的先根遍历顺序的特例;而对树的后根遍历的顺序是对森林的后根遍历顺序的特例。
4.树与二叉树的应用
由两种遍历序列确定二叉树
已知先序和中序序列、中序和后序序列、先序和后序序列能唯一确定一棵二叉树吗?
- 由先序序列和中序序列唯一确定一棵二叉树
在二叉树的先序遍历过程中,根结点一定是第一个访问的结点。在中序遍历二叉树时,先中序遍历左子树,然后是根结点,最后遍历右子树。由此在二叉树的中序序列中,根结点位于左右子树序列的中间,把序列分为两部分,左边序列为左子树结点,右边是右子树结点。
根据先序序列的左子树和中序序列左子树部分,左子树的根结点可继续将中序序列分为左子树和右子树两个部分,依此类推,就可以构造出二叉树。
设结点的先序序列为(A,B,C,D,E,F,G),中序序列为(B,D,C,A,F,E,G),下图为确定二叉树的过程。
由先序序列和中序序列构造二叉树的算法实现如下:
void CreateBiTree1(BiTree *T,char *pre,char *in,int len)
/*由先序序列和中序序列构造二叉树*/
{
int k;
char *temp;
if(len<=0)
{
*T=NULL;
return;
}
*T=(BitNode*)malloc(sizeof(BitNode));/*生成根结点*/
(*T)->data=*pre;
for(temp=in;temp<in+len;temp++) /*在中序序列in中找到根结点所在的位置*/
if(*pre==*temp)
break;
k=temp-in; /*左子树的长度*/
CreateBiTree1(&((*T)->lchild),pre+1,in,k); /*建立左子树*/
CreateBiTree1(&((*T)->rchild),pre+1+k,temp+1,len-1-k); /*建立右子树*/
}
- 由中序序列和后序序列唯一确定一棵二叉树
由中序序列和后序序列也可以唯一确定一棵二叉树。在二叉树的后序序列中,最后一个结点元素一定是根结点。在中序遍历二叉树的过程中,先中序遍历左子树,然后是根结点,最后遍历右子树。因此在二叉树的中序序列中,根结点将中序序列分为左子树序列和右子树序列两部分。由中序序列的左子树结点个数,通过扫描后序序列, 可以将后序序列分为左子树序列和右子树序列。依此类推就可以构造出二叉树。
设结点的中序序列为(D,B,G,E,A,C,F),后序序列为(D,G,E,B,F,C,A)。下图为确定二叉树的过程。
由中序序列和后序序列构造二叉树的算法实现如下:
void CreateBiTree2(BiTree *T,char *in,char *post,int len)
/*由中序序列和后序序列构造二叉树*/
{
int k;
char *temp;
if(len<=0)
{
*T=NULL;
return;
}
for(temp=in;temp<in+len;temp++) /*在中序序列in中找到根结点所在的位置*/
if(*(post+len-1)==*temp)
{
k=temp-in; /*左子树的长度*/
(*T)=(BitNode*)malloc(sizeof(BitNode));
(*T)->data =*temp;
break;
}
CreateBiTree2(&((*T)->lchild),in,post,k); /*建立左子树*/
CreateBiTree2(&((*T)->rchild),in+k+1,post+k,len-k-1); /*建立右子树*/
}
- 由先序序列和后序序列不能唯一确定二叉树
假设有一个先序序列为(A,B,C),一个后序序列为(C,B,A),可以构造出两棵树。
由此可知,给定先序序列和后序序列不能唯一确定二叉树。
- 程序示例
【例1】已知先序序列(E,B,A,C,F,H,G,I,K,J)和中序序列(A,B,C,D,E,F,G,H,I,J,K)、中序序列(A,B,C,D,E,F,G,H,I,J,K)和后序序列(A,C,D,B,G,J,K,I,H,F,E),构造一棵二叉树。
#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#include<conio.h>
#define MaxSize 100
/*二叉树类型定义*/
typedef struct Node
{
char data;
struct Node * lchild,*rchild;
}BitNode,*BiTree;
/*函数声明*/
void CreateBiTree1(BiTree *T,char *pre,char *in,int len);
void CreateBiTree2(BiTree *T,char *in,char *post,int len);
void Visit(BiTree T,BiTree pre,char e,int i);
void PrintLevel(BiTree T);
void PrintTLR(BiTree T);
void PrintLRT(BiTree T);
void PrintLevel(BiTree T);
void main()
{
BiTree T,ptr=NULL;
char ch;
int len;
char pre[MaxSize],in[MaxSize],post[MaxSize];
T=NULL;
/*由中序序列和后序序列构造二叉树*/
printf("由先序序列和中序序列构造二叉树:\n");
printf("请你输入先序的字符串序列:");
gets(pre);
printf("请你输入中序的字符串序列:");
gets(in);
len=strlen(pre);
CreateBiTree1(&T,pre,in,len);
/*后序和层次输出二叉树的结点*/
printf("你建立的二叉树后序遍历结果是:\n");
PrintLRT(T);
printf("\n你建立的二叉树层次遍历结果是:\n");
PrintLevel(T);
printf("\n");
printf("请你输入你要访问的结点:");
ch=getchar();getchar();
Visit(T,ptr,ch,1);
/*由中序序列和后序序列构造二叉树*/
printf("由先序序列和中序序列构造二叉树:\n");
printf("请你输入中序的字符串序列:");
gets(in);
printf("请你输入后序的字符串序列:");
gets(post);
len=strlen(post);
CreateBiTree2(&T,in,post,len);
/*先序和层次输出二叉树的结点*/
printf("\n你建立的二叉树先序遍历结果是:\n");
PrintTLR(T);
printf("\n你建立的二叉树层次遍历结果是:\n");
PrintLevel(T);
printf("\n");
printf("请你输入你要访问的结点:");
ch=getchar();getchar();
Visit(T,ptr,ch,1);
}
void PrintLevel(BiTree T)
/*按层次输出二叉树的结点*/
{
BiTree Queue[MaxSize];
int front,rear;
if(T==NULL)
return;
front=-1; /*初始化化队列*/
rear=0;
Queue[rear]=T;
while(front!=rear) /*如果队列不空*/
{
front++; /*将队头元素出队*/
printf("%4c",Queue[front]->data);/*输出队头元素*/
if(Queue[front]->lchild!=NULL) /*如果队头元素的左孩子结点不为空,则将左孩子入队*/
{
rear++;
Queue[rear]=Queue[front]->lchild;
}
if(Queue[front]->rchild!=NULL) /*如果队头元素的右孩子结点不为空,则将右孩子入队*/
{
rear++;
Queue[rear]=Queue[front]->rchild;
}
}
}
void PrintTLR(BiTree T)
/*先序输出二叉树的结点*/
{
if(T!=NULL)
{
printf("%4c ",T->data); /*输出根结点*/
PrintTLR(T->lchild); /*先序遍历左子树*/
PrintTLR(T->rchild); /*先序遍历右子树*/
}
}
void PrintLRT(BiTree T)
/*后序输出二叉树的结点*/
{
if (T!=NULL)
{
PrintLRT(T->lchild); /*先序遍历左子树*/
PrintLRT(T->rchild); /*先序遍历右子树*/
printf("%4c",T->data); /*输出根结点*/
}
}
void Visit(BiTree T,BiTree pre,char e,int i)
/*访问结点e*/
{
if(T==NULL&&pre==NULL)
{
printf("\n对不起!你还没有建立二叉树,先建立再进行访问!\n");
return;
}
if(T==NULL)
return;
else if(T->data==e) /*如果找到结点e,则输出结点的双亲结点*/
{
if(pre!=NULL)
{
printf("%2c的双亲结点是是:%2c\n",e,pre->data);
printf("%2c结点在%2d层上\n",e,i);
}
else
printf("%2c位于第1层,无双亲结点!\n",e);
}
else
{
Visit(T->lchild,T,e,i+1);/*遍历左子树*/
Visit(T->rchild,T,e,i+1);/*遍历右子树*/
}
}
void CreateBiTree1(BiTree *T,char *pre,char *in,int len)
/*由先序序列和中序序列构造二叉树*/
{
int k;
char *temp;
if(len<=0)
{
*T=NULL;
return;
}
*T=(BitNode*)malloc(sizeof(BitNode));/*生成根结点*/
(*T)->data=*pre;
for(temp=in;temp<in+len;temp++) /*在中序序列in中找到根结点所在的位置*/
if(*pre==*temp)
break;
k=temp-in; /*左子树的长度*/
CreateBiTree1(&((*T)->lchild),pre+1,in,k); /*建立左子树*/
CreateBiTree1(&((*T)->rchild),pre+1+k,temp+1,len-1-k); /*建立右子树*/
}
void CreateBiTree2(BiTree *T,char *in,char *post,int len)
/*由中序序列和后序序列构造二叉树*/
{
int k;
char *temp;
if(len<=0)
{
*T=NULL;
return;
}
for(temp=in;temp<in+len;temp++) /*在中序序列in中找到根结点所在的位置*/
if(*(post+len-1)==*temp)
{
k=temp-in; /*左子树的长度*/
(*T)=(BitNode*)malloc(sizeof(BitNode));
(*T)->data =*temp;
break;
}
CreateBiTree2(&((*T)->lchild),in,post,k); /*建立左子树*/
CreateBiTree2(&((*T)->rchild),in+k+1,post+k,len-k-1); /*建立右子树*/
}
- 测试结果
树的广义表形式表示
【例2】一棵树可以用广义表形式表示,如下图所示的一棵树可以表示为A(B(D,E,F),C(G,H)),编写算法对以孩子链表表示的树输出其广义表的表示形式。
在树的广义表形式中,如果不考虑括号和逗号,那么结点是按照先根遍历序列排列的,因此可以对以孩子兄弟链表表示的树进行先根遍历,并在输出各子树的结点时加上括号。
当树不为空时,输出根结点;若根结点有孩子,则先输出左括号“(”,再依次输出各子树的广义表形式,并用逗号隔开,最后输出右括号“)”。显然这是一个递归过程,算法描述如下:
void ListTree(CSTree T)
{
CSTree p;
if(T==NULL)
return;
printf("%c",T->data);/*输出根结点*/
p=T->firstchild;
if(p!=NULL)/*若根结点有孩子*/
{
printf("(");/*输出左括号*/
ListTree(p);
p=p->nextsibling;
while(p!=NULL)
{
printf(",");/*输出逗号,以分隔各子树*/
ListTree(p);/*输出下一个子表的广义表形式*/
p=p->nextsibling;
}
printf(")");/*输出右括号*/
}
}
树的实现与插入子树
源码参见树的实现