Transcript
Page 1: 第五章   多维数组与广义表

2007.9

第五章 多维数组与广义表

Page 2: 第五章   多维数组与广义表

5.1 多维数组5.1.1 多维数组的定义5.1.2 多维数组的存储

5.2 矩阵的压缩存储 5.2.1 特殊矩阵 5.2.2 稀疏矩阵5.3 广义表

Page 3: 第五章   多维数组与广义表

5.1 多维数组

Page 4: 第五章   多维数组与广义表

5.1.1 多维数组的定义一维数组

一维数组可以看成是一个线性表或一个向量,它在计算机内是存放在一块连续的存储单元中,适合于随机查找。

有一个直接前驱和一个直接后继二维数组

二维数组可以看成是向量的推广。有两个直接前驱和两个直接后继

Page 5: 第五章   多维数组与广义表

三维数组最多可有三个直接前驱和三个直接后继

多维数组把三维以上的数组称为多维数组,可有多个直接前驱和多个直接后继是一种非线性结构。

Page 6: 第五章   多维数组与广义表

在 C 语言中的描述typedef int datatype;

datatype array1[N]; datatype array2[M][N];datatype array3[X][Y][Z];

数组一旦被定义,它的维数和维界就不再改变。因此,数组只有存取元素和修改元素值的操作。

Page 7: 第五章   多维数组与广义表

考虑问题的基本出发点: 计算机的内存结构是一维的。因此用一维内存来存多

维数组,就必须按某种次序将数组元素排成线性序列。 数组一旦建立,结构中的元素个数和元素间的关系就

不再发生变化。因此,一般都是采用顺序存储的方法来表示数组。

5.2 多维数组的存储

Page 8: 第五章   多维数组与广义表

两种顺序存储方式行优先顺序——将数组元素按行排列

在 PASCAL 、 C 语言中,数组就是按行优先顺序存储的。

列优先顺序——将数组元素按列向量排列在 FORTRAN 语言中,数组就是按列优先顺序存储的。

推广到多维数组的情况:行优先顺序:先排最右下标,从右到左,最后排最左下标列优先顺序:先排最左下标,从左向右,最后排最右下标。

Page 9: 第五章   多维数组与广义表

计算机如何实现数组元素的随机存取?

按上述两种方式顺序存储的序组,只要知道:开始结点的存放地址(即基地址),维数每维的上、下界每个数组元素所占用的单元数,

就可以将数组元素的存放地址表示为其下标的线性函数。因此,数组中的任一元素可以在相同的时间内存取,即顺序存储的数组是一个随机存取结构。

如何计算数组元素的地址?

Page 10: 第五章   多维数组与广义表

一维数组二维数组三维数组

如何计算数组元素的地址? 内存0

ListSize -1

a0 a1 a2 … an

a00 a01 …… a0n-1

a10 a11 …… a1n-1 ………………………….

am-1 0 am-1 1 …… a m-1 n-1

a00 a01 …… a0n-1

a10 a11 …… a1n-1 ………………………….

am-1 0 am-1 1 …… a m-1 n-1

a0

a1

…an

内存

a00

a0n

a10

…a1n

Page 11: 第五章   多维数组与广义表

假设数组各维的下界是 1, 按“行优先顺序”存储 ,假设每个元素占用 d 个存储单元。

二维数组 Amn , aij 的地址计算函数为: LOC(aij)=LOC(a11)+[(i-1)*n+j-1]*d

三维数组 Amnp , aijk 的地址计算函数为: LOC(aijk)=LOC(a111)+[(i-1)*n*p+(j-1)*p +(k-

1)]*d

Page 12: 第五章   多维数组与广义表

更一般的二维数组是 A[c1..d1,c2..d2] ,这里 c1,c2 不一定是 1 。

aij 的地址计算函数为: LOC(aij)=LOC(ac1c2)+[(i-c1)*(d2-c2+1)+j-c2)]*d

例如,在 C 语言中,数组各维下标的下界是 0 ,因此

在 C 语言中,二维数组的地址计算公式为: LOC(aij)=LOC(a00)+(i*(d2+1)+j)*d

Page 13: 第五章   多维数组与广义表

最基本的原理

Ai1…in 的起始地址

=第一个元素的起始地址

该元素前面的元素个数 ╳ 单位

长度〸

Page 14: 第五章   多维数组与广义表

程序员试题

Page 15: 第五章   多维数组与广义表

2006-1对于二维数组 a[0…4,1…5] ,设每个元素占 1 个存

储单元,且以行为主序存储,则元素 a[2,1] 相对于数组空间起始地址的偏移量是 ___(40)___ 。   (40)A . 5         B . 10  

     C . 15        D . 25

