树的儿子链表表示法

树的另一种常用的表示方法就是儿子链表表示法。这种表示法用一个线性表来存储树的所有结点信息,称为结点表。对每个结点建立一个儿子表。儿子表中只存储儿子结点的地址信息,可以是指针,数组下标甚至内存地址。由于每个结点的儿子数目不定,因此儿子表常用单链表来实现,因此这种表示法称为儿子链表表示法。这种实现法与图的邻接表表示法类似。下图是一个儿子链表表示法的示意图。

图3 树的儿子链表实现

图3中儿子链表结构表示的树如图4所示,树中各结点存放于一个数组实现的表中,数组下标作为各结点的指针。每一个数组元素(即每一个结点)含有一个儿子表,在图3中儿子表是用单链表来实现的,当然也可以用其他表的实现方式来实现儿子表,比如说游标方式(静态链表)。但由于每个结点的儿子数目不确定,所以一般不用数组来实现儿子表,但可以用数组来实现结点表,就如图3所示。在图3中可以看到,位于结点表第一个位置的结点(未必是根结点)有两个儿子结点,从左到右的两个儿子结点分别位于结点表的第2和第3个位置。因为图3中的结点表用数组实现,所以结点的标号就是结点在结点表中的数组下标。如图4所示。

图4 图3中儿子链表所表示的树

为了指明树的根结点的位置,我们可以用一个变量Root记录根结点在结点表中的位置。有了根结点的位置,就可以利用儿子表依次找到树中所有的结点。

儿子链表表示的树的类型定义如下:

Type

{======================

NodeListType是一个元素为NodeType类型的线性表,其位置类型为TPosition,

NodeListType定义了结点表的类型;

ChildrenListType是一个元素为TPosition类型的线性表, ChildrenListType定义了儿子表的类型

=======================}

TPosition=....

ChildrenListType=...

NodeType=Record     {结点的类型}

           Label:LabelType; {结点的标号}

           Children:ChildrenListType;{结点的儿子表}

          End;

NodeListType=...

TreeType=record

         root:TPosition;  {记录树根在结点表中的位置}

         Node:NodeListType;  {结点表}

         end;

    其中NodeListType是一个元素为NodeType类型的线性表,其位置类型为TPosition,NodeListType定义了结点表的类型;ChildrenListType是一个元素为TPosition类型的线性表, ChildrenListType定义了儿子表的类型。以上类型定义并不考虑表的具体实现方式,如果假设结点表和儿子表都用单链表实现,则类型定义可以具体实现如下:

{儿子链表实现树的类型定义的一个具体实例,结点表和儿子表都用单链表实现}

Type

TPosition=^NodeType;       {结点表的位置类型}

ChildrenNodeType=record    {儿子表的结点项的类型}

                                           child:TPosition;   {指向儿子结点的位置指针}

                                            next:^ChildrenNodeType; {指向下一个儿子表项的指针}

                                          end;

NodeType=Record     {结点的类型定义为单链表}

                     Label:LabelType; {结点的标号}

                     Children:^ChildrenNodeType;{指向儿子表的指针}

                     Next:TPosition;

                    End;

TreeType=^NodeType;   {树的类型定义为结点指针类型}

注意以上的定义只是一种具体情况,实际应用中结点表和儿子表可能用数组、链表等任何一种表的实现方式实现。

下面我们就讨论结点表和儿子表都用链表实现的儿子链表的ADT操作的实现,之所以用单链表实现结点表和儿子表,是因为这样可以使ADT树的实现较为简洁和高效。至于结点表和儿子表的其他实现方式,可以类似地实现。

儿子链表实现的ADT树操作

函数 Leftmost_Child(v,T)

功能

这是一个求最左儿子结点的函数。函数值为树T中结点v的最左儿子的位置。当v是叶结点时,函数值为nil,表示结点v没有儿子。

实现

Function Leftmost_Child(v:TPosition;var T:TreeType):TPosition;

begin

 return(v^.Children);

end;

说明

返回v的儿子表的第一个位置的元素,就是v的最左儿子的位置指针,若v的儿子表为空则返回空结点nil。

复杂性

显然为O(1)。

函数 Right_Sibling(v,T)

功能

这是一个求右邻兄弟的函数,函数值为树T中结点v的右邻兄弟。当v没有右邻兄弟时,函数值为nil。

实现

Function Right_Sibling(v:TPosition;var T:TreeType):TPosition;

var

i:TPosition;

