目录
first树的基本概念
树是由n (n>=0) 个结点构成的有限集合,n=0的树被称为空树;当n!=0时树中的结点应满足以下两个条件
- 有且仅有一个特定的结点称之为根
- 其余结点分成m(m>=0)个互不相交的有限集合T1、T2......Tm,其中每一集合又都是一个树,T1、T2......Tm为根节点的子树
树的说明
说明:
- 数据结构中允许空树的存在
- 当只有一个结点时,树的结点为根
- 当n>1时,其余结点可分为互不相交的子树
树的几个基本用语
结点的度
树中每个结点具有的子树数或者后继结点数称为结点的度
数的度
树中所有结点的度的最大值称为该数的度
叶子(终端结点)
度不为零的结点
儿子
一个结点的后继结点称为该结点的儿子
父亲
一个结点称为其后继结点的父亲
子孙
一个结点的所有子树中的结点称为该结点的子孙
祖先
是从根结点到达一个结点的路径上通过的结点称为该结点的子孙
兄弟
同一个父结点上的结点
堂兄弟
其父亲在同一结点上的结点
结点的层次
从根结点开始定义
深度
树中结点的最大层数称为树的层数或高度
有序树
若把树中结点的各子树看成是从左到右(不可置换)有序的,则称为有序树;否则,称为无序树
森林
0个或多个不相交的树的集合称为 森林
路径
在树中,若从结点Ki开始沿着数枝自上而下能到达结点Kj,则称从Ki到Kj存在一条路径
注:路径的长度等于所经过树枝的长度
森林详解:
由m棵互不相交的树构成的集合称为森林。森林和树的概念十分相近,一棵树中每个结点其子树的集合即为一个森林;而在森林中的每棵树之上加一个共同的根,森林就成为了一棵树
树的其他表示方法
嵌套集合表示法(了解)
凹入表示法(了解)
树的存储结构
根据数据元素之间关系的不同表示方式,常用的数的存储结构主要有三种:双亲表示法、孩子表示法、孩子兄弟表示法
双亲表示法
存储树中的结点时,可以包含两个信息:结点的值data和体现结点之间相互关系的属性——该结点的双亲parent。借助于每个结点的这两个信息便可唯一地表示任意一棵树,这种表示方法称为双亲表示法
代码实现
#define MAXSIZE 100
typedef char datatype;
typedef struct node
{
datatype data;
int parent;
}node;
typedef struct tree
{
node treelist[MAXSIZE];
int length,root;
}tree;
孩子表示法
整棵树中所有结点的相互关系是通过指明结点子女的位置来体现的,称这种表示法为孩子表示法
分类:
指针式的孩子表示法
数组式的孩子表示法
链表式的孩子表示法
//指针式的孩子表示法
#define m 3 //度数
typedef char datatype; //结点值类型
typedef struct node{
datatype data;
struct node *child[m];//指向子女的指针数组
}node ,*tree;
tree root;
//数组式的孩子表示法
#define m 3 //树的度数
#define MAXSIZE 20 //存放树结点的数组大小
typedef char datatype; //树中结点值的类型
typedef struct node{ //树中结点的类型
datatype data;
int child[m];
}treenode;
treenode tree[MAXSIZE]; //根节点下标
int root; //树中实际所含结点的个数
int length;
//链表式的孩子表示法
#define MAXSIZE 50
typedef char datatype;
typedef struct chnode{ //孩子结点的类型
int child;
struct chnode *next;
}chnode,*chpoint;
typedef struct{ //树中每个结点的类型
datatype data;
chpoint firstchild; //指向第一个子女的指针
}node ;
typedef struct{ //树的类型
node treelsit[MAXSIZE];
int length,root;
}tree;
孩子兄弟表示法
在存储树中的每个结点时,除了包括结点值域外,还设置两个指针域firstchild和rightsibling,分别指向该结点的第一个子女和其右兄弟,即以二叉链表方式加以存储,因此该方法也常被称为二叉树表示法
typedef char datatype; //树中结点的类型
typedef struct node{ //树中每个结点的类型
datatype data;
struct node *firstchild ,*rightsibling;
}node ,*pnode;
pnode root; //指向树根结点的指针
树的遍历
定义
指按某种规定的顺序访问树中的每一个结点一次,且每个结点仅被访问一次。数的遍历方式分为以下三种
树的前序遍历:首先访问根节点,再依次按前序遍历的方式访问根节点的每一棵子树。
树的后序遍历:首先按后序遍历的方式访问根节点的每一棵子树,然后再访问根结点。
树的层次遍历:首先访问第一层上的根结点,然后从左到右依次访问第二层上所有的结点,再以同样的方式访问第三层的结点,......,最后访问树中最低一层的所有结点。
树的前序遍历的递归实现
void preorder(tree p) //*p为指向树根节点的指针
{
int i;
if(p!=NULL) //树不为空
{
printf("%c",p->data); //输出根节点的值
for(i=0;i<m;++i) //依次递归实现树的前序遍历
preorder(p->child[i]);
}
}
树的后序遍历的递归实现
void postorder(tree p) //*p为指向树根节点的指针
{
int i;
if(p!=NULL) //树不为空
{
for(i=0;i<m;++i) //依次递归实现各子树的后序遍历
postorder(p->child[i]);
printf("%c",p->data);//输出根节点的值
}
}
按前序遍历顺序建立一棵度数为3的树的递归算法
void creattree(tree *p)
{
int i;
char ch;
if((ch=getchar())==' ') *p=NULL;
else
{
*p=(tree)malloc(sizeof(node));
(*p)->data=ch;
for(i=0;i<m;++i)
creattree(&(*p)->child[i]);
}
}
树的层次遍历算法
在树的层次遍历过程中,对于某一层上的每个结点被访问后,应立即将其所有子女结点按从左到右的顺序依次保存起来,该层上所有结点的这些子女结点正好构成下一层的所有结点,接下来应该被访问的就是它们。显然,这里用于保存子女结点的数据结构选择队列最合适,队伍中的每个元素均为在排队等待访问的结点。
由于树的层次遍历首先访问的是根结点,因此初始值时队列中仅包含根结点。只要队列不为空,就意味着还有结点未被访问,遍历就必须继续进行;每次需访问一个结点时均取队头元素,访问完成后若其子女非空,则将其所有子女按顺序依次进队;不断重复以上过程,直到队列为空。
代码实现
void levelorder(tree t) //t为指向树根结点的指针
{
tree queue[20]; //存放等待访问的结点队列
int f,r,i; //f、r分别为队头、队尾指针
tree p;
f=0;
r=0;
queue[0]=t;
while(f<=r) //队列不为空
{
p=queue[f];
f++;
printf("%c",p->data); //访问队头元素
for(i=0;i<m;++i) //将刚被访问的元素的所有子女结点依次进队
{
if(p->child[i])
{
++r;
queue[r]=p->child[i];
}
}
}
}
树的线性表示
树的线性表示便于树的输入、输出,同时在存储时也比较节省空间。本节主要介绍树的两种线性表示方法:括号表示法和层号表示法。
树的括号表示
- 若T为空树,其括号表示为空;
- 若树T只包含一个结点,则其括号表示即为该结点本身;
- 如果树T由根结点A和它的m棵子树T1,T2,......Tn构成,则其括号表示为:
- A(T1的括号表示,T2的括号表示,......,Tn的括号表示)其中子树的括号表示同样应该遵循以上规则
树的括号表示具有以下特点:
- “( ” 前面的元素一定为某棵树或子树的根结点,而其所有子树中的结点一定位于该“( ” 和与之对应的“( ” 之间;
- 任何“( ” 和与之配对的“( ” 之间的括号表示序列同样满足(1)中的性质。
树的括号表示和树的孩子表示转换算法
从左至右扫描树的括号表示
每当遇到左括号时,其前一个结点进栈,并读下一个符号
每当遇到有括号时,栈顶元素出栈。说明以栈顶元素为根的子树结构完毕,若此时栈为空,算法结束,否则读下一个符号
没当遇到“,”,则滑过该符号,并读下一个符号。
#define m 3 //树的度数
#define MAXSIZE 20 //树的孩子表示法对应的数组的大小
#define BMAXSIZE 50 //树的括号表示对应的数组大小
typedef char datatype; //树的结点类型
typedef struct node{ //树中孩子表示法中结点的类型
datatype data;
int child[m];
}treenode;
treenode tree[MAXSIZE]; //树孩子表示法的存储数组
int root; //根节点下标
int length; //树中实际所含结点个数
char p[BMAXSIZE]; //存放树括号表示的数组
//将树的括号表示法转换成树的孩子表示法
void bracktotree(char p[],int *root,int *length,treenode tree[])
{
int stack[MAXSIZE]; //存储树或子树根结点的栈
int top; //栈顶指针
int i,j,k,l,done; //done为程序结束的标志
k=0;
j=0;
*root=0;
top=0; //栈和标志的初始化
done=1;
tree[j].data=p[k]; //产生孩子表示法中的根结点
++k;
for(i=0;i<m;++i)
tree[j].child[i]=-1;
while(done)
{
if(p[k]=='(') //遇到左括号,则前面的元素对应结点进栈
{
stack[top]=j;
++top;
++k;
}
else if(p[k]==')') //遇到有括号,栈顶元素出栈
{
--top;
if(top=0) done=0;//栈顶为空,算法结束
else ++k;
}
else if(p[k]==',')
++k;
else //将当前被扫表的元素作为栈顶元素的子女
{
++j;
tree[j].data=p[k];
for(i=0;i<m;++i)
tree[j].child[i]=-1;
l=stack[top-1];
i=0; //寻找栈顶元素当前第一个空子女
while(tree[l].child[i]!=-1)
++i;
tree[l].child[i]=j;
++k;
}
}
}
树的层号表示法和树的扩充孩子表示转换算法
#define m 3 //树的度数
#define MAXSIZE 20 //数组元素个数的最大值
typedef char datatype; //树中结点值的类型
typedef struct node{ //树的扩充孩子表示法中结点的类型
datatype data;
int child[m];
int parent;
}treenode;
typedef struct{ //层号表示法中结点的类型
datatype data;
int lev; //存储结点的层号
}levelnode;
treenode tree[MAXSIZE]; //树的扩充孩子表示法存储数组
int root; //根结点下标
int lenth; //树中实际所含结点的个数
levelnode ltree[MAXSIZE]; //树层号表示法的数组
//将树的层号表示法转换成树的扩充孩子表示法
leveltree(int length,levelnode ltree[],int *root,treenode tree[])
{
int i,j,k;
for(i=0;i<length;++i)
for(j=0;j<m;++j) tree[i].child[j]=-1;
*root=0; //第一个元素为根结点
tree[0].data=ltree[0].data;
tree[0].parent=-1; //根结点的双亲为空
for(i=1;i<length;++i)
{
tree[i].data=ltree[i].data;
j=i-1;
if(ltree[i].lev>ltree[j].lev)//结点i为前一个元素j的第1个子女
{
tree[i].parent=j;
tree[j].child[0]=i;
}
else
{
while(ltree[i].lev<ltree[j].lev)//寻找i的兄弟
j=tree[j].parent;
tree[i].parent=tree[j].parent; //结点i和结点j的双亲相同
j=tree[j].parent;
k=0;
while(tree[j].child[k]!=-1) ++k;
tree[j].child[k]=i;
}
}
}