2003设数组 a[3..16 , 5..20] 的元素以列为主序存放,每个

元素占用两个存储单元,则数组元素 a[i,j](3≤ i≤ 16 , 5≤ j≤ 20) 的地址计算公式为 ___(11)___ 。

(11)A . a-118+2i+28j B . a-116+2i+28j C . a-144+2i+28j D . a-146+2i+28j

Page 16: 第五章   多维数组与广义表

2001二维数组 X 的行下标范围是 0 ~ 5, 列下标范围是 1 ~ 8, 每个数

组元素占六个字节 , 则该数组的体积为 __(6)__ 个字节 , 若已知 X 的最后一个元素的起始字节地址为 382, 则 X 的首地址 ( 即第一个元素的起始字节地址 ) 为 __(7)__, 记为 Xd 。若按行存储 , 则 X{1,5] 的起始地址是 __(8)__, 结束字节地址是 __(9)__ 。若按列存储 , 则 X[4,8] 的起始字节地址为 __(10)__ 。

(6): A.210 B.240 C.288 D.294(7): A.0 B.6 C.94 D.100(8): A.Xd+24 B.Xd+72 C.Xd+78

D.Xd+144(9): A.Xd+29 B.Xd+77 C.Xd+83

D.Xd+147(10):A.Xd+186 B.Xd+234 C.Xd+270

D.Xd+276

(6)C (7)D (8)B (9)B (10)D

Page 17: 第五章   多维数组与广义表

5.2 矩阵的压缩存储

Page 18: 第五章   多维数组与广义表

在编程时,简单而又自然的方法,是将矩阵描述为一个二维数组。矩阵在这种存储表示之下,可以对其元素进行随机存取。

但是在一些特殊矩阵中,非零元素呈某种规律分布

或者矩阵中有大量的零元素,如果仍用二维数组存,会造成极大的浪费,尤其是处理高阶矩阵的时候。

为了节省存储空间, 我们可以对这类矩阵进行压缩存储。

Page 19: 第五章   多维数组与广义表

5.2.1 几种常见的特殊矩阵

1 2 3 4 5

2 3 4 5 6

3 4 5 6 7

4 5 6 7 8

5 6 7 8 9

对称矩阵 在一个 n 阶方阵 A 中,若元素满足下述性质: aij=aji 0≦i,j≦n-1 ,则称 A 为对称矩阵。

特征:元素关于主对角线对称

压缩存储的办法: 只存矩阵中上三角或下三角中的元素。

