树的定义:
树(Tree)是n(n>=0)个结点的有限集。当n=0时为空树,在任一课非空树种:
-
有且仅有一个特定的称为根的结点;
-
当n>1时,其余结点可分为m(m>0)个互不相交的有限集,其中每一个集合本身又是一棵树,并且称为根的子树;
相关概念:
- 度:结点拥有的子树数量;度为0的结点是叶子结点
- 结点的祖先:从根到该结点所经过的分支上的所有结点
树的深度:从根所在的层到最下面叶子结点的层次数
存储结构:
在使用树结构描述实际问题时,大多数不是二叉树,更多的是普通的树结构,在存储之间具有普通树结构的数据时,经常使用的方法有3种:
- 双亲表示法
- 孩子表示法
- 孩子兄弟表示法
双亲表示法:
取一块连续的内存空间,在存储每个结点的同时,各自都附加一个记录其父结点位置的变量。
在树结构中,除了树根外,每个结点都只有一个父结点(叫“双亲结点”)。
代码表示:
#define TElemType int
#define Tree_Size 100
//孩子表示法
typedef struct CTNode
{
int child; //链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
struct CTNode *next;
}*ChildPtr;
typedef struct
{
TElemType data; //结点的数据类型
ChildPtr firstchild; //孩子链表的头指针
}CTBox;
typedef struct
{
CTBox nodes[Tree_Size]; //存储结点的数组
int n, r; //结点数量和树根的位置
}CTree;
例如,使用孩子表示法存储图 1 (A),存储效果如图 2:
当算法中需要在树结构中频繁地查找某结点的父结点时,使用双亲表示法最合适。当频繁地访问结点的孩子结点时,双亲表示法就很麻烦,采用孩子表示法就很简单。
孩子表示法:
将树中的每个结点的孩子结点排列成一个线性表,用链表存储起来。对于含有 n 个结点的树来说,就会有 n 个单链表,将 n 个单链表的头指针存储在一个线性表中,这样的表示方法就是孩子表示法。如果结点没有孩子(例如叶子结点),那么它的单链表为空表。
代码表示:
#define TElemType int
#define Tree_Size 100
//孩子表示法
typedef struct CTNode
{
int child; //链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
struct CTNode *next;
}*ChildPtr;
typedef struct
{
TElemType data; //结点的数据类型
ChildPtr firstchild; //孩子链表的头指针
}CTBox;
typedef struct
{
CTBox nodes[Tree_Size]; //存储结点的数组
int n, r; //结点数量和树根的位置
}CTree;
例如,使用孩子表示法存储图 1 (A),存储效果如图 2:
使用孩子表示法存储的树结构,正好和双亲表示法相反,适用于查找某结点的孩子结点,不适用于查找其父结点。可以将两种表示方法合二为一,存储效果如图 3:
使用孩子表示法存储的树结构,正好和双亲表示法相反,适用于查找某结点的孩子结点,不适用于查找其父结点。可以将两种表示方法合二为一,存储效果如图 3:
其中孩子指针域,表示指向当前结点的第一个孩子结点,兄弟结点表示指向当前结点的下一个兄弟结点。
代码表示:
#define ElemType int
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild, *nextsibling;
}CSNode, *CSTree;
通过孩子兄弟表示法,普通树转化为了二叉树,所以孩子兄弟表示法又被称为“二叉树表示法”或者“二叉链表表示法”。
例如,用孩子兄弟表示法表示图 1 (A)的普通树,存储结果为: