6.1树的基本概念
树型结构是区别于线性结构的另一大数据结构,它具有分支性
和层次性
。
树是由n(n>=0)个结点构成的有限集合。n=0的树称为空树;当n!=0时,树中的结点度应该满足下列条件:
- 有且仅有一个特定的结点称之为根
- 其余结点分成m(m>=0)个互不相交的有限集合,T1,T2,…,Tm,其中每一个集合又都是一颗树,称T1,T2,…,TM为根结点的子树。
- 以上是一个递归定义。
树的度
:我们将一结点拥有的子女数称为该结点的度,树中所有结点度的最大值称为树的度。
叶子结点
:称度为0的结点为终端结点或者叶子结点。
分支结点
:称度不为0的结点为非终端结点或分支结点。
树枝
:树中连接两个结点的线段称为树枝
路径
:在树中,若从结点Ki开始沿着树枝自上而下能到达结点kj,则称ki到kj存在一条路径。路径的长度等于所经过的树枝数。
树中结点的层次
:从树根开始定义,根节点为第1层,根的子女结点构成第2层,以此类推。若某结点kj位于第i层,则其子女就位于第i+1层。
树的深度或高度
:称树中结点的最大层次数为树的深度或高度。
有序树
:若树中任意结点的子树均看成是从左到右有次序的,不能随意交换,则称该树是有序树;否则称为无序树。
森林
:由m(m>=0)颗互不相交的树构成的集合称为森林。森林和树的概念十分相近,一颗树中每个结点的子树所构成的几个即为一个森林,而在森林中的每棵树之上加一个共同的根,森林就成为了一棵树。
6.3树的存储结构
常用的树的存储结构有三种:双亲表示法、孩子表示法、孩子兄弟表示法
6.3.1双亲表示法
在树中,除根节点没有双亲外,其他每个结点的双亲是唯一确定的。故存储树中的结点应该包含两个信息:结点的值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;
6.3.2孩子表示法
采用孩子表示法表示一棵树时,树中每个结点除了存储自身的值之外,还必须指出其所有子女的位置。 每个结点通常包含两个域:一个是元素的值域data,另一个为指针数组。数组中的每个元素均为一个指向该结点子女的指针;一颗m度的数,其指针数组的大小即为m。
孩子表示法又分为三种:
- 指针方式的孩子表示法
- 数组方式的孩子表示法
- 链表方式的孩子表示法
1、指针方式的孩子表示法
#define m 3
typedef char datatype;
typedef struct node{
datatype data;
struct node *child[m];
}node ,*tree;
tree root;
2、数组方式的孩子表示法
为了查找方便,可以将树中的所有结点存储在一个一维数组中。
#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; /*树中实际所含结点的个数*/
3、链表方式的孩子表示法
把每个结点的子女排列起来形成一个单链表,这样n个结点就形成n个单链表;而n个单链表的头指针又组成一个线性表,为了查找方便,可以使用数组方式加以存储。
#define MAXSIZE 50
typedef char datatype;
typedef struct cnode{
/*孩子结点的类型*/
int child;
struct chnode *next;
}chnode,*chpoint;
typedef struct{
/*树中,每个结点的类型*/
datatype data;
chpoint firstchild;
}node;
typedef struct {
/*树的类型*/
node treelist [MAXSIZE];
int length ,root;
}tree;
6.3.3孩子兄弟表示法
即在存储树中每个结点时,除了包含该结点值域外,还设置两个指针域firstchild和rightsibling,分别指向该结点的第一个子女和其右兄弟,即以二叉链表方式加以存储。因此该方法也常称为二叉树表示法。
6.4树的遍历
树的遍历,就是按照某种规定的顺序访问树中的每个结点,且每个结点仅被访问一次。
树的遍历常分为3种:
- 树的前序遍历(根左右。如果根下还有根,则继续访问以下的根)
- 树的后序遍历(左右根。)
- 树的层次遍历
树的前序遍历和后序遍历的定义具有递归性,因此采用递归方式实现树的前、后序遍历,只要按照其各自规定的顺序,访问根节点时就输出根节点的值,访问子树时遍进行递归调用即可。
1、树的前序遍历
void preorder(tree p)
{
int i;
if(p!=NULL)
{
printf("%c",p->data);
for(i=0;i<m;i++)
preorder(p->child[i]);
}
}
2、树的后序遍历
void postorder(tree p)
{
int i;
if(p!=NULL)
{
for(i=0;i<m;i++) /*依次递归实现各个子树的后序遍历*/
postorder(p->child[i]);
printf("%c",p->data); /*输出根节点的值*/
}
}
3、树的前序遍历建立一颗3度树
返回根的地址
tree createtree()
{
int i;
char ch;
tree t;
if((ch=getchar())=='#')
t=NULL;
else
{
t=(tree)malloc(sizeof(node));
t->data=ch;
for(i=0;i<m;++i)
t->child[i]=createtree();
}
return t;
}
4、树的层次遍历
队列(先进先出)思想:先访问第一层结点,若有子女,则将子女加入队列中;访问第二层结点时,将刚加入的结点出队。再将第二层结点的子女结点进队。。。。。重复上述过程。(队列中的每个元素均为在排队等待访问的结点)
思想:在树的层次遍历过程中,对于某一层上的每个结点的子女结点正好构成下一层的所有结点,接下来应该被访问的就是他们。树的层次遍历中首先访问的是根节点,因此初始时队列中仅包含根结点。只要队列不为空,就意味着还有结点未被访问。
void levelorder(tree t)
{
tree queue[100]; /*存放等待访问的结点队列*/
int f,r,i; /*f ,r分别为队头、队尾指针*/
tree p;
f=0;r=1;queue[0]=t;
while(f<t) /*队列不为空*/
{
p=queue[f];
f++;
printf("%c",p->data);
for(i=0;i<m;++i)
if(p->child[i])
{
queue[r]=p->child[i];
++r;
}
}
}
6.5树的线性表示
6.5.1树的括号表示
- 若树T为空树,则其括号表示为空。
- 若树T只包含一个结点,其括号表示即为该结点本身。
- 如果不是上述情况,则表示为**A(B,C(F,G,H),D,E(J,I))**注意,有逗号!!!
6.5.2树的层号表示
设j为树中的一个结点,若为j赋予的一个整数值lev(j)满足以下两个条件:
- 如果结点i为j的后件,则lev(i)>lev(j)
- 如果结点i与j为同一结点的后件,则lev(i)=lev(j)
则称满足以上条件的整数值lev(j)为结点j的层号。
一个栗子:
- 10A , 20B , 20C , 30F , 30G , 30H , 20D , 20E , 40J , 40I
- 1A , 2B , 2C , 5F , 5G , 5H , 2D , 2E , 3J , 3I