所需空间:2

)1()1(

1

0

nniN

n

i

Page 20: 第五章   多维数组与广义表

三角矩阵 特征:上三角矩阵中,主对角

线的下三角中的元素均为常数。在大多数情况下,常数为零。

下三角矩阵正好相反。压缩方法:

只存上 ( 下 ) 三角阵中上 ( 下 ) 三角中的元素

常数 c可共享一个存储空间

所需空间:

1 2 3 4 5

0 3 4 5 6

0 0 5 6 7

0 0 0 7 8

0 0 0 0 9

1 2 3 4 5

4 3 4 5 6

4 4 5 6 7

4 4 4 7 8

4 4 4 4 9

1 0 0 0 0

2 3 0 0 0

3 6 5 0 0

4 7 9 7 0

5 8 1 2 9

1 4 4 4 4

2 3 4 4 4

3 6 5 4 4

4 7 9 7 4

5 8 1 2 91

2

)1(

nnN

Page 21: 第五章   多维数组与广义表

对角矩阵 特征:所有的非零元素集中在以主对角线

为中心的带状区域中, 即除了主对角线和主对角线相邻两侧的若干条对角线上的元素之外,其余元素皆为零。

压缩存储的办法: 只存对角线上的元素。

存三对角矩阵所需的空间:

1 1 0 0 0

2 3 7 0 0

0 4 5 3 0

0 0 6 7 5

0 0 0 8 9

三对角矩阵

23 nN

Page 22: 第五章   多维数组与广义表

特征:只有少量非零元素,且非零元素的分布没有规律。

压缩存储的办法: 只存非零元素。

所需空间:与非零元素的个数和存储方式有关。

稀疏矩阵

1 2 0 0 5

0 3 0 0 0

0 4 0 0 0

0 0 6 0 0

0 0 0 8 0

Page 23: 第五章   多维数组与广义表

5.2.2 特殊矩阵的压缩存储 矩阵类型

对称矩阵 三角矩阵 对角矩阵

压缩的基本思想: 只存有用的元素 由用二维数组改为用一维数组来存放

说明: 按 C 语言中规定,下标从 0 开始 不失一般性,按“行优先顺序”存储

Page 24: 第五章   多维数组与广义表

关键问题 如何确定一维数组的大小? 如何确定矩阵元素在一维数组中的位置?从而保证对矩阵元素的随机存取

Aij 的位置 = 该元素前的元素个数

= 所需空间1

2

3

3

4

5

4

5

6

7

1 2 3 4 5

2 3 4 5 6

3 4 5 6 7

4 5 6 7 8

5 6 7 8 9

Page 25: 第五章   多维数组与广义表

1 . 对称矩阵如何确定一维数组的大小?

2

)1(

nnN

设:存放下三角阵中的元素,则:如何确定元素 Aij 在一维

数组中的位置?

1

2

3

3

4

5

4

5

6

7

1 2 3 4 5

2 3 4 5 6

3 4 5 6 7

4 5 6 7 8

5 6 7 8 9

在下三角阵中当

根据

在上三角阵中当

ij

jiij

ij

Ajijii

A

AjiijjijALoc

,2

)1(

)A(

,2

)1()(

Page 26: 第五章   多维数组与广义表

2. 三角矩阵1 4 4 4 4

2 3 4 4 4

3 6 5 4 4

4 7 9 7 4

5 8 1 2 9

12

)1(

nnN

1

2

3

3

6

5

4

如何确定一维数组的大小?

设:在下三角阵中,则:如何确定元素 Aij 在一

维数组中的位置?

即下三角阵中的元素,当

即下三角阵中的常数,,当

,2

)1(

2

)1()(jij

ii

jinnijALoc

Page 27: 第五章   多维数组与广义表

3. 三对角矩阵1 1 0 0 0

2 3 7 0 0

0 4 5 3 0

0 0 6 7 5

0 0 0 8 9

23 nN如何确定一维数组的大小?

如何确定元素 Aij 在一维数组中的位置?

1

1

2

3

7

4

5

3

6

7

5

8

9

)( ijALoc

在 Aij 之前有 i 行 ,共有 3*i-1 个非零元素 ,在第 i 行, aij 之前有 j-i+1 个非零元素,

3*i-1+(j-i+1) = 2*i+j

Page 28: 第五章   多维数组与广义表

程序员试题2004-1 对矩阵压缩存储的主要目的是 __(5)__ 。(5) A.方便运算  B.节省存储空间  C .降低计算复杂度  D .提高运算速度

2003 将一个三对角矩阵 A[l..100 , 1..100] 中的元素按行存储在一维数组 B[l..298] 中,矩阵 A 中的元素 A[66,65] 在数组 B 中的下标为 ___(4)___ 。

(4) A . 195 B . 196 C . 197 D. 198

Page 29: 第五章   多维数组与广义表

5.2.3 稀疏矩阵的压缩存储顺序存储:三元组表链式存储:十字链表

Page 30: 第五章   多维数组与广义表

1. 三元组表存稀疏矩阵

考虑:只存非零元素一个非零元素的必需信息有:

(i, j, aij)记录一个稀疏矩阵的必需信息有:

行数M,列数 N,非零元素个数 T

1 2 0 0 5

0 3 0 0 0

0 4 0 0 0

0 0 6 0 0

0 0 0 8 0

i j Aij

0 0 1

0 1 2

0 4 5

1 1 3

2 1 4

3 2 6

4 3 8

M=5N=5T=7

Page 31: 第五章   多维数组与广义表

三元组表的 C 语言描述#define maxsize 10000typedef int datatype;typedef struct{ /*三元组结点 */ int i,j; datatype v; }TriTupleNode;typedef struct{ TriTupleNode data[maxsize]; /*三元组表 */ int m,n,t;/* 稀疏矩阵的行数 , 列数 , 非零元素个数

*/ }TriTupleTable;

i j V

Page 32: 第五章   多维数组与广义表

2 .带行指针的三元组表把具有相同行号的非零元用一个单链表连接起

来,稀疏矩阵中的若干行组成若干个单链表,合起来称为带行指针的链表。

0 2 9 0 0 0

0 0 0 0 0 0

-3

0 0 0 0 4

0 0 24

0 0 0

0 18

0 0 0 0

15

0 0 -7

0 0

Page 33: 第五章   多维数组与广义表

3. 十字链表

i jVal/Next

列指针col

行指针row

Page 34: 第五章   多维数组与广义表
Page 35: 第五章   多维数组与广义表

5.2.4 应用举例: 稀疏矩阵的转置

1. 矩阵转置的数学解释 一个 m×n 的矩阵 A ,它的转置 B 是一个 n×m

