数据结构(十八)树的定义与存储结构
一、树的定义
1.树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、...Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
2.结点分类:树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。
3.结点间关系:结点的子树的根称为该结点的孩子(Child),该结点称为孩子的双亲(Parent)。同一个双亲的孩子之间互称为兄弟(Sibling),结点的祖先是从根到该结点所经分支上的所有结点,反之,以某结点为根的子树中任一结点都称为该结点的子孙。
4.结点的层次(Level):从根开始定义起,根为第一层,根的孩子为第二层次。若某结点在第i层,则其子树的根就在第i+1层。其双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。
5.如果将树中结点的各子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。森林(Forest)是m(m>=0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。
二、树的存储结构
树的存储结构有四种不同的表示法:双亲表示法、孩子表示法、双亲孩子表示法、孩子兄弟表示法。
1.双亲表示法
以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点在数组中的位置。结点结构包括data数据域和parent指针域。约定根结点的位置与设置为-1。这种存储结构,可以根据结点的parent指针很容易找到它的双亲结点,所用的时间复杂度为O(1),直到parent为-1时,表示找到了树结点的根。但是,要知道结点的孩子是什么,只能遍历整个结构才行。
为了便于找到结点的孩子是什么,可以增加一个结点最左边孩子的域firstchild,叫它长子域,这样就很容易得到结点的孩子。如果没有孩子的结点,这个长子域就设置为-1。对于有2个孩子来说,知道了长子,另一个当然就是次子了。
为了便于体现各兄弟之间的关系,可以增加一个右兄弟域rightsib来记录每个结点右兄弟的下标,如果右兄弟不存在,就设置为-1。
2.孩子表示法
由于树中每个结点可能有多棵子树,可以考虑用多重链表,即每个结点有多个指针域,其中每个指针指向一棵子树的根结点,把这种方法叫做多重链表表示法。由于树的每个结点的读,也就是它的孩子个数是不同的,所以有两种方案来解决。
方案一:指针域的个数就等于树的度,也就是各个结点度的最大值。适用于树的各结点度相差很小的情况下,因为开辟的空间都被充分利用了。
方案二:每个结点指针域的个数等于该结点的度,专门取一个位置来存储结点指针域的个数。这种方法克服了浪费空间的缺点,对空间利用了是很高了,但是由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。
孩子表示法既可以减少空指针的浪费又能使结点结构相同。具体办法是,把每个结点的孩子结点排列起来,以单链表作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则次单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。表头数组的表结构包括data数据域和firstchild孩子链表头指针域。孩子链表的孩子结点的结构包括child数据域,用来存储某个结点在表头数组中的下标,还有next指针域,用来存储指向某结点的下一个孩子结点的指针。
3.双亲孩子表示法
对于孩子表示法来说,这样的结构对于查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。对于遍历整棵树也是很方便的,对头结点的数组循环即可。但是,要想知道某个结点的双亲是谁,就必须要遍历整棵树才行。可以把双亲表示法和孩子表示法综合一下形成双亲孩子表示法,即在头结点增加一个parent指针域即可。
4.孩子兄弟表示法
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,可以将结点结构设计成这样:data数据域,firstchild指针域用来存储该结点的第一个孩子结点的存储地址,rightsib指针域用来存储该结点的右兄弟结点的存储地址。
孩子兄弟表示法,给查找某个结点的某个孩子带来了方便,只需要通过firstchild找到次结点的长子,再通过长子结点的right找到它的二弟,接着一直下去,直到找到具体的孩子。