1 / 52

第 5 章 数组和广义表

第 5 章 数组和广义表. 5.1 数组的定义和运算 5.2 数组的顺序存储和实现  5.3 特殊矩阵的压缩存储 5.4 广义表. 1. 数组的定义和运算. 数组是 n(n > 1) 个相同类型数据元素 a 0 ,a 1 ,…,a n-1 构成的有限序列 , 且该有限序列存储在一块地址连续的内存单元中。 数组的定义类似于采用顺序存储结构的线性表,是线性表在维数上的扩张,也就是线性表中的元素又是一个线性表。

raquel
Download Presentation

第 5 章 数组和广义表

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第5章 数组和广义表 5.1 数组的定义和运算 5.2 数组的顺序存储和实现 5.3 特殊矩阵的压缩存储 5.4 广义表

  2. 1. 数组的定义和运算 • 数组是n(n>1)个相同类型数据元素a0,a1,…,an-1构成的有限序列,且该有限序列存储在一块地址连续的内存单元中。 • 数组的定义类似于采用顺序存储结构的线性表,是线性表在维数上的扩张,也就是线性表中的元素又是一个线性表。 • n维数组,bi是第i维的长度,则n维数组共有 个数据元素,每个元素受n个关系的制约,就单个关系而言,这n个关系仍然是线性的。

  3. 数组具有以下性质: (1)数组中的数据元素数目固定。一旦定义了一个 数组,其数据元素数目不再有增减变化。 (2)数组中的数据元素具有相同的数据类型。 (3)数组中的每个数据元素都和一组惟一的下标值对 应。 (4)数组是一种随机存储结构。可随机存取数组中的 任意数据元素。 • 数组的基本操作 (1)取值 Value(A,&e, index1,..., indexn) (2)赋值Assign(&A, e, index1,…, indexn)

  4. 2.数组的存储结构 • 一维数组中, LOC(a0)确定, 每个数据元素占用L个存储单元,则任一数据元素ai的存储地址LOC(ai)就可由以下公式求出: LOC(ai)=LOC(a0)+i*L (0≤i≤n-1) • 二维数组,由于计算机的存储结构是线性的,如何用线性的存储结构存放二维数组元素就有一个行/列次序排放问题。

  5. Am×n看成m个行向量的线性表 • Am×n看成n个列向量的线性表

  6. 以行序为主序的存储方式 (BASIC, C, PASCAL) a00, …, a0,n-1, a10, …, a1,n-1, …, am-1,0, …, am-1,n-1 • LOC(aij)=LOC(a00)+(i*n+j)*L,其中n为列数 • 以列序为主序的存储方式 (FORTRAN) a00, …, am-1,0, a01, …, am-1,1, …, a0,n-1, … am-1,n-1 LOC(aij)=LOC(a00)+(j*m+i)*L , 其中m为行数

  7. n维数组 每一元素对应下标(j1, j2, …, jn),0≤ji≤bi-1,bi为第i维的长度。以行序为主序。 LOC(j1, j2, …, jn) = LOC(0,0,…,0) + (bn×…×b2×j1 + bn×…b3×j2 + … + bnjn-1 + jn) L

  8. 例: 对二维数组float a[5][4]计算: (1)数组a中的数组元素数目; (2)若数组a的起始地址为2000,且每个数组元素长度为32位(即4个字节),数组元素a[3][2]的内存地址。 元素数目共有5*4=20个。 C语言采用行序为主序的存储方式,则有: • LOC(a3,2)=LOC(a0,0)+(i*n+j)*k • =2000+(3*4+2)*4=2056。

  9. 3. 矩阵的压缩存储 • 特殊矩阵(对称矩阵、三角矩阵、对角矩阵) 特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵,为了节省存储空间,特别是在高阶矩阵的情况下,可以利用特殊矩阵的规律,对它们进行压缩存储,也就是说,使多个相同的非零元素共享同一个存储单元,对零元素不分配存储空间。

  10. 对称矩阵的压缩存储 若一个n阶方阵A中的元素满足ai,j=aj,i(1≤i,j≤n),则称其为n阶对称矩阵。 (1)只存储对称矩阵中上三角或下三角中的元素, (2)将n2个元素压缩存储到n(n+1)/2个元素的空间中,以一个一维数组作为A的存储空间。

  11. n2个元素←→ n(n+1)/2个元素 aij(1≤i,j≤n)←→ B[n(n+1)/2] • 1+2+…+(i-1)+j-1; • aij←→ aji

  12. 下三角矩阵的压缩存储 B[n(n+1)/2+1]

  13. 上三角矩阵的压缩存储 B[n(n+1)/2+1] (i-1)*n-i(i-1)/2+(i-1)+j-(i-1) -1= (i-1)(2n-i+2)/2+j-i

  14. 对角矩阵 :所有的非零元都集中在以主对角线为中心 的带状区域内。 半带宽为b的对角矩阵

  15. 三对角矩阵 B[3n-2]; k=2(i-1)+j-1; ①共有3n-2个非零元。 ②主对角线左下方的元素下标有关系式:i=j+1 k = 3(i-1)-1+1-1 = 3(i-1)-1 = 2(i-1)+i-2 = 2(i-1)+j-1. ③主对角线上的元素下标有关系式:i=j k = 3(i-1)-1+2-1 = 3(i-1) = 2(i-1)+i-1 = 2(i-1)+j-1. ④主对角线右上方的元素下标有关系式:i=j-1 k = 3(i-1)-1+3-1 = 3(i-1)+1 = 2(i-1)+i = 2(i-1)+j-1.

  16. 稀疏矩阵 非零元较零元少,且没有一定规律。 m×n的矩阵,有t个非零元, 矩阵的稀疏因子:δ=t/(m*n),当δ≤0.05时为稀疏矩阵。 压缩存储,只存储非零元 ①三元组顺序表(i,j,ai,j) ②行逻辑链接的顺序表 ③十字链表 • ((1,2,12),(1,3,9),(3,1,-3),(3,6,14), • (4,3,24),(5,2,18),(6,1,15),(6,4,-7))

  17. #define MaxSize 100 /*矩阵中非零元素最多个数*/ typedef struct { int i; /*行号*/ int j; /*列号*/ ElemType e; /*元素值*/ } Triple; /*三元组定义*/ typedef struct { int rows; /*行数*/ int cols; /*列数*/ int nums; /*非零元素个数*/ Triple data[MaxSize+1]; /*data[0]未用*/ } TSMatrix; /*三元组顺序表定义*/

  18. 用三元组表实现稀疏矩阵的转置运算 一个6*7的矩阵A,以行序为主序顺序排列

  19. 矩阵的转置,方法一:

  20. Status TransposeSMatrix(TSMatrix A, TSMatrix &B) { B.rows=A.cols; B.cols=A.rows; B.nums=A.nums; if (B.nums!=0){ q = 1; /*q为B.data的下标*/ for (col=1; col<=A.cols; col++) for (p=1; p<=A.nums; p++) /*p为A.data的下标*/ if (A.data[p].j == col){ B.data[q].i = A.data[p].j; B.data[q].j = A.data[p].i; B.data[q].e = A.data[p].e; q++; } } return OK; }

  21. 效率分析 1、主要时间消耗在查找M.data[p].j=col的元素,由两重循环完成: for(col=1; col<=M.nu; col++) 循环次数=nu {for(p=1; p<=M.tu; p++) 循环次数=tu 所以该算法的时间复杂度为O(nu*tu) ----即M的列数与M中非零元素的个数之积 最恶劣情况:M中全是非零元素,此时tu=mu*nu, 时间复杂度为 O(nu2*mu ) 注:若M中基本上是非零元素时,即使用非压缩传统转置算法的时间复杂度也不过是O(nu*mu)(程序见下面) 结论:压缩转置算法不能滥用。 前提:仅适用于非零元素个数很少(即tu<<mu*nu)的情况。

  22. 方法1时间复杂度: O(cols*nums) 当非零元个数nums和cols*rows同数量级时, O(rows*cols2) 仅适用于nums<<rows*cols. 常规存储方式时, 实现矩阵转置的经典算法如下: for(i=0; i<m; i++) for (j=0; j< n; j++) T[i][ j] = M[j][i]; O(cols*rows)

  23. p 1 ① (1, 3, -3) 2 ② (1, 6, 15) 3 ③ (2 ,1, 12) ④ 4 (2, 5, 18) ⑤ (3, 1, 9) ⑥ (3, 4, 24) ⑦ (4, 6, -7) ⑧ (5, 3, 14) 方法2快速转置 思路:依次把a.data中的元素直接送入b.data的恰当位置上(即M三元组的p指针不回溯)。 q 三 元 组 表 b.data 三 元 组 表 a.data 3 5 关键:怎样寻找b.data的“恰当”位置?

  24. 设计思路: 如果能预知M矩阵每一列(即T的每一行)的非零元素个数,又能预知第一个非零元素在b.data中的位置,则扫描a.data时便可以将每个元素准确定位(因为已知若干参考点)。 请注意a.data特征:每列首个非零元素必定先被扫描到。 技巧:利用带辅助向量的三元组表,它正好携带每行(或列)的非零元素个数 NUM(i)以及每行(或列)的第一个非零元素在三元组表中的位置POS(i)等信息。 请回忆: 规律:POS(1)=1 POS(i)=POS(i-1)+NUM(i-1) 不过我们需要的是按列生成的M矩阵的辅助向量。

  25. 0129 0 0 0 00 0 0 0 0 -3 0 0 0 14 0 0 0 24 0 0 0 018 0 0 0 0 150 0 -7 0 0 令:M中的列变量用col表示;num[ col ]:存放M中第col 列中非0元素个数,cpot[ col ]:存放M中第col列的第一个非0元素的位置,(即b.data中待计算的“恰当”位置所需参考点) col 1 2 3 4 5 6 M 3 5 7 8 8 规律: cpot(1)=1 cpot[col]=cpot[col-1] + num[col-1] 讨论:按列优先的辅助向量求出后,下一步该如何操作? 由a.data中每个元素的列信息,即可直接查出b.data中的重要参考点之位置,进而可确定当前元素之位置!

  26. Status FastTranSMatrix(TSMatrix A, TSMatrix &B) { B.rows=A.cols; B.cols=A.rows; B.nums=A.nums; if(B.nums){ for(col=1; col<=A.nums; ++col) num[col] = 0; for(t=1; t<=A.nums; ++t) ++num[A.data[t].j]; cpot[1] = 1; for(col=2; col<=A.nums; ++col) cpot[col] = cpot[col-1]+num[col-1]; for(p=1; p<=A.nums; ++p){ col = A.data[p].j; q = cpot[col]; B.data[q].i = A.data[p].j; B.data[q].j = A.data[p].i; B.data[q].e = A.data[p].e; ++cpot[col]; } } return OK; } O(cols+nums) 当nums和cols*rows同数量级时 O(rows*cols)

  27. row col e 1 3 -3 cpot[1] cpot[2] cpot[2] cpot[3] cpot[3] cpot[4] cpot[6] 1 2 3 4 5 6 7 8 1 6 15 2 1 12 2 5 18 3 1 9 3 4 24 4 6 -7 6 3 14

  28. 快速转置算法的效率分析: 1.与常规算法相比,附加了生成辅助向量表的工作。增开了2个长度为列长的数组(num[ ]和cpos[ ])。 2.从时间上,此算法用了4个并列的单循环,而且其中前3个单循环都是用来产生辅助向量表的。 for(col = 1; col <=M.nu; col++) 循环次数=nu; for( i = 1; i <=M.tu; i ++) 循环次数=tu; for(col = 2; col <=M.nu; col++) 循环次数=nu; for( p =1; p <=M.tu ; p ++ ) 循环次数=tu; 该算法的时间复杂度=(nu*2)+(tu*2)=O(nu+tu) 讨论:最恶劣情况是tu=nu*mu(即矩阵中全部是非零元素), 而此时的时间复杂度也只是O(mu*nu),并未超过传统转置算法的时间复杂度。 传统转置:O(mu*nu) 压缩转置:O(mu*tu) 压缩快速转置:O(nu+tu)——牺牲空间效率换时间效率。 小结:

  29. row col val down right 数组的链式存储结构 2.三元组线性表的链式存储结构称为十字链表 当矩阵非零元的位置或个数经常变化时,采用链式存储结构更为恰当. 1). 链表结点结构 链域down:链接同一列中下一个非零元; 链域right:链接同一行中下一个非零元; 稀疏矩阵中同一列的非零元通过down链接成一个链表,同一行的非零元通过right链接成一个链表。

  30. ∧ 1 2 12 1 3 9 ∧ ∧ 3 1 -3 3 6 14 ∧ ∧ 4 3 24 ∧ ∧ 5 2 18 ∧ ∧ 6 1 15 6 4 -7 ∧ ∧ ∧ 十字链表

  31. 小结 基本学习要点如下: (1)理解数组和一般线性表之间的差异。 (2)重点掌握数组的顺序存储结构和元素地址计算方法。 (3)掌握各种特殊矩阵如对称矩阵、上、下三角矩阵和对角矩阵的压缩存储方法。 (4)掌握稀疏矩阵的各种存储结构以及基本运算实现算法。 (5)灵活运用数组这种数据结构解决一些综合应用问题。

  32. 练习: 1. 将一个A[1..100, 1..100]的三对角矩阵,按行优先存入一维数组B[1..298],A中元素a66,65在B数组中的位置k=?。 在主对角线左下方,65*3-1+1 = 195。

  33. 2. 有二维矩阵A[m][n],其元素类型为int,每行每列按从小到大有序,试给出一个算法求数组中值为x的元素的行号和列号,前提是x在A中存在,要求比较次数不超过m+n次。 void search(int **A, int x, int &i, int &j) { i = 0; j = n-1; while(A[i][j] != x){ if(A[i][j] > x) j--; else i++; } }

  34. 3. ()编写算法,求串s所含不同字符的总数和每种字符的个数。 void StrCount(SString s) { SString r; int *num = new int[s[0]]; for(i=1; i<=s[0]; i++) num[i] =0; r[1] = s[1]; num[1] = 1; r[0] = 1; for(i=2; i<=s[0]; i++){ j = 1; while( s[i]!=r[j] && j<=r[0]) ++j ++num[j]; if(j>r[0]) { r[j] = s[i]; ++r[0]; } } }

  35. void StrCount(SString s) { int num[256]; for(i=0; i<256; ++i) num[i] = 0; for(i=0; i<s[0]; ++i) ++num[int(s[i])]; j = 0; for(i=0; i<256; ++i) if(num[i]) ++j; }

  36. 5.4 广义表 1. 广义表的定义(列表) 广义表是线性表的推广,是由零个或多个单元素或子表所 组成的有限序列。 LS=(a1,a2,…,ai,…,an) ai:是单个数据元素,则ai是广义表的原子;如果ai是一个广义表,则ai是广义表的子表。 规定用小写字母表示原子,用大写字母表示广义表的 表名。 • 广义表与线性表的区别: 线性表的成份都是结构上不可分的单个数据元素,而广 义表的成份即可以是单元素,也可以是有结构的表,其定义 是递归的定义。

  37. 广义表的长度:广义表中所含元素的个数n,n≥0。广义表的长度:广义表中所含元素的个数n,n≥0。 • 广义表的深度:广义表展开后所含的括号的最大层数。(广义表中括号嵌套的最大次数,广义表中括弧的重数)

  38. 特点 • 有次序性 • 有长度 • 有深度 • 可递归 • 可共享 一个直接前驱和一个直接后继 =表中元素个数 =表中括号的重数 自己可以作为自己的子表 可以为其他广义表所共享 特别提示:任何一个非空表,表头可能是原子,也可能是列表;但表尾一定是列表。

  39. D=( )空表,长度为0,深度为1。 • A=(a, (b, c)) 长度为2,第一个元素为原子a,第二个元素为子表(b, c),深度为2。 • B=(A, A, D) 长度为3, 其前两个元素为表A, 第三个元素为空表D,深度为3。 • C=(a, C) 长度为2递归定义的广义表,C相当于无穷表C=(a,(a, (a,…))),深度无限。 递归表的深度是无穷值,长度是有限值。 • F=((a, (a, b), ((a, b), c))) 长度为1,深度为4。

  40. 广义表的表头(Head)和表尾(Tail): 当广义表非空时,称第一个元素a1为广义表的表头, 其余元素组成的表(a2, a3, …,an)称为广义表的表尾。 表头可能是原子,也可能是广义表,但表尾一定是广 义表。 • 广义表的图形表示 用方框表示原子,用圆圈表示广义表。

  41. A=( ); B=(e); C=(a, (b, c, d)) D=(A, B, C); E=((a, (a, b), ((a, b), c)))

  42. 2. 广义表的基本操作 • 取表头 GetHead(LS) = a1。 • 取表尾 GetTail(LS) = (a2,a3,…,an)。 ①B=(e) GetHead(B) = e; GetTail(B) = ( ). ②A=(a, ((b, c), d, e)) GetHead( GetHead( GetTail(A))) = GetHead( GetHead( (((b, c), d, e)) )) = GetHead( ((b, c), d, e) ) = (b, c). ③A=( ); B = ( ( ) ) A空表,长度0,深度1,无表头和表尾; B长度1,深度2,表头( ),表尾( )。

  43. 3. 广义表的存储结构 广义表是一种递归的数据结构,因此很难为每个广义表分配固定大小的存储空间,所以其存储结构只好采用动态链式结构。 • 广义表的头尾链表存储表示 • 广义表的扩展线性链表存储表示

  44. 原子结点 表结点 • 广义表的头尾链表存储表示 hp:指向该表的表头;tp:指向该表的表尾。 typedef enum {ATOM, LIST} ElemTag; //ATOM=0,表示原子;LIST=1,表示子表 typedef struct GLNode { ElemTag tag; //标志位tag用来区别原子结点和表结点 union{  AtomType atom; //原子结点的值域atom struct { struct GLNode * hp, *tp; } ptr;//表结点的指针域 };  }*GList;

  45. C=(a, (b, c, d)) • A=( ) A = NULL; (a, (b, c, d)) ((b, c, d)) (b, c, d) (c, d) (d)

  46. (a, b, c) (b, c) ( c) • D=( ); A= (a, b, c); B=(A, A, D); C = (a, C). (A, D) (D)=(( )) (A, A, D)

  47. 孩子兄弟表示法 • 在孩子兄弟表示法中,也有两种结点:一种是有孩子的表结点,另一种是无孩子的元素结点,用以表示单元素。在表结点中应该包括一个指向第一个孩子的指针和指向兄弟的指针;而在元素结点中应该包括一个指向兄弟的指针和单元素的元素值。为了区分这两类结点,在结点中还要设置一个标志域,如果标志为1,则表示该结点为表结点;如果标志为0,则表示该结点为元素结点。

  48. 广义表的扩展线性链表存储表示 • (孩子兄弟表示法带表头结点) 原子结点 表结点 hp指向第一个元素,tp指向与本元素同一层的下一元素。

  49. C=(a, (b, c, d)) (b, c, d)

  50. D=( ); A=(a, (b, c)); B=(A, A, D); C=(a, C)

More Related