的矩阵,且 a[i][j]=b[j][i] , 0≦i≦m , 0≦j≦n。

007

650

020

060

052

700Aij=Bji

Page 36: 第五章   多维数组与广义表

2. 利用三元组表实现转置1 2 0

0 3 0

0 4 0

0 0 6

i j Aij

0 0 1

0 1 2

1 1 3

2 1 4

3 2 6

M=4N=2T=5

1 0 0 0

2 3 4 0

0 0 0 6

i j Bij

0 0 1

0 1 2

1 1 3

2 1 4

3 2 6

M=2N=4T=5

Aij=Bji

Page 37: 第五章   多维数组与广义表

思想一:直接交换 a.data 中 i 和 j 的内容

问题: b.data 是一个按列优先顺序存储的稀疏矩阵 B 解决方法:重新排列 B 中三元组的顺序。

0 2 5

0 3 0

0 4 0

0 0 6i j Ai

j

0 1 2

0 2 5

1 1 3

2 1 4

3 2 6

M=4N=2T=5

0 0 0 0

2 3 4 0

5 0 0 6

i j Bij

1 0 2

2 0 5

1 1 3

1 2 4

2 3 6

Aij=Bji

i j Bij

1 0 2

1 1 3

1 2 4

2 0 5

2 3 6

ji按 i排序

M=2N=4T=5

Page 38: 第五章   多维数组与广义表

b.m=a.n; b.n=a.m; b.t=a.t; /*基本信息的赋值 *//*按交换 i 、 j 的方式给 B 的三元组赋值 */for ( i=0; i<b.t; i++ ) { b.data[i].i=a.data[i].j;

b.data[i].j=a.data[i].i;

b.data[i].v=a.data[i].v;}/*扫描 B ,按 i 排序 */

i j Aij

0 1 2

0 2 5

1 1 3

2 1 4

3 2 6

M=4N=2T=5

i j Bij

1 0 2

2 0 5

1 1 3

1 2 4

2 3 6

i j Bij

1 0 2

1 1 3

1 2 4

2 0 5

2 3 6

ji 按 i排序

M=2N=4T=5

Page 39: 第五章   多维数组与广义表

思想二:在 A 中按列序找三元组,写入B

B 的行优先即 A 的列优先对 A 的第 col 列 ,扫描三元组表 a.data ,找出所有列号等于 col 的三元组,将它们的行号和列号互换后依次放入b.data 中,即可得到 B 的按行优先的压缩存储表示。

0 2 5

0 3 0

0 4 0

0 0 6

i j Aij

0 1 2

0 2 5

1 1 3

2 1 4

3 2 6

M=4N=2T=5

0 0 0 0

2 3 4 0

5 0 0 6

Aij=Bji

i j Bij

1 0 2

1 1 3

1 2 4

2 0 5

2 3 6

M=2N=4T=5

col=0 ,没有匹配的三元组col=1 ,找到 2 , 3 , 4col=2 ,找到 5 , 6

Page 40: 第五章   多维数组与广义表

Void transmatrix(tripletable a,tripletable b) { int pa, pb, col; b.m=a.n; b.n=a.m; b.t=a.t; /*基本信息的赋值 */ if(b.t<=0) { printf(“A=0\n”); return 0;} /*无非零元素 */ pb=0; /*pb指向三元组表 B 中的当前位置 */ for(col=0;col<a.n;col++) /* 按列 col扫描表 A*/

for(pa=0;pa<=a.t;pa++) /*pb指向表 A 中的当前位置 */ /* 找所有列号等于 col 的三元组, i,j互换写放入B*/ if(a.data[pa].j==col){ b.data[pb].i=a.data[pa].j; b.data[pb].j=a.data[pa].i; b.data[pb].v=a.data[pa].v; pb++; } }

Page 41: 第五章   多维数组与广义表

算法分析 主要的工作是在 pa 和 col 的两个循环中完成的,故

算法的时间复杂度为 O(n*t) ,即矩阵的列数和非零元的个数的乘积成正比。

传统矩阵的转置算法的时间复杂度为 O(n*m)

当非零元素的个数 t和 m*n 同数量级时,该算法的时间复杂度为 O(m*n2) 。(最坏情况)

三元组顺序表虽然节省了存储空间,但时间复杂度比一般矩阵转置的算法还要复杂,同时还有可能增加算法的难度。因此,此算法仅适用于 t<=m*n 的情况。

Page 42: 第五章   多维数组与广义表

思想三:快速转置

基本思想:在 A 中按行序找三元组,确定该三元组在B 中的位置,写入 B.

关键问题:如何确定每个三元组在 B 中的位置

