顺序二叉树的压缩存储算法
摘要:顺序二叉树占用空间大而又插入删除不便,讨论二叉树压缩存储的文章并不多见。本文讨论了顺序存储二叉树的不同存储方法,着重介绍了两种有效压缩存储空间的算法。一种是基于结点的孩子树数目,另一种是基于叶结点的位置,分别称为孩子树存储法和叶结点位置法。
06年5月程序员(初级)考试第37题:
一棵二叉树如下图所示。若采用顺序存储结构,即用一维数组元素存储该二叉树中的结点(概结点的下标为1,若某结点的下标为i,则其左孩子位于下标 2i 处,右孩子位于下标 2i+1 处,则该数组的大小至少为__;

通常顺序二叉树的存储方法有两种:一种是补成满二叉树,如上图,假设结点的内容为a,b,c,d,e,f,上面的二叉树可记做:
a[15]=[a,b,c,0,0,d,e,0,0,0,0,0,0,0,f]
另一种是"info+位置",即把结点的信息和补成满二叉树时层次遍历的序号,上图中二叉树可记做:
a[12]=[a,1,b,2,c,3,d,6,e,7,f,14]
其中,1,2……分别表示其前面的字母代表的结点在该二叉树为满二叉树时的层次遍历序号。
若只讨论使用“最小存储空间”,有没有更好的存储方法呢?有的。下面介绍两种算法思路并给出相关算法。
(1) 孩子树信息存储法
这种算法的基本思路是:一个结点的信息可分为结点的值(info)和其左、右子树信息(CS,childState)。我们用两个位的四个值(0,1,2,3)来表示一个结点子树的四种不同情况--无子树、只有左子树、只有右子树、有双子树。
这样,对于一结点的数值类型为int型的数来说,我们需要有8+2位即可表示其全部信息。每四个非空结点的CS可合并成一个byte;从高位到低位依次用两位来保存第1、2、3、4个非空结点的子树情况,上图中的二叉树可存储为:
a[10]=[a,b,c,d,204,e,f,0,0,4]
其中,a[4]=204=(3<<6)+(0<<4)+3<<2+(0<<0)=11001100B
a[9]=4的原理相同。
为了简化算法和节约空间,我们把表示孩子树信息的byte保存在另一个数组中。
下面的算法将存储二叉树的数组a[]压缩为两个数组:存储结点info的b[]和存储结点孩子树信息的c[]。
/* a[]-- the info of the node2
i-- the stratum of the tree3
j--the result4
n--the node order in each stratum5
*/6
#include <stdio.h>7
void main()8
{9
int n;10
int flag;11
int word=0;12
int b[41]={0};13
int j=0;14
int c[11]={0};15
int i=1;16
int infonub=1;17
int t=0;18
int a[64]={0,6,3,6,0,22,5,6,0,0,0,19
0,7,0,81,42,0,0,0,0,0,20
0,0,0,24,25,0,0,12,7,6,21
0,0,0,0,0,0,0,0,0,0,22
0,0,0,0,0,0,0,0,0,0,23
0,0,0,0,0,0,0,0,0,0,24
0,0,0,25
};26
flag=0;27
clrscr();28
printf("the lengh of a[16] is %d\n",strlen(a[16]));29
if (a[1]!=0)30
{b[0]=a[1];j++;}31
while(1)32
{33
word=word*2;34
for(n=1;n<=(1<<(i-1));n++)35
{36
if(a[(1<<(i-1))+n-1]!=0)37
{38
infonub=infonub%4;39
if (a[2*((1<<(i-1))+n-1)]!=0)40
{41
b[j]=a[2*((1<<(i-1))+n-1)];j++;42
flag=1;43
if(a[2*((1<<(i-1))+n-1)+1]!=0)44
{45
b[j]=a[2*((1<<(i-1))+n-1)+1];j++;46
flag=3;47
}48
else49
word++;50
}51
else52
{53
if(a[2*((1<<(i-1))+n-1)+1]!=0)54
{55
b[j]=a[2*((1<<(i-1))+n-1)+1];56
j++;57
flag=2;58
word++;59
}60
else61
{62
flag=0;63
word=word+2;}64
}65
c[t]=(c[t]<<2)+flag;66
if(infonub==0)67
t++;68
infonub++;69
}70
}71
if(word==2*(1<<(i-1)))72
break;73
i++;74
}75
if(!infonub) infonub=4;76
c[t]=c[t]<<2*(infonub-4);77

78
for(j=0;j<20;j++)79
printf("%d\n",b[j]);80
printf("\n");81
for(j=0;j<5;j++)82
printf("%d\n",c[j]);83

84
}上面的算法大大压缩了存储空间,无论二叉树的数据量和结点info有多大,每个非空结点固定地对应CS的2个bit,占用的存储空间为
存储空间=结点INFO的大小*非空结点的数目+非空结点的数目/4
(2) 叶结点位置法
孩子树算法节约空间,但算法复杂,而且查找不便。而叶结点位置法原理同上,但算法简单了不少,而且通过简单的排序便可实现查找。
这种算法的基本思路是:二叉树的任意一个结点都是叶结点的父结点/祖父结点。树中任一结点所对应的info都可以根据叶子结点在满二叉树中的序号计算出来。
对于上面提到的程序员试题,其用叶结点位置法压缩的结果是:
a[6]=[a,b,c,d,e,f];
b[3]=[2,6,14]
b[]中的2,6,14分别为叶结点b,d,f在将该二叉树补充为满二叉树时的层次遍历序号。
压缩算法在此不再啰嗦,下面的算法实现了将压缩存储的二叉树还原成出非空结点的序号。根据叶结点的位置信息a[]计算出二叉树中非空结点的位置信息,并按倒序存储在数组b[]中。结合结点info便可还原出二叉树(本例中没有提供info信息)。
#include <stdio.h>2
void main()3
{4
int a[6]={0,4,5,13,24,60};5
int b[15]={0};6
int j=0;7
int left,middle,right,k;8
int n=5;9

10
clrscr();11
b[0]=a[n];12
j++;13
while(n>1)14
{15
a[n]=a[n]/2;16
a[0]=a[n];17
left=1;right=n-1;18
while(left<=right)19
{20
middle=(left+right)/2;21
if (a[0]<a[middle])22
right=middle-1;23
else24
left=middle+1;25
}26
if (a[n]==a[left-1]&&left!=1)27
{28
n--;29
}30
else31
{32
for(k=n-1;k>=left;k--)33
a[k+1]=a[k];34
a[left]=a[0];35
}36
b[j]=a[n];37
j++;38

39
}40
for(j=0;j<15;j++)41
printf("%d\n",b[j]);42
}范晨鹏
------------------
软件是一种态度
成功是一种习惯
这是我的页面头部



浙公网安备 33010602011771号