此结构是将二叉树的所有结点,按照一定的次序,存储到一片连续的存储单元中。因此,必须将结点排成一个适当的线性序列,使得结点在这个序列中的相应位置能反映出结点之间的逻辑关系。这种结构特别适用于近似满二叉树。
在一棵具有n个结点的近似满二叉树中,我们从树根起,自上层到下层,逐层从左到右给所有结点编号,就能得到一个足以反映整个二叉树结构的线性序列,如图6所示。其中每个结点的编号就作为结点的名称。

图6 近似满二叉树的结点编号
因此,我们可以对树的类型作如下说明:
TPosition=integer; TreeType=record NodeCount:integer; {树的总结点数} NodeList:array [1..MaxNodeCount] of LabelType; {存储结点的数组} end; |
将数组下标作为结点名称(编号),就可将二叉树中所有结点的标号存储在一维数组中。例如,图6中的二叉树的顺序存储结构如图7所示。

图7 近似满二叉树的顺序存储结构
在二叉树的这种表示方式下,各结点之间的逻辑关系是隐含表示的。近似满二叉树中,除最下面一层外,各层都充满了结点。可能除最底层外,每一层的结点个数恰好是上一层结点个数的2倍。因此,从一个结点的编号就可推知其父亲,左、右儿子,和兄弟等结点的编号。例如,对于结点i我们有:
-
仅当i=1时,结点i为根结点;
-
当i>1时,结点i的父结点为
i/2
;
-
结点i的左儿子结点为2i;
-
结点i的右儿子结点为2i+1;
-
当i为奇数且不为1时,结点i的左兄弟结点为i-1;
-
当i为偶数时,结点i的右兄弟结点为i+1。
由上述关系可知,近似满二叉树中结点的层次关系足以反映结点之间的逻辑关系。因此,对近似满二叉树而言,顺序存储结构既简单又节省存储空间。
对于一般的二叉树,采用顺序存储时,为了能用结点在数组中的位置来表示结点之间的逻辑关系,也必须按近似满二叉树的形式来存储树中的结点。显然,这将造成存储空间的浪费。在最坏情况下,一个只有k个结点的右单枝树却需要2k-1个结点的存储空间。例如,只有3个结点的右单枝树,如图8(a)所示,添上一些实际不存在的虚结点后,成为一棵近似满二叉树,相应的顺序存储结构如图8(b)所示。