k:^ChildrenNodeType;

Find:Boolean;

begin

 i:=T; {i指向T的第一个结点}

 Find:=false;

 while (i<>nil)and(not Find) do

  begin

   k:=Locate(v,i^.Children); {在结点i的儿子表中定位结点v}

   if k<>nil then Find:=true;

            {如果在i的儿子表中找到结点v则Find=true;}

   i:=i^.next;

  end;

 if (Find)and(k^.next<>nil)

            {如果找到v在某个结点的儿子表中的位置

             并且v不是该结点的儿子表的最后一个元素}

    then return(k^.next^.child) {则返回v的右邻兄弟的指针}

    else return(nil);{否则返回空指针}

end;

说明

由于儿子链表只保存各个结点的儿子的信息,没有保存兄弟和父亲的信息,因此在查找v的兄弟时,必须先找到v的父亲结点,然后再找到v在父亲结点的儿子表中的位置,才能得到v的右邻兄弟。

复杂性

假设树有n个结点,在最坏情况下,结点v恰好是树的根结点,则循环要找遍T的所有结点,因此复杂性为O(n);在最好情况下,第一次循环就可以找到v的兄弟,因此最好情况下复杂性为O(1);平均情况下复杂性可以证明(证略)为O(n/2)。

函数 Parent(v,T)

功能

这是一个求父结点的函数,函数值为树T中结点v的父亲在结点表中的位置。当v是根结点时,函数值为nil,表示结点v没有父结点。

实现

Function Parent(v:TPosition;var T:TreeType):TPosition;

var

i:TPosition;

k:^ChildrenNodeType;

Find:Boolean;

begin

 i:=T; {i指向T的第一个结点}

 Find:=false;

 while (i<>nil)and(not Find) do

  begin

   k:=Locate(v,i^.Children); {在结点i的儿子表中定位结点v}

   if k<>nil then Find:=true else i:=i^.next;

            {如果在i的儿子表中找到结点v则Find=true否则i:=i^.next}

  end;

 if Find  then return(i) {则返回v的父亲的指针}

                               else return(nil);{否则返回空指针}

end;

说明

由于儿子链表只保存各个结点的儿子的信息,没有保存父亲的信息,因此在查找v的父亲时,必须依次扫描结点表,找到儿子表中含有结点v的结点。

复杂性

同Right_Sibling一样,最好情况为O(1),最坏情况为O(n),平均情况为O(n/2)。

函数 Create(i,v,T1,T2,..,Ti)

功能

这是一族建树过程。对于每一个非负整数i,该函数生成一个新树T,T的根结点v的标号为x,并令v有i个儿子,这些儿子从左到右分别为树T1,T2,..,Ti的根。当i=0时,v既是树根,又是树叶。

实现

Procedure Create(i:integer;var x:LabelType;var T1,T2,..,Ti,T:TreeType);

var

k:integer;

p:TPosition;

tmp:^ChildrenNodeType;

begin

 new(T);

T^.Label:=x;

T^.children:=nil;

T^.next:=nil;     {建立新树的根结点}

p:=T;             {p指向链表T的最后一个结点}

for k:=i downto 1 do  {用downto是为了保持子树Tk从左到右的顺序}

 if Tk<>nil then   {如果子树Tk不为空}

    begin

       p^.next:=Tk;{将链表Tk接在链表T之后}

   while p^.next<>nil do p:=p^.next; {p指向链表T的最后一个结点}

       new(tmp);

   tmp^.child:=Tk;

   tmp^.next:=T^.children;  {建立一个新的儿子表项}

   T^.children:=tmp;

   {将Tk的根结点在T中的位置插入T的根结点的儿子表的首部}

   end;

end;

说明

这个过程首先生成新树的根结点,其标号为x;然后对于每一个Tk,1≤k≤i,如果Tk不为空则将Tk的根结点链接到T的后面,为了实现这一步,可以设置一个指针p,p始终指向T的最后一个结点。然后将每个Tk加入到T的根结点的儿子表中。for循环用的是downto,是因为后面将Tk的根结点插入到T的根的儿子表的第一个位置上,因此只有从右向左插入Tk才可以保持子树从左到右的顺序。

复杂性

如果∑(Tk的结点数)=n,即生成的新树的结点总数为n,则复杂性显然为O(n)。

 

Label,Root和MakeNull比较简单,这里就不一一实现了。

<script src="../../../lib/footer.js" type="text/javascript"> </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值