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

图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>