1、树的定义:是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:<1>有且仅有一个特定的称为根(Root)的结点;<2>当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、......、Tm,其中每一个集合本身又是一颗树,并且称为根的子树。注:n>0时根结点是唯一的,不可能存在多个根节点。
2、结点分类:根的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。
3、结点间的关系:结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent)。同一个双亲的孩子之间互称兄弟(Sibling)。结点的祖先是从根结点到该结点所经分支上的所有结点,以某结点为根的子树中的任一结点都称为该结点的子孙。
4、树的其他相关概念:结点的层次(Level)从根开始定义起,根为第一层,根的孩子结点为第二层。双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。
如果将树中结点的各个子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
森林是m(m>=0)课互不相交的树的集合。对于树中的每一个结点而言,其子树的集合即为森林。
5、对比线性表与树的结构:线性表:第一个数据元素无前驱;最后一个数据元素无后继;中间元素一个前驱一个后继;
树结构:根结点无双亲唯一;叶节点无孩子,可以多个;中间结点一个双亲多个孩子。
6、树的抽象数据类型
ADT 树(tree)
Data
树是由一个根结点和若干课子树构成。树中结点具有相同数据类型及层次关系。
Operation
InitTree(*T):构造空树T。
DestroyTree(*T):销毁树T。
CreateTree(*T,definition):按defeinition中给出树的定义来构造树。
ClearTree(*T):若树存在,则将树T清为空树。
TreeEmpty(T):若T为空树,返回true,否则返回false。
TreeDepth(T):返回T的深度。
Root(T):返回T的根结点。
Value(T,cur_e):cur_e是树T中一个结点,返回此结点的值。
Assign(T,cur_e,value):给树T的结点cur_e赋值为value。
Parent(T,cur_e):若cur_e是树T的非根结点,则返回它的双亲,否则返回空。
LeftChild(T,cur_e):若cur_e是树T的非叶结点,则返回它的最左孩子,返回空。
RightSibling(T,cur_e):若cur_e有右兄弟,则返回它的右兄弟,否则返回空。
InsertChild(*T,*p,i,c):其中p指向树T的某个结点,i为所指结点p的度加上1,非空树c与T不相交,操作结果为插入c为树T中p指结点的第i课子树。
DeleteChild(*T,*p,i):其中p指向树T的某个结点,i为所指结点p的度,操作结果为删除T中p所指结点的第i课子树。
endADT
<1>双亲表示法:假设以一组连续空间存储树的结点,同时在每一个结点中,附设一个指示器指示其双亲结点在数组中的位置。一个data域表示的是数据域,存储结点的数据信息;parent域是指针域,存储该结点的双亲在数组中的下标。
双亲表示法的结点结构定义代码:
/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType; /*树结点的数据类型,目前暂定为整型*/
typedef struct PTNode /*结点结构*/
{
TElemType data; /*结点数据*/
int parent; /*双亲位置*/
}PTNode;
typedef struct /*树结构*/
{
PTNode nodes[MAX_TREE_SIZE]; /*结点数组*/
int r,n; /*根的位置和结点数*/
}PTree;
<2>孩子表示法:
多重链表表示法:因为树中每一个结点可能有多棵子树,则我们考虑用多重链表来表示,即每一个结点有多个指针域,其中每个指针指向一颗子树的根结点。
1)指针域的个数就等于树的度
缺点:如果树中的度相差很大时,很浪费空间,但是如果相差很小就可以充分利用空间。
2)每个结点指针域的个数等于该结点的度,专门取一个位置来存储结点指针域的个数。这是在上一中方法上的改进以实现按需分配空间。
优点:提高空间利用率
缺点:由于各个结点的链表是不同结构的,加上要维护结点的度的值,会给运算带来时间上的损耗。
孩子表示法就可以减少空指针的浪费也可以使结点结构相同。把每个结点的孩子结点排列起来以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
于是有两个结构:一个是孩子链表的孩子结点,其中child域是数据域,用来存储每个结点在表头数组中的下标。next是指针域,用来存储指向某结点的下一个孩子结点的指针。另一个是表头数组的表头结点,其中data是数据域,存储某结点的数据信息,firstchild是头指针域,存储该结点的孩子链表的头指针。
孩子表示法的结构定义代码:
/*树的孩子表示法结构定义*/
#define MAX_TREE_SIZE 100
typedef struct CTNode /*孩子结点*/
{
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct /*表头结构*/
{
TElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct
{
CTBox nodes[MAX_TREE_SIZE]; /*结点数组*/
int r,n; /*根的位置和结点数*/
}CTree;
因此,查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表,对于遍历整棵树,只需要对头结点的数组循环即可。
缺点:查询某结点的双亲需要遍历整棵树。可以综合双亲表示法和孩子表示法,用双亲孩子表示法。
<3>孩子兄弟表示法:任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
data是数据域,firstchild是指针域,存储该结点的第一个孩子的存储地址,rightsib是指针域,存储该结点的右兄弟结点的存储地址。
结构定义代码:
/*树的孩子兄弟表示法结构定义*/
typedef struct CSNode
{
TElemType data;
struct CSNode *firstchild,*rightsib;
}CSNode,*CSTree;
优点:查找某结点的某孩子方便,只需要通过firstchild找到此结点的长子,然后再通过长子结点的rightsib找到它的二弟,接着一直下去,直到找到具体的孩子。
缺点:查询某结点的双亲时比较麻烦,改进增加一个parent指针。