A 中三元组在 B 的中位置 = 每列的第一个非零元素在数组 B 中应有的位置 + 每一列非零元素的个数

Page 43: 第五章   多维数组与广义表

0 2 5

0 3 0

0 4 0

0 0 6

i j Aij

0 1 2

0 2 5

1 1 3

2 1 4

3 2 6

M=4N=2T=5

0 0 0 0

2 3 4 0

5 0 0 6

Aij=Bji

i j Bij

1 0 2

1 1 3

1 2 4

2 0 5

2 3 6

M=2N=4T=5

col 列号 0 1 2

cpos[i]

每列第一个非零元素在 B 中的位置

0 0 3

cnum[i]

每列非零元素个数

0 3 2

由递推关系得出 cpos 的值:cpos[0]=0cpos[i]=cpos[i-1]+cnum[i-1]

Page 44: 第五章   多维数组与广义表

void fasttranstri(tritupletable b, tritupletable a){ int col ; /* 当前列号 */ int pa, pb; /*pa,pb: 三元组 a,b 的下标 */ int cnum[0..a.n], cpos[0..a.n];

b 基本信息 m,n,t 的赋值; 若 a 无非零元素,则返回; 初始化数组 cnum ; /* 统计 a 中每列非零元素的个数; */ for(pa=0; pa<a.t; pa++) { col=a.data[pa].j;

cnum[col]++;} /* 由递推关系计算 cpos 的值 */ cpos[0]=0; for(col=1;col<=a.n;col++) cpos[col] = cpos[col-1] + cnum[col-1];

Page 45: 第五章   多维数组与广义表

/* 扫描 a ,将元素交换 i,j 写入 b*/ for( pa=0; pa<a.t; pa++ ){ col = a.data[p].j; pb = cpot[col]; b.data[pb].i = a.data[pa].j; b.data[pb].j = a.data[pa].i; b.data[pb].v = a.data[pa].v; cpos[col]++; } }}

Page 46: 第五章   多维数组与广义表

算法分析时间复杂度 O( n+t )。当非零元素的个数 t和 m*n 同数量级时,该算法的

时间复杂度为 O(m*n) ,与不压缩存储的情况相同。

Page 47: 第五章   多维数组与广义表

5.3 广义表

Page 48: 第五章   多维数组与广义表

5.5.1 基本概念广义表是第 2 章提到的线性表的推广。线性表中的元素仅限于原子项,即不可以再分,而广义表中的元素既可以是原子项,也可以是子表(另一个线性表)。

1 .广义表的定义广义表是 n≥0 个元素 a1 , a2 ,…, an 的有限序列,其中每一个 ai 或者是原子,或者是一个子表。广义表通常记为 LS=(a1,a2,…,an) ,其中 LS 为广义表的名字, n 为广义表的长度, 每一个 ai 为广义表的元素。但在习惯中,一般用大写字母表示广义表,小写字母表示原子。

2 .广义表举例( 1 ) A=( ) , A 为空表,长度为 0 。( 2 ) B=(a, ( b,c ) ) , B 是长度为 2 的广义表,第一项为原子,第二项为子

表。( 3 ) C=(x,y,z) , C 是长度为 3 的广义表,每一项都是原子。( 4 ) D=(B,C) , D 是长度为 2 的广义表,每一项都是上面提到的子表。( 5 ) E=(a,E) ,是长度为 2 的广义表,第一项为原子,第二项为它本身。

Page 49: 第五章   多维数组与广义表

3 .广义表的表示方法1 )用 LS=(a1 , a2 ,…, an) 形式,其中每一个 ai 为原子或广义表例如: A=(b,c), B=(a,A) , E=(a,E) 都是广义表。

2 )将广义表中所有子表写到原子形式,并利用圆括号嵌套例如,上面提到的广义表 A 、 B 、 C 可以描述为:A(b,c)B(a,A(b,c))E(a,E(a,E (…) ))

3 )将广义表用树和图来描述

Page 50: 第五章   多维数组与广义表

4 .广义表的深度一个广义表的深度是指该广义表展开后所含括号的层数。

. ( 1 ) A=(b,c) ( 2 ) B=(a,A) ( 3 ) C=(A,B) (4) E=(a,E)

depth=1 depth=2 depth=3 depth=∞5 .广义表的分类( 1 )线性表:元素全部是原子的广义表。( 2 )纯表:与树对应的广义表,见图 (1) 和 (2) 。( 3 )再入表:与图对应的广义表 ( 允许结点共享 ) ,见图 (3) 。( 4 )递归表:允许有递归关系的广义表,例如 (4) 。

这四种表的关系满足:递归表再入表 纯表 线性表

E

a


Recommended