图8 一般二叉树的顺序存储结构
下面我们就用这种顺序存储结构来实现二叉树的常用操作。在这种表示法中,整数0表示空结点∧。对于非近似满二叉树,我们将其补为近似满二叉树,并规定一个特殊的标号&,用来表示补充的结点,&要根据标号的具体类型来确定。
顺序存储结构实现ADT二叉树的操作
函数 Parent(v,T); 功能
这是一个求父结点的函数,函数值为树T中结点v的父亲。当v是根结点时,函数值为∧,表示结点v没有父结点。
实现
Function Parent(v:TPosition;var T:TreeType):TPosition;
begin
return(v div 2);
end;
说明
根据这种表示法,我们知道,当i>1时,结点i的父结点为 i/2 。
复杂性
显然为O(1)。
|
函数 Left_Child(v,T); 功能
这是一个求左儿子结点的函数。函数值为树T中结点v的左儿子。当结点v没有左儿子时,函数值为∧。
实现
Function Left_Child(v:TPosition;var T:TreeType):TPosition;
begin
if (2*v>T.NodeCount)or(T.NodeList[2*v]=&) then return(0)
else return(2*v);
end;
说明
如果结点v的左儿子存在,则其下标为2*v。
复杂性
显然为O(1)。
|
函数 Right_Child(v,T); 功能
这是一个求右儿子结点的函数。函数值为树T中结点v的右儿子。当结点v没有右儿子时,函数值为∧。
实现
Procedure Right_Child(v:TPosition;var T:TreeType):TPosition;
begin
if (2*v+1>T.NodeCount)or(T.NodeList[2*v+1]=&) then return(0)
else return(2*v+1);
end;
说明
如果结点v的左儿子存在,则其下标为2*v+1。
复杂性
显然为O(1)。
|
函数 Create(x,Left,Right,T); 功能
这是一个建树过程。该函数生成一棵新的二叉树T,T的根结点标号为x,左右儿子分别为Left和Right。
实现
Procedure Create(x:LabelType;var Left,Right,T:TreeType);
begin
T.NodeList[1]:=x;
T.NodeCount:=Left.NodeCount+Right.NodeCount+1;
h_left:=cal(Left.NodeCount);
h_right:=cal(Right.NodeCount);{分别计算Left和Right的高度}
if h_left>h_Right then h:=h_left else h:=h_right;{新树T的高度为h+1}
for i:=Left.NodeCount+1 to (1 shl (h+1))-1 do Left.NodeList[i]:=&;
Left.NodeCount:=(1 shl (h+1))-1;
{将Left补成高度为h的满二叉树;其中shl是左移位运算,用来计算2的幂}
if h_right<h then
begin
for i:=Right.NodeCount+1 to (1 shl h)-1 do Right.NodeList[i]:=&;
Right.NodeCount:=(1 shl h)-1;
end;
{若Right的高度小于h,则将Right补成高度为h-1的满二叉树}
{=======
对于Left中编号为i的结点v,它在新树T中的编号为2h +i,其中h= log2(i+1)-1 ;对于Right中编号为i的结点v,它在新树T中的编号为2h+1+i,其中h= log2(i+1)-1 。
=======}
for i:=1 to Left.NodeCount do
T.NodeList[(1 shl cal(i))+i]:=Left.NodeList[i];
{计算出Left中编号为i的结点在T中的位置,将其复制到T中}
for i:=1 to Right.NodeCount do
T.NodeList[(1 shl (cal(i)+1))+i]:=Right.NodeList[i];
{计算出Right中编号为i的结点在T中的位置,将其复制到T中}
end;
其中cal(i)用来计算含有i个结点的近似满二叉树T的高度,cal(i)= log2(i+1)-1 ,可以实现如下:
Function cal(i:integer;):integer;
var
x:real;
begin
x:=log2(i+1)-1;
if x=int(x) then return(x) else return(int(x)+1); {向上取整}
end;
其中log2(n)计算实数n以2为底的对数。
说明
在顺序存储的结构下,建立一棵新的二叉树的过程比较复杂。我们首先给出以下几个命题:
命题一
一棵高度为h的满二叉树有2h+1-1个结点。
证明:
满二叉树的第i层有2i个结点,i=0,1,2,...,h(树根为第0层),因此高度为h的满二叉树有20+21+..+2h = 2h+1-1个结点。
推论一
我们从树根起,自上层到下层,逐层从左到右给二叉树的所有结点编号,如图6所示,则近似满二叉树的第h层的从左到右第k个结点的编号为2h+k-1。
证明:
由于是近似满二叉树,所以第0层到第h-1层是满二叉树,根据命题一知道共有2h-1个结点,因此第h层的从左到右第1个结点的编号为2h-1+1,第h层的从左到右第k个结点的编号为2h-1+k。
推论二
一棵有n个结点的近似满二叉树,高度为 log2(n+1)-1 ,其中 是天花板符号, x 表示大于等于x的最小整数。
证明:
有n个结点的近似满二叉树,若其高度为h,则满足2h-1<n≤2h+1-1,化简得 log2(n+1)-1 ≤ h < [log2(n+1)-1]+1,即h= log2(n+1)-1 。
推论三
在近似满二叉树T中,设编号为i的结点处于T的第h层从左到右第k个位置上,则h= log2(i+1)-1 ,k=i-(2h-1)。
证明:
我们先不考虑编号大于i的结点,则前i个结点构成一棵近似满二叉树,根据推论二知其层数为h= log2(i+1)-1 ,又因为第0层到第h-1层是满二叉树,根据命题一知道共有2h-1个结点,所以编号为i的结点处于第h层的第k=i-(2h-1)个位置上。
我们用T(h,k)表示树T的第h层的第k个结点,则有下列命题:
命题二
Left(h,k)=T(h+1,k),Right(h,k)=T(h+1,k+2h),其中Left和Right分别是树T的根结点的左右子树。
证明:
显然。
我们用N(v,T)表示结点v在生成的新树T中的编号,则有下列命题:
命题三
对于Left中编号为i的结点v,N(v,T)=2h +i,其中h= log2(i+1)-1 ;对于Right中编号为i的结点v,N(v,T)=2h+1+i,其中h= log2(i+1)-1 。
证明:
在Left中编号为i的结点,根据推论三,他处于Left的第h= log2(i+1)-1 层,第k=i-(2h-1)个位置上。根据命题二该结点处于新树T的第h+1层第k个位置上,所以根据推论一,它在二叉树T中的编号为2h+1+k-1=2*2h+[i-(2h-1)]-1=2h+i。结点在Right中的情况同理可证。
有了命题三,我们就可以完成建树的过程。算法如下:
-
根据推论二计算Left和Right的高度,分别为hLeft和hRight ; -
设h=max{hLeft,hRight},新树T的高度就为h+1; -
将Left补成高度为h的满二叉树; -
若hRight<h,则将Right补成高度为h-1的满二叉树; -
依次扫描Left的每一个结点,根据命题三计算出Left中编号为i的结点在T中的位置,将其复制到T中; -
依次扫描Right的每一个结点,根据命题三计算出Right中编号为i的结点在T中的位置,将其复制到T中;
具体程序见前文的实现。
复杂性
算法的主要时间花在扫描和赋值结点上,设新树有n个结点,则复杂性为O(n)。
|