设T是一棵树,表示T的一种最简单的方法是用一个一维数组存储每个结点,数组的下标就是结点的位置指针,每个结点中有一个指向各自的父亲结点的数组下标的域,这样可使Parent操作非常方便。类型定义如下:
Type
TPosition=integer; {结点的位置类型为整型}
NodeType=Record
Label:LabelType; {该结点的标号}
Parent:TPosition; {该结点的父亲的数组下标,对于根结点该域为0}
End;
TreeType=Record
NodeCount:integer; {树的结点的总数目}
Node:Array [1..MaxNodeCount] of NodeType;{存储树的结点}
End;
由于树中每个结点的父亲是唯一的,所以上述的父亲数组表示法可以唯一地表示任何一棵树。在这种表示法下,寻找一个结点的父结点只需要O(1)时间。在树中可以从一个结点出发找出一条向上延伸到达其祖先的道路,即从一个结点到其父亲,再到其祖父等等。求这样的道路所需的时间正比于道路上结点的个数。在树的父亲数组表示法中,对于涉及查询儿子和兄弟信息的树操作,可能要遍历整个数组。为了节省查询时间,可以规定指示儿子的数组下标值大于父亲的数组下标值,而指示兄弟结点的数组下标值随着兄弟的从左到右是递增的。
父亲数组实现的ADT树操作
函数 Parent(v,T)
功能
这是一个求父结点的函数,函数值为树T中标号为v的结点的父亲。当v是根结点时,函数值为0,表示结点v没有父结点。
实现
Function Parent(v:TPosition;var T:TreeType):TPosition;
begin
return(T.Node[v].Parent);
end;
说明
由于每个结点都有一个域存储了其父亲结点的标号(数组下标),因此Parent操作实现非常简单。
复杂性
显然为O(1)。
函数 Leftmost_Child(v,T)
功能
这是一个求最左儿子结点的函数。函数值为树T中标号为v的结点的最左儿子。当v是叶结点时,函数值为0,表示结点v没有儿子。
实现
Function Leftmost_Child(v:TPosition;var T:TreeType):TPosition;
begin
i:=v+1;
while (i<=T.NodeCount)and(T.Node[i].Parent<>v) do inc(i);
if i=T.NodeCount+1 then return(0)
else return(i);
end;
说明
因为没有保存每个结点的子结点的信息,因此只能依次扫描每个结点,根据我们的约定,子结点一定排在父结点的后面,且兄弟结点的下标从左到右依次递增,因此第一次遇到的父亲是n的结点就是n的最左结点。
复杂性
该算法的复杂性取决于while循环。若设T.NodeCount=n,显然,在最坏情况下循环执行n-v次,最好情况下执行1次,平均情况下执行
(n-v)/2
,所以无论何种情况下,复杂性都为O(n)。
函数 Right_Sibling(v,T)
功能
这是一个求右邻兄弟的函数,函数值为树T中结点v的右邻兄弟。当v没有右邻兄弟时,函数值为0。
实现
Function Right_Sibling(v:TPosition;var T:TreeType):TPosition;
begin
i:=v+1;
while (i<=T.NodeCount)and(T.Node[i].Parent<>T.Node[v].Parent) do
inc(i);
if i=T.NodeCount+1 then return(0)
else return(i);
end;
说明
依次搜索排在v之后的结点,遇到第一个与v有相同父结点的结点就是v的右邻兄弟。
复杂性
同Leftmost_Child一样,该函数复杂性为O(n),其中n为树的总结点数。
函数 Create(i,x,T1,T2,..,Ti)
功能
这是一族建树过程。对于每一个非负整数i,该函数生成一棵新树T,T的根结点是标号为x的新结点v,并令v有i个儿子,这些儿子从左到右分别为树T1,T2,..,Ti的根。当i=0时,v既是树根,又是树叶。
实现
Procedure Create(i:integer;var x:LabelType;var T1,T2,..,Ti,T:TreeType);
var
k,j,father:integer;
begin
with T do
begin
NodeCount:=1;
Node[1].Label:=x;
Node[1].Parent:=0; {生成根结点}
for k:=1 to i do
if Tk.NodeCount<>0 then
begin
inc(NodeCount);
Node[NodeCount]:=Tk.Node[1];
Node[NodeCount].Parent:=1;{修改Tk的根结点的父亲使其指向T的根}
father:=NodeCount; {记下Tk的根结点的标号}
for j:=2 to Tk.NodeCount do
beign
inc(NodeCount);
Node[NodeCount]:=Tk.Node[j];
Node[NodeCount].Parent:=Node[NodeCount].Parent+father-1;
{修改Tk的每一个非根结点的父亲,因为Tk的根结点的位置改变了}
end;
end;
end;
end;
说明
这个过程首先生成一个新结点,其中存储的数据为x,新结点在数组T的第一个元素位置上;然后对于每一个Tk,1≤k≤i,如果Tk不为空则将Tk的每一个结点复制到T中,同时修改Tk的每一个元素的父结点,因为Tk的根结点在T中的下标已经不是1了,而是father,因此Tk的每一个元素的父结点的下标都应给增加一个增量father-1。
复杂性
如果∑(Tk的结点数)=n,即生成的新树的结点总数为n,则复杂性为O(n)。
Label,Root和MakeNull比较简单,这里就不一一实现了。
我们可以看出,用父亲数组实现树,比较容易实现Parent运算,但是对于Leftmost_Child和Right_Sibling则效率不高,而且这种实现方法很占用内存。但实现上比较简单。
<script src="../../../lib/footer.js" type="text/javascript"> </script>