二叉树的顺序存储结构

此结构是将二叉树的所有结点,按照一定的次序,存储到一片连续的存储单元中。因此,必须将结点排成一个适当的线性序列,使得结点在这个序列中的相应位置能反映出结点之间的逻辑关系。这种结构特别适用于近似满二叉树

在一棵具有n个结点的近似满二叉树中,我们从树根起,自上层到下层,逐层从左到右给所有结点编号,就能得到一个足以反映整个二叉树结构的线性序列,如图6所示。其中每个结点的编号就作为结点的名称。

图6 近似满二叉树的结点编号

因此,我们可以对树的类型作如下说明:

TPosition=integer;

TreeType=record

           NodeCount:integer;      {树的总结点数}

           NodeList:array [1..MaxNodeCount] of LabelType; {存储结点的数组}

         end;

将数组下标作为结点名称(编号),就可将二叉树中所有结点的标号存储在一维数组中。例如,图6中的二叉树的顺序存储结构如图7所示。

图7 近似满二叉树的顺序存储结构


    在二叉树的这种表示方式下,各结点之间的逻辑关系是隐含表示的。近似满二叉树中,除最下面一层外,各层都充满了结点。可能除最底层外,每一层的结点个数恰好是上一层结点个数的2倍。因此,从一个结点的编号就可推知其父亲,左、右儿子,和兄弟等结点的编号。例如,对于结点i我们有:

  1. 仅当i=1时,结点i为根结点;

  2. 当i>1时,结点i的父结点为i/2

  3. 结点i的左儿子结点为2i;

  4. 结点i的右儿子结点为2i+1;

  5. 当i为奇数且不为1时,结点i的左兄弟结点为i-1;

  6. 当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中的情况同理可证。

 

有了命题三,我们就可以完成建树的过程。算法如下:

  1. 根据推论二计算Left和Right的高度,分别为hLeft和hRight

  2. 设h=max{hLeft,hRight},新树T的高度就为h+1;

  3. 将Left补成高度为h的满二叉树;

  4. 若hRight<h,则将Right补成高度为h-1的满二叉树;

  5. 依次扫描Left的每一个结点,根据命题三计算出Left中编号为i的结点在T中的位置,将其复制到T中;

  6. 依次扫描Right的每一个结点,根据命题三计算出Right中编号为i的结点在T中的位置,将其复制到T中;

具体程序见前文的实现。

复杂性

算法的主要时间花在扫描和赋值结点上,设新树有n个结点,则复杂性为O(n)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值