1、第5章 数组和广义表,数组是一种人们非常熟悉的数据结构,几乎所有的程序设计语言都支持这种数据结构或将这种数据结构设定为语言的固有类型。数组这种数据结构可以看成是线性表的推广。 科学计算中涉及到大量的矩阵问题,在程序设计语言中一般都采用数组来存储,被描述成一个二维数组。但当矩阵规模很大且具有特殊结构(对角矩阵、三角矩阵、对称矩阵、稀疏矩阵等),为减少程序的时间和空间需求,采用自定义的描述方式。 广义表是另一种推广形式的线性表,是一种灵活的数据结构,在许多方面有广泛的应用。,5.1 数组的定义,数组是一组偶对(下标值,数据元素值)的集合。在数组中,对于一组有意义的下标,都存在一个与其对应的值。一维
2、数组对应着一个下标值,二维数组对应着两个下标值,如此类推。数组是由n(n1)个具有相同数据类型的数据元素a1,a2,an组成的有序序列,且该序列必须存储在一块地址连续的存储单元中。 数组中的数据元素具有相同数据类型。 数组是一种随机存取结构,给定一组下标,就可以访问与其对应的数据元素。 数组中的数据元素个数是固定的。,5.1.1 数组的抽象数据类型定义,1. 抽象数据类型定义 ADT Array 数据对象:ji= 0,1,bi-1 , 1,2, ,n ; D = aj1j2jn | n0称为数组的维数,bi是数组第i维的长度,ji是数组元素第i维的下标,aj1j2jnElemSet 数据关系:
3、R = R1, R2, , Rn Ri=|0jkbk-1 , 1kn且ki,0jibi-2, aj1j2 ji+1jnD 基本操作: ADT Array,由上述定义知,n维数组中有b1b2 bn个数据元素,每个数据元素都受到n维关系的约束。 2. 直观的n维数组以二维数组为例讨论。将二维数组看成是一个定长的线性表,其每个元素又是一个定长的线性表。设二维数组A=(aij)mn,则A=(1,2,p) (p=m或n) 其中每个数据元素j是一个列向量(线性表) :j =(a1j ,a2j ,amj) 1jn 或是一个行向量: i =(ai1 ,ai2 ,ain) 1im 如图5-1所示。,5.2 数组
4、的顺序表示和实现,数组一般不做插入和删除操作,也就是说,数组一旦建立,结构中的元素个数和元素间的关系就不再发生变化。因此,一般都是采用顺序存储的方法来表示数组。问题:计算机的内存结构是一维(线性)地址结构,对于多维数组,将其存放(映射)到内存一维结构时,有个次序约定问题。即必须按某种次序将数组元素排成一列序列,然后将这个线性序列存放到内存中。二维数组是最简单的多维数组,以此为例说明多维数组存放(映射)到内存一维结构时的次序约定问题。,通常有两种顺序存储方式 行优先顺序(Row Major Order) :将数组元素按行排列,第i+1个行向量紧接在第i个行向量后面。对二维数组,按行优先顺序存储的
5、线性序列为:a11,a12,a1n, a21,a22,a2n , am1,am2,amn PASCAL、C是按行优先顺序存储的,如图5-2(b)示。 列优先顺序(Column Major Order) :将数组元素按列向量排列,第j+1个列向量紧接在第j个列向量之后,对二维数组,按列优先顺序存储的线性序列为:a11,a21,am1, a12,a22,am2, , an1,an2,anmFORTRAN是按列优先顺序存储的,如图5-2(c)。,设有二维数组A=(aij)mn,若每个元素占用的存储单元数为d(个),LOCa11表示元素a11的首地址,即数组的首地址。 1. 以“行优先顺序”存储 第1
6、行中的每个元素对应的(首)地址是:LOCa1j=LOCa11+(j-1)d j=1,2, ,n (2) 第2行中的每个元素对应的(首)地址是:LOCa2j=LOCa11+nd +(j-1)d j=1,2, ,n 第m行中的每个元素对应的(首)地址是: LOCamj=LOCa11+(m-1)nd +(j-1)d j=1,2, ,n,由此可知,二维数组中任一元素aij的(首)地址是: LOCaij=LOCa11+(i-1)n +(j-1)d (5-1) i=1,2, ,m j=1,2, ,n根据(5-1)式,对于三维数组A=(aijk)mnp,若每个元素占用的存储单元数为d(个),LOCa111表
7、示元素a111的首地址,即数组的首地址。以“行优先顺序”存储在内存中。三维数组中任一元素aijk的(首)地址是:LOC(aijk)=LOCa111+(i-1)np+(j-1)p+(k-1)d (5-2),例如,二维数组Amn按“行优先顺序”存储在内存中,假设每个元素占用d个存储单元。元素aij的存储地址应是数组的基地址加上排在aij前面的元素所占用的单元数。因为aij位于第i行、第j列,前面i-1行一共有(i-1)n个元素,第i行上aij前面又有j-1个元素,故它前面一共有(i-1)n+j-1个元素,因此,aij的地址计算函数为: LOC(aij)=LOC(a11)+(i-1)*n+j-1*d
8、同样,三维数组Amnp按“行优先顺序”存储,其地址计算函数为:LOC(aijk)=LOC(a111)+(i-1)*n*p+(j-1)*p+(k-1)*d,上述讨论均是假设数组各维的下界是不是1,更一般的二维数组是Ac1d1,c2d2,这里c1,c2不一定是1。aij前一共有i-c1行,二维数组一共有d2-c2+1列,故这i-c1行共有(i-c1)*(d2-c2+1)个元素,第i行上aij前一共有j-c2个元素,因此,aij的地址计算函数为:LOC(aij)=LOC(ac1c2)+(i-c1)*(d2-c2+1)+j-c2)*d例如,在C语言中,数组各维下标的下界是0,因此在C语言中,二维数组的
9、地址计算公式为:LOC(aij)=LOC(a00)+(i*(d2+1)+j)*d,2. 以“列优先顺序”存储 第1列中的每个元素对应的(首)地址是:LOCaj1=LOCa11+(j-1)l j=1,2, ,m (2) 第2列中的每个元素对应的(首)地址是:LOCaj2=LOCa11+ml +(j-1)l j=1,2, ,m 第n列中的每个元素对应的(首)地址是: LOCajn=LOCa11+ (n-1)ml +(j-1)l j=1,2, ,m 由此可知,二维数组中任一元素aij的(首)地址是: LOCaij=LOCa11+(i-1)m+(j-1)l (5-1) i=1,2, ,n j=1,2,
10、 ,m,5.3 矩阵的压缩存储,在科学与工程计算问题中,矩阵是一种常用的数学对象,在高级语言编程时,通常将一个矩阵描述为一个二维数组。这样,可以对其元素进行随机存取,各种矩阵运算也非常简单。对于高阶矩阵,若其中非零元素呈某种规律分布或者矩阵中有大量的零元素,若仍然用常规方法存储,可能存储重复的非零元素或零元素,将造成存储空间的大量浪费。对这类矩阵进行压缩存储: 多个相同的非零元素只分配一个存储空间; 零元素不分配空间。,5.3.1 特殊矩阵,特殊矩阵:是指非零元素或零元素的分布有一定规律的矩阵。 1. 对称矩阵 若一个n阶方阵A=(aij)nn中的元素满足性质: aij=aji 1i,jn且i
11、j 则称A为对称矩阵,如图5-3所示。,对称矩阵中的元素关于主对角线对称,因此,让每一对对称元素aij和aji(ij)分配一个存储空间,则n2个元素压缩存储到n(n+1)/2个存储空间,能节约近一半的存储空间。不失一般性,假设按“行优先顺序”存储下三角形(包括对角线)中的元素。设用一维数组(向量)sa0n(n+1)/2存储n阶对称矩阵,如图5-4所示。为了便于访问,必须找出矩阵A中的元素的下标值(i,j)和向量sak的下标值k之间的对应关系。,若ij:ai j在下三角形中,直接保存在sa中。ai j之前的i-1行共有元素个数: 1+2+(i-1)=i(i-1)/2 而在第i行上,ai j之前恰
12、有j-1个元素,因此,元素ai j保存在向量sa中时的下标值k之间的对应关系是:k=i(i-1)/2+j-1 ij若ij:则aij是在上三角矩阵中。因为aij=aji,在向量sa中保存的是aji 。依上述分析可得:k=j(j-1)/2+i-1 ij对称矩阵元素ai j保存在向量sa中时的下标值k与(i,j)之间的对应关系是:,根据上述的下标对应关系,对于矩阵中的任意元素aij,均可在一维数组sa中唯一确定其位置k;反之,对所有k=0,1,2, ,n(n+1)/2-1,都能确定sak中的元素在矩阵中的位置(i,j)。称sa0n(n+1)/2为n阶对称矩阵A的压缩存储。 2. 三角矩阵以主对角线划
13、分,三角矩阵有上三角和下三角两种。上三角矩阵的下三角(不包括主对角线)中的元素均为常数c(一般为0)。下三角矩阵正好相反,它的主对角线上方均为常数,如图5-5所示。,三角矩阵中的重复元素c可共享一个存储空间,其余的元素正好有n(n+1)/2个,因此,三角矩阵可压缩存储到向量sa0n(n+1)/2中,其中c存放在向量的最后1个分量中。下三角矩阵元素ai j保存在向量sa中时的下标值k与(i,j)之间的对应关系是:,上三角矩阵的第p行有n-p+1个元素,元素ai j前有i-1行,p从第一行1到i-1行累加每行元素个数,保存在向量sa中时的下标值k与(i,j)之间的对应关系是:,3. 对角矩阵矩阵中
14、,除了主对角线和主对角线上或下方若干条对角线上的元素之外,其余元素皆为零。即所有的非零元素集中在以主对角线为了中心的带状区域中,如图5-6所示。,如上图三对角矩阵,非零元素仅出现在主对角(ai i,1in)上、主对角线上的那条对角线(ai i+1,1in-1) 、主对角线下的那条对角线上(ai+1 i,1in-1)。显然,当| i-j |1时,元素aij=0。由此可知,一个k对角矩阵(k为奇数)A是满足下述条件: 当| i-j |(k-1)/2时, ai j=0,对角矩阵可按行优先顺序或对角线顺序,将其压缩存储到一个向量中,并且也能找到每个非零元素和向量下标的对应关系。仍然以三对角矩阵为例讨论
15、。 当i=1,j=1、2,或i=n, j=n-1、n或 1in-1,j=i-1、i、i+1的元素aij外,其余元素都是0。对这种矩阵,当以按“行优先顺序”存储时, 第1行和第n行是2个非零元素,其余每行的非零元素都要是3个,则需存储的元素个数为3n-2。,如图5-7所示三对角矩阵的压缩存储形式。数组sa中的元素sak与三对角矩阵中的元素aij存在一一对应关系,在aij之前有i-1行,共有3(i-1)-1个非零元素,在第i行,有j-i+1个非零元素,这样,非零元素aij的地址为:LOCai j =LOCa11 +3(i-1)-1+(j-i+1)d =LOCa11+(2i+j-3)d 上例中,a3
16、4对应着sa7 , k=2i+j-3=23+4-3=7 称sa03n-2是n阶三对角矩阵A的压缩存储。上述各种特殊矩阵,其非零元素的分布都是有规律的,因此总能找到一种方法将它们压缩存储到一个向量中,并且一般都能找到矩阵中的元素与该向量的对应关系,通过这个关系,仍能对矩阵的元素进行随机存取。,5.3.2 稀疏矩阵,稀疏矩阵(Sparse Matrix):对于稀疏矩阵,目前还没有一个确切的定义。设矩阵A是一个nm的矩阵中有s个非零元素,设 =s/(nm),称为稀疏因子,如果某一矩阵的稀疏因子满足0.05时称为稀疏矩阵,如图5-8所示。,稀疏矩阵的压缩存储,对于稀疏矩阵,采用压缩存储方法时,只存储非
17、0元素。必须存储非0元素的行下标值、列下标值、元素值。因此,一个三元组(i, j, aij)唯一确定稀疏矩阵的一个非零元素。如图5-8的稀疏矩阵A的三元组线性表为: ( (1,2,12), (1,3,9), (3,1,-3), (3,8,4), (4,3,24), (5,2,18), (6,7,-7), (7,4,-6) ),1. 三元组表,若以行序为主序,稀疏矩阵中所有非0元素的三元组,就可以得构成该稀疏矩阵的一个三元组顺序表。相应的数据结构定义如下: 三元组结点定义 #define MAX_SIZE 101typedef int elemtype ;typedef struct int i
18、 ; /* 行下标 */ int j ; /* 列下标 */ elemtype v; /* 元素值 */ Triple ;, 三元组顺序表定义 typedef struct int rn ; /* 行数 */ int cn ; /* 列数 */ int tn ; /* 非0元素个数 */ Triple dataMAX_SIZE ; TMatrix ; 图5-8所示的稀疏矩阵及其相应的转置矩阵所对应的三元组顺序表如图5-9所示。,矩阵的运算包括矩阵的转置、矩阵求逆、矩阵的加减、矩阵的乘除等。在此,先讨论在这种压缩存储结构下的求矩阵的转置的运算。一个mn的矩阵A,它的转置B是一个nm的矩阵,且bi
19、j=aji,0in,0jm,即B的行是A的列,B的列是A的行。设稀疏矩阵A是按行优先顺序压缩存储在三元组表a.data中,若仅仅是简单地交换a.data中i和j的内容,得到三元组表b.data,b.data将是一个按列优先顺序存储的稀疏矩阵B,要得到按行优先顺序存储的b.data,就必须重新排列三元组表b.data中元素的顺序。,求转置矩阵的基本算法思想是: 将矩阵的行、列下标值交换。即将三元组表中的行、列位置值i 、j相互交换; 重排三元组表中元素的顺序。即交换后仍然是按行优先顺序排序的。 方法一: 算法思想:按稀疏矩阵A的三元组表a.data中的列次序依次找到相应的三元组存入b.data中
20、。 每找转置后矩阵的一个三元组,需从头至尾扫描整个三元组表a.data 。找到之后自然就成为按行优先的转置矩阵的压缩存储表示。,void TransMatrix(TMatrix a , TMatrix ,算法分析:本算法主要的工作是在p和col的两个循环中完成的,故算法的时间复杂度为O(cntn),即矩阵的列数和非0元素的个数的乘积成正比。 而一般传统矩阵的转置算法为: for(col=1; col=n ;+col) for(row=0 ; row=m ;+row) bcolrow=arowcol ;其时间复杂度为O(nm)。当非零元素的个数tn和mn同数量级时,算法TransMatrix的时
21、间复杂度为O(mn2)。由此可见,虽然节省了存储空间,但时间复杂度却大大增加。所以上述算法只适合于稀疏矩阵中非0元素的个数tn远远小于mn的情况。,方法二(快速转置的算法) 算法思想:直接按照稀疏矩阵A的三元组表a.data的次序依次顺序转换,并将转换后的三元组放置于三元组表b.data的恰当位置。前提:若能预先确定原矩阵A中每一列的(即B中每一行)第一个非0元素在b.data中应有的位置,则在作转置时就可直接放在b.data中恰当的位置。因此,应先求得A中每一列的非0元素个数。 附设两个辅助向量num 和cpot 。 numcol:统计A中第col列中非0元素的个数; cpotcol :指示
22、A中第一个非0元素在b.data中的恰当位置。,显然有位置对应关系:,例图5-8中的矩阵A和表5-9(a)的相应的三元组表可以求得numcol和cpotcol的值如表5-1:,快速转置算法如下:void FastTransMatrix(TMatrix a, TMatrix b) int p , q , col , k ; int numMAX_SIZE , coptMAX_SIZE ; b.rn= ; =a.rn ; b.tn=a.tn ;/* 置三元组表b.data的行、列数和非0元素个数 */ if (b.tn=0) printf(“ The Matrix A=0n” ) ; else f
23、or (col=1 ; col= ; +col) numcol=0 ;/* 向量num初始化为0 */for (k=1 ; k=a.tn ; +k) +num a.datak.col ;/* 求原矩阵中每一列非0元素个数 */,for (cpot0=1, col=2 ; col= ; +col)cpotcol=cpotcol-1+numcol-1 ;/* 求第col列中第一个非0元在b.data中的序号 */ for (p=1 ; p=a.tn ; +p) col=a.datap.col ; q=cpotcol ;b.dataq.row=a.datap.col ;b.dataq.col=a.d
24、atap.row ;b.dataq.value=a.datap.value ; +cpotcol ; /*至关重要!当本列中 */ ,2. 行逻辑链接的三元组表,将上述方法二中的辅助向量cpot 固定在稀疏矩阵的三元组表中,用来指示“行”的信息。得到另一种顺序存储结构:行逻辑链接的三元组顺序表。其类型描述如下:#define MAX_ROW 100typedef struct Triple dataMAX_SIZE ; /* 非0元素的三元组表 */ int rposMAX_ROW; /* 各行第一个非0位置表 */ int rn ,cn , tn ; /* 矩阵的行、列数和非0元个数 */
25、RLSMatrix ;,稀疏矩阵的乘法 设有两个矩阵:A=(aij)mn ,B=(bij)np 则: C=(cij)mp 其中 cij=aikbkj1kn , 1im ,1jp 经典算法是三重循环: for ( i=1 ; i=m ; +i) for ( j=1 ; j=p ; +j) cij=0 ; for ( k=1 ; k=n ; +k)cij= cij+aikbkj; 此算法的复杂度为O(mnp)。,设有两个稀疏矩阵A=(aij)mn ,B=(bij)np ,其存储结构采用行逻辑链接的三元组顺序表。 算法思想:对于A中的每个元素a.datap(p=1, 2, , a.tn),找到B中所
26、有满足条件:a.datap.col=b.dataq.row的元素b.dataq,求得a.datap.valueb.dataq.value,该乘积是cij中的一部分。求得所有这样的乘积并累加求和就能得到cij。为得到非0的乘积,只要对a.data1a.tn 中每个元素(i,k,aik)(1ia.rn,) ,找到b.data中所有相应的元素(k,j,bkj)(1kb.rn,) 相乘即可。则必须知道矩阵B中第k行的所有非0元素,而b.rpos 向量中提供了相应的信息。,b.rposrow指示了矩阵B的第row行中第一个非0元素在b.data 中的位置(序号),显然,b.rposrow+1-1指示了第
27、row行中最后一个非0元素在b.data 中的位置(序号) 。最后一行中最后一个非0元素在b.data 中的位置显然就是b.tn 。 两个稀疏矩阵相乘的算法如下: void MultsMatrix(RLSMatrix a, RLSMatrix b, RLSMatrix c)/* 求矩阵A 、B的积C=AB,采用行逻辑链接的顺序表 */ elemtype ctempMax_Size ; int p , q , arow , ccol , brow , t ; if (!=b.rn) printf(“Errorn”) ; exit(0); ,else c.rn=a.rn ; =b. n ; c.t
28、n=0 ; /* 初始化C */ if (a.tn*b.tn!=0) /* C 是非零矩阵 */ for (arow=1 ; arow=a.rn ; +arow) ctemparow=0 ; /* 当前行累加器清零 */c.rposarow=c.tn+1; p=a.ropsarow;for ( ; pa.rposarow+1;+p)/* 对第arow行的每一个非0元素 */ brow=a.datap.col ;/* 找到元素在b.data中的行号 */if () t=( b.rposbrow+1;else t=b.tn+1 ;,for (q=b.rposbrow ; qMAX_SIZE) pr
29、intf(“Errorn”) ; exit(0); else,c.datac.tn=(arow , ccol , ctempccol) ; ,3. 十字链表,对于稀疏矩阵,当非0元素的个数和位置在操作过程中变化较大时,采用链式存储结构表示比三元组的线性表更方便。矩阵中非0元素的结点所含的域有:行、列、值、行指针(指向同一行的下一个非0元)、列指针(指向同一列的下一个非0元)。其次,十字交叉链表还有一个头结点,结点的结构如图5-10所示。,由定义知,稀疏矩阵中同一行的非0元素的由right指针域链接成一个行链表, 由down指针域链接成一个列链表。则每个非0元素既是某个行链表中的一个结点,同时又
30、是某个列链表中的一个结点,所有的非0元素构成一个十字交叉的链表。称为十字链表。此外,还可用两个一维数组分别存储行链表的头指针和列链表的头指针。对于图5-11(a)的稀疏矩阵A ,对应的十字交叉链表如图5-11(b)所示,结点的描述如下:typedef struct Clnode int row , col ; /* 行号和列号 */ elemtype v ; /* 元素值 */ struct Clnode *down , *right ; OLNode ; /* 非0元素结点 */,typedef struct Clnode int rn; /* 矩阵的行数 */ int cn; /* 矩阵的
31、列数 */ int tn; /* 非0元素总数 */ OLNode *rhead cn+1; OLNode *chead rn+1; CrossList ;,5.4 广义表,广义表是线性表的推广和扩充,在人工智能领域中应用十分广泛。在第2章中,我们把线性表定义为n(n0 )个元素a1, a2 , an的有穷序列,该序列中的所有元素具有相同的数据类型且只能是原子项(Atom)。所谓原子项可以是一个数或一个结构,是指结构上不可再分的。若放松对元素的这种限制,容许它们具有其自身结构,就产生了广义表的概念。广义表(Lists,又称为列表 ):是由n(n 0)个元素组成的有穷序列: LS=(a1,a2,
32、an),其中ai或者是原子项,或者是一个广义表。LS是广义表的名字,n为它的长度。若ai是广义表,则称为LS的子表。 习惯上:原子用小写字母,子表用大写字母。 若广义表LS非空时: a1(表中第一个元素)称为表头; 其余元素组成的子表称为表尾;(a2,a3,an) 广义表中所包含的元素(包括原子和子表)的个数称为表的长 度。 广义表中括号的最大层数称为表深 (度)。 有关广义表的这些概念的例子如表5-2所示。,表5-2 广义表及其示例,广义表的重要结论: 广义表的元素可以是原子,也可以是子表,子表的元素又可以是子表, 。即广义表是一个多层次的结构。表5-2中的广义表D的图形表示如图5-12所示
33、。 (2) 广义表可以被其它广义表所共享,也可以共享其它广义表。广义表共享其它广义表时通过表名引用。 (3) 广义表本身可以是一个递归表。 (4) 根据对表头、表尾的定义,任何一个非空广义表的表头可以是原子,也可以是子表, 而表尾必定是广义表。,5.4.1 广义表的存储结构,由于广义表中的数据元素具有不同的结构,通常用链式存储结构表示,每个数据元素用一个结点表示。因此,广义表中就有两类结点: 一类是表结点,用来表示广义表项,由标志域,表头指针域,表尾指针域组成; 另一类是原子结点,用来表示原子项,由标志域,原子的值域组成。如图5-13所示。只要广义表非空,都是由表头和表尾组成。即一个确定的表头
34、和表尾就唯一确定一个广义表。,相应的数据结构定义如下: typedef struct GLNode int tag ; /* 标志域,为1:表结点;为0 :原子结点 */ union elemtype value; /* 原子结点的值域 */ struct struct GLNode *hp , *tp ;ptr ; /* ptr和atom两成员共用 */ Gdata ; GLNode ; /* 广义表结点类型 */,例: 对A=(),B=(e),C=(a, (b, c, d) ),D=(A, B, C),E=(a, E)的广义表的存储结构如图5-14所示。,对于上述存储结构,有如下几个特点:
35、 (1) 若广义表为空,表头指针为空;否则,表头指针总是指向一个表结点,其中hp指向广义表的表头结点(或为原子结点,或为表结点) ,tp指向广义表的表尾(表尾为空时,指针为空,否则必为表结点)。 (2) 这种结构求广义表的长度、深度、表头、表尾的操作十分方便。 (3) 表结点太多,造成空间浪费。也可用图5-15所示的结点结构。,习 题 五, 什么是广义表?请简述广义表与线性表的区别? 一个广义表是(a, (a, b), d, e, (a, (i, j), k) ,请画出该广义表的链式存储结构。 设有二维数组a68,每个元素占相邻的4个字节,存储器按字节编址,已知a的起始地址是1000,试计算:
36、 数组a的最后一个元素a57起始地址; 按行序优先时,元素a46起始地址; 按行序优先时,元素a46起始地址。, 设A和B是稀疏矩阵,都以三元组作为存储结构,请写出矩阵相加的算法,其结果存放在三元组表C中,并分析时间复杂度。 设有稀疏矩阵B如下图所示,请画出该稀疏矩阵的三元组表和十字链表存储结构。,第6章 树和二叉树,树型结构是一类非常重要的非线性结构。直观地,树型结构是以分支关系定义的层次结构。树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,可用树来组织信息;在分析算法的行为时,可用树来描述其执行过程等等。本章将详细讨论树和二叉树数据结构,主要
37、介绍树和二叉树的概念、术语,二叉树的遍历算法。树和二叉树的各种存储结构以及建立在各种存储结构上的操作及应用等。,6.1 树的基本概念,1. 树的定义树(Tree)是n(n0)个结点的有限集合T,若n=0时称为空树,否则: 有且只有一个特殊的称为树的根(Root)结点; 若n1时,其余的结点被分为m(m0)个互不相交的子集T1, T2, T3Tm,其中每个子集本身又是一棵树,称其为根的子树(Subtree)。这是树的递归定义,即用树来定义树,而只有一个结点的树必定仅由根组成,如图6-1(a)所示。,6.1.1 树的定义和基本术语,2. 树的基本术语 结点(node):一个数据元素及其若干指向其子
38、树的分支。 结点的度(degree) 、树的度:结点所拥有的子树的棵数称为结点的度。树中结点度的最大值称为树的度。,如图6-1(b)中结点A的度是3 ,结点B的度是2 ,结点M的度是0,树的度是3 。 叶子(left)结点、非叶子结点:树中度为0的结点称为叶子结点(或终端结点)。相对应地,度不为0的结点称为非叶子结点(或非终端结点或分支结点)。除根结点外,分支结点又称为内部结点。如图6-1(b)中结点H、I、J、K、L、M、N是叶子结点,而所有其它结点都是分支结点。 孩子结点、双亲结点、兄弟结点一个结点的子树的根称为该结点的孩子结点(child)或子结点;相应地,该结点是其孩子结点的双亲结点(
39、parent)或父结点。,如图6-1(b)中结点B 、C、D是结点A的子结点,而结点A是结点B 、C、D的父结点;类似地结点E 、F是结点B的子结点,结点B是结点E 、F的父结点。 同一双亲结点的所有子结点互称为兄弟结点。如图6-1(b)中结点B 、C、D是兄弟结点;结点E 、F是兄弟结点。 层次、堂兄弟结点规定树中根结点的层次为1,其余结点的层次等于其双亲结点的层次加1。若某结点在第l(l1)层,则其子结点在第l+1层。双亲结点在同一层上的所有结点互称为堂兄弟结点。如图6-1(b)中结点E、F、G、H、I、J。, 结点的层次路径、祖先、子孙从根结点开始,到达某结点p所经过的所有结点成为结点p
40、的层次路径(有且只有一条)。结点p的层次路径上的所有结点(p除外)称为p的祖先(ancester) 。以某一结点为根的子树中的任意结点称为该结点的子孙结点(descent)。 树的深度(depth):树中结点的最大层次值,又称为树的高度,如图6-1(b)中树的高度为4。 有序树和无序树:对于一棵树,若其中每一个结点的子树(若有)具有一定的次序,则该树称为有序树,否则称为无序树。, 森林(forest):是m(m0)棵互不相交的树的集合。显然,若将一棵树的根结点删除,剩余的子树就构成了森林。 3. 树的表示形式 倒悬树。是最常用的表示形式,如图6-1(b)。 嵌套集合。是一些集合的集体,对于任何
41、两个集合,或者不相交,或者一个集合包含另一个集合。图6-2(a)是图6-1(b)树的嵌套集合形式。 广义表形式。图6-2(b)是树的广义表形式。 凹入法表示形式。见P120树的表示方法的多样化说明了树结构的重要性。,6.1.2 树的抽象数据类型定义,ADT Tree数据对象D:D是具有相同数据类型的数据元素的集合。数据关系R:若D为空集,则称为空树; 基本操作: ADT Tree详见p118119。,6.2 二叉树,6.2.1 二叉树的定义 1. 二叉树的定义二叉树(Binary tree)是n(n0)个结点的有限集合。若n=0时称为空树,否则: 有且只有一个特殊的称为树的根(Root)结点;
42、 若n1时,其余的结点被分成为二个互不相交的子集T1,T2,分别称之为左、右子树,并且左、右子树又都是二叉树。由此可知,二叉树的定义是递归的。,二叉树在树结构中起着非常重要的作用。因为二叉树结构简单,存储效率高,树的操作算法相对简单,且任何树都很容易转化成二叉树结构。上节中引入的有关树的术语也都适用于二叉树。 2. 二叉树的基本形态二叉树有5种基本形态,如图6-3所示。,6.2.2 二叉树的性质,性质1:在非空二叉树中,第i层上至多有2i-1个结点(i1)。 证明:用数学归纳法证明。当i=1时:只有一个根结点,21-1=20 =1,命题成立。现假设对i1时,处在第i-1层上至多有2(i-1)-
43、1个结点。由归纳假设知,第i-1层上至多有2i-2个结点。由于二叉树每个结点的度最大为2,故在第i层上最大结点数为第i-1层上最大结点数的2倍。即 22i-22i-1 证毕 性质2:深度为k的二叉树至多有2k-1个结点(k1) 。 证明:深度为k的二叉树的最大的结点数为二叉树中每层上的最大结点数之和。由性质1知,二叉树的第1层、第2层第k层上的结点数至多有:20、21 2k-1 。 总的结点数至多有: 20+21+ +2k-1=2k-1 证毕,性质3:对任何一棵二叉树,若其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1。 证明:设二叉树中度为1的结点数为n1,二叉树中总结点数为N,因
44、为二叉树中所有结点均小于或等于2,则有:N=n0+n1+n2 再看二叉树中的分支数:除根结点外,其余每个结点都有唯一的一个进入分支,而所有这些分支都是由度为1和2的结点射出的。设B为二叉树中的分支总数,则有: N=B+1 Bn1+2n2 N=B+1=n1+2n2+1 n0+n1+n2=n1+2n2+1 即 n0=n2+1 证毕,满二叉树和完全二叉树一棵深度为k且有2k-1个结点的二叉树称为 满二叉树(Full Binary Tree)。如图6-4(a) 就是一棵深度为4的满二叉树。 满二叉树的特点: 基本特点是每一层上的结点数总是最大结点数。 满二叉树的所有的支结点都有左、右子树。 可对满二叉
45、树的结点进行连续编号,若规定从根结点开始,按“自上而下、自左至右”的原则进行。,完全二叉树(Complete Binary Tree):如果深度为k,由 n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应,该二叉树称为完全二叉树。或深度为k的满二叉树中编号从1到n的前n个结点构成了一棵深度为k的完全二叉树。其中 2k-1 n2k-1 。完全二叉树是满二叉树的一部分,而满二叉树是完全二叉树的特例。 完全二叉树的特点:若完全二叉树的深度为k ,则所有的叶子结点都出现在第k层或k-1层。对于任一结点,如果其右子树的最大层次为l,则其左子树的最大层次为l 或 l+
46、1。,性质4:n个结点的完全二叉树深度为:2n +1。其中符号: x表示不大于x的最大整数。x 表示不小于x的最小整数。 证明:假设完全二叉树的深度为k,则根据性质2及完全二叉树的定义有:2k-1-1n2k-1 或 2 k-1n2k取对数得:k12nk 因为k是整数。 k= 2n +1 证毕,性质5:若对一棵有n个结点的完全二叉树(深度为2n +1)的结点按层(从第1层到第2n +1层)序自左至右进行编号,则对于编号为i(1in)的结点: 若i=1:则结点i是二叉树的根,无双亲结点;否则,若i1,则其双亲结点编号是 i/2 。 如果2in:则结点i为叶子结点,无左孩子;否则,其左孩子结点编号是2i。 如果2i+1n:则结点i无右孩子;否则,其右孩子结点编号是2i+1。,证明:用数学归纳法证明。首先证明和,然后由和导出。当i=1时,由完全二叉树的定义知,结点i的左孩子的编号是2,右孩子的编号是3。若2n,则二叉树中不存在编号为2的结点,说明结点i的左孩子不存在。若3n,则二叉树中不存在编号为3的结点,说明结点i的右孩子不存在。现假设对于编号为j(1ji)的结点,(2)和(3)成立。即: 当2jn :结点j的左孩子编号是2j;当2jn时,结点j的左孩子结点不存在。,