树形结构属非线性结构,常用的树性结构有树和二叉树。树形结构可以表示元素或节点之间的一对多关系。
树
树是由n(n≥0)个节点组成的有限集合(记为T)。如果n=0,它是一棵空树,这是树的特例。如果n>0,这n个节点中存在(且仅存在)一个节点作为树的根节点(简称为根root),其余节点可以分为m(m≥0)个互不相交的有限集T1,T2,…,Tm,其中每个子集本身又是一颗符合本定义的树,称为根的子树。
树的定义是递归的,因为在树的定义中又用到树的定义。它刻画了树的固有特性,即一棵树是由若干棵互不相交的子树构成,而子树又由更小的若干棵子树构成。
树是一种非线性数据结构,具有以下特点:它的每一个节点可以有零个或多个后继节点,但有且只有一个前驱节点(根节点除外);这些数据节点按分支关系组织起来,清晰地反映了数据元素之间的层次关系。可以看出,数据元素之间存在一对多的关系。
ADT Tree
{
数据对象:D={ai|1≤i≤n,n≥0,ai 为ElemType类型}//ElemType是自定义的类型标识符
数据关系:R={< ai,aj>| ai,aj∈D,1≤i≤n,1≤j≤n,其中每个元素只有一个前驱节点(除根节点),可以有零个或多个后继节点,有且仅有一个元素没有前驱节点}
基本运算:初始化树,销毁树,求双亲节点,求子孙节点,插入或删除节点,遍历树
}
树的逻辑表示方法:能够正确表示出树中数据元素之间的层次关系。
- 树形表示法:用一个圆圈表示一个节点,圆圈内的符号代表该节点的数据信息,节点之间的关系通过连线表示。虽然每条连线上都不带有箭头(即方向),但它仍然是有向的,其隐含方向从上向下,即连线的上方节点是下方节点的前驱节点,下方节点是上方节点的后继节点。它的直观形象是一颗倒置的树(树根在上,树叶在下)。
- 文氏图表示法:每棵树对应一个圆圈,圆圈内包含根节点和子树的圆圈,同一个根节点下的各子树对应的圆圈是不能相交的。节点之间的关系通过圆圈的包含来表示。
- 凹入表示法:每棵树的根对应着一个条形,子树的根对应着一个较短的条形,且树根在上,子树的根在下,同一个根下的各子树的根对应的条形长度是一样的。
- 括号表示法:每棵树对应一个由根作为名字的表,表名放在表的左边,表是由在一个括号里的各子树对应的表组成的,各子树对应的表之间用逗号分开。用这种方法表示的树中,节点之间的关系是通过括号的嵌套表示的。
树的基本术语:
- 节点的度与树的度:树中某个节点的子树的个数称为该节点的度。树中各节点的度的最大值称为树的度,通常将度为m的树称为m次树。
- 分支节点与叶子节点:度不为零的节点称为非终端节点,又叫分支节点。度为零的节点称为终端节点或叶子节点。在分支节点中,每个节点的分支数就是该节点的度。如度为1的节点,分支数为1,称为单分支节点;度为2的节点,分支数为2,称双分支节点。
- 路径与路径长度:对于任意两个节点ki和kj,若树中存在一个节点序列ki,ki1,ki2,…,kin,kj,使得序列中除ki外的任一节点都是其在序列中的前一节点的后继节点,则称该节点序列为ki到kj的一条路径,用路径所通过的节点序列(ki,ki1,ki2,…,kin,kj)表示这条路径。路径长度等于路径所通过的节点数目减1(即路径上分支数目)。可见,路径就是从ki出发“自上而下”到达kj所通过的树中节点序列。显然,从根到树中其余节点均存在一条路径。
- 孩子节点、双亲节点、兄弟节点:在一棵树中,每个节点的后继节点,被称为该节点的孩子节点(或子女节点)。相应的,该节点被称作其孩子节点的双亲节点(或父母节点),具有同一双亲的孩子节点互为兄弟节点。进一步推广,可以把每个节点的所有子树中的节点称作该节点的子孙节点,从树根节点到达该节点的路径上经过的所有节点(除了该节点本身外)称作该节点的祖先节点。
- 节点的层次和树的高度:树中每个节点都处在一定的层次上。节点的层次从树根开始定义,根节点为第一层,其孩子节点为第二次,以此类推,一个节点的层次为其双亲节点所在的层次加1。树中节点的最大层次称为树的高度(或树的深度)。
- 有序树和无序树:若树中各节点的子树是按照一定的次序从左向右安排的,且相对次序是不能随意变换的,则称为有序树,否则称为无序树。
- 森林:n(n>0)个互不相交的树的集合称为森林。森林的概念与树的概念十分相近,只要把树的根节点删去就成了森林。反之把n棵独立的树加上根节点,森林就成了树。
树的性质:
- 树中的节点数等于所有节点的度数加1。
- 度为m的树中第i层上至多有mi-1个节点(i≥1)。推广:当一颗m次树的第i层有mi-1个节点(i≥1)时,称该层是满的,若一颗m次树的所有叶子节点在同一层,除该层外其余每一层都是满的,称该树为满m次树。显然,满m次树是所有相同高度的m次树中节点总数最多的树。也就是说,对于n个节点,构造的m次树为满m次树或者接近满m次树,此时树的高度最小。
- 高度为h的m次树至多有(mh-1)/(m-1)个节点。满m次树的另一种定义:当一棵高度为h的m次树上的节点树等于(mh-1)/(m-1)时,则称该树为满m次树。
- 具有n个节点的m次树的最小高度为(logm(n(m-1)+1))向上取整。
树的基本运算:
- 寻找满足某种特定关系的节点
- 插入或删除某个节点
- 遍历树中每个节点:树的遍历运算是按某种方式访问树中的每一个节点且每个节点只被访问一次。树的遍历运算主要有以下三种:
先根遍历:访问根节点,再按照从左到右的次序先根遍历根节点的每一颗子树(递归)
后根遍历:按照从左到右的次序后根遍历根节点的每一颗子树,再访问根节点(递归)
层次遍历:从根节点开始,从上到下、从左到右访问树中每一个节点。
树的存储结构:树的存储结构要求既要存储节点的数据元素本身,又要存储节点之间的逻辑关系。常用的有以下三种存储结构:
双亲存储结构:顺序存储结构,用一组连续空间存储树的所有节点,同时在每个节点中附设一个伪指针指示其双亲节点的位置。类型定义如下:
typedef struct{
Elemtype data; //存放节点的值
int parent; //存放双亲的位置,根节点伪指针值为-1
}PTree[MaxSize];
该结构利用节点只有唯一双亲的性质。便于求双亲节点,求孩子节点需要遍历整个结构。
孩子链存储结构:此存储结构每个节点不仅包含数据值,还包含指向其所有孩子节点的指针。由于树中每个节点的子树个数(节点的度)不同,若按各个节点的度设计变长结构,则每个节点的孩子节点指针域个数增加使算法实现非常麻烦。孩子链存储结构按树的度(所有节点度的最大值)设计节点的孩子节点指针域个数。类型定义如下:
typedef struct node
{ Elemtype data; //节点的值
struct node *sons[MaxSons]; //指向孩子节点,MaxSons为树的度
}TSonNode;
便于查找孩子节点,查找双亲节点费时。树的度较大时,存在较多空指针域。
孩子兄弟链存储结构:为每个节点设计3个域,一个数据元素域,一个指向该节点的第一个孩子节点的指针域,一个指向该节点的下一个兄弟节点的指针域。类型定义如下
typedef struct tnode
{ Elemtype data; //节点的值
struct tnode *hp; //指向兄弟
struct tnode *vp; //指向孩子节点
}TSBNode;//兄弟节点和孩子节点是有序的,不能混淆
此结构实际上把该树转换为二叉树的存储结构,该结构可方便地实现树与二叉树的相互转换。缺点查找双亲节点需要从根节点开始起遍历查找。