• 沒有找到結果。

数据结构(C++版)(第二版) - 万水书苑-出版资源网

N/A
N/A
Protected

Academic year: 2021

Share "数据结构(C++版)(第二版) - 万水书苑-出版资源网"

Copied!
25
0
0

加載中.... (立即查看全文)

全文

(1)第 5 章 多维数组和广义表. 本章主要介绍多维数组的概念及其在计算机中的存放、一些特殊矩阵的压缩存储及相应 运算的实现、广义表的概念和存储结构及其相关运算的实现。.     . 多维数组的定义及其在计算机中的存储表示 对称矩阵、三角矩阵、对角矩阵等特殊矩阵在计算机中的压缩存储表示及地址计算公式 稀疏矩阵的三元组表示及两种转置算法的实现 稀疏矩阵的十字链表表示及相加算法的实现 广义表存储结构表示及其基本运算. 5.1. 多维数组. 5.1.1 多维数组的概念 数组是大家都已经很熟悉的一种数据类型,几乎所有高级程序设计语言中都设定了数组 类型。在此,仅简单地讨论数组的逻辑结构及其在计算机内的存储方式。 1.一维数组 一维数组可以看成是一个线性表或一个向量(第 2 章中已经介绍),它在计算机内是存放在 一块连续的存储单元中,适合于随机查找。这在第 2 章的线性表的顺序存储结构中已经介绍。 2.二维数组 二维数组可以看成是向量的推广。例如,设 A 是一个有 m 行 n 列的二维数组,则 A 可以 表示为: a 01  a 0 n 1   a 00  a a 11  a 1n 1  10 A  ……………………………    a m 10 a m 11  a m 1n 1  在此,可以将二维数组 A 看成是由 m 个行向量[X0,X1,…,Xm-1]T 组成,其中,Xi=( ai0, ai1, …,ain-1),0≤i≤m-1;也可以将二维数组 A 看成是由 n 个列向量[y0, y1, …,yn-1]组成,其中 yi=(a0i, a1i, …,am-1i),0≤i≤n-1。由此可知二维数组中的每一个元素最多可有两个直接前驱和两 个直接后继(边界除外),故是一种典型的非线性结构。.

(2) 第 5 章 多维数组和广义表. 75. 3.多维数组 同理,三维数组最多可有 3 个直接前驱和 3 个直接后继,三维以上的数组可以作类似分 析。因此,可以把三维以上的数组称为多维数组,多维数组可有多个直接前驱和多个直接后继, 故多维数组是一种非线性结构。 5.1.2 多维数组在计算机内的存储 怎样将多维数组中的元素存入到计算机内存中呢?由于计算机内存结构是一维的(线性 的),因此,用一维内存存放多维数组就必须按某种次序将数组元素排成一个线性序列,然后 将这个线性序列顺序存放在存储器中,具体实现方法在下一节中介绍。. 5.2. 多维数组的存储结构. 由于数组是先定义后使用,且为静态分配存储单元,也就是说,一旦建立了数组,则结 构中的数组元素个数和元素之间的关系就不再发生变动,即它们的逻辑结构就固定下来了,不 再发生变化。因此,采用顺序存储结构表示数组是顺理成章的事。本章中,仅重点讨论二维数 组的存储,三维及三维以上(多维)的数组,可以作类似分析。 多维数组的顺序存储有两种形式:行优先顺序存储和列优先顺序存储。 5.2.1 行优先顺序 1.存放规则 行优先顺序存储也称为低下标优先存储或左边下标优先于右边下标存储。具体实现时, 按行号从小到大的顺序,先将第一行中的元素按列号从小到大全部存放好, 再存放第二行元素、 第三行元素,依此类推…… 在 BASIC 语言、Pascal 语言、C/C++语言等高级程序设计语言中,都是按行优先顺序存 放的。例如,对刚才的 Am×n 二维数组,可用如下形式存放到内存中:a00,a01,…,a0n-1,a10, a11,...,a1 n-1,…,am-1 0 ,am-1 1,…,am-1 n-1,即二维数组按行优先存放到内存后变成了一个 线性序列(线性表)。 因此,可以得出多维数组按行优先顺序存放到内存的规律:最左边下标变化最慢,最右 边下标变化最快,右边下标变化一遍,与之相邻的左边下标才变化一次。因此,在算法中,若 用循环语句的嵌套来实现按行优先顺序存放,最左边下标可以看成是最外层循环,最右边下标 可以看成是最内层循环。 2.地址计算 由于多维数组在内存中排列成一个线性序列,因此,若知道第一个元素的内存地址,如 何求得其他元素的内存地址呢?可以将它们的地址排列看成是一个等差数列,假设每个元素占 d 个字节,元素 aij 的存储地址应为第一个元素的地址加上排在 aij 前面的元素所占用的单元地 址数,而 aij 的前面有 i 行(0~i-1)共 i×n 个元素,而本行前面又有 j 个元素,故 aij 的前面一 共有 i×n+j 个元素,设 a00 的内存地址为 LOC(a00),则 aij 的内存地址按等差数列计算为 LOC(aij)=LOC(a00)+(i×n+j)×d。同理,三维数组 Am×n×p 按行优先顺序存放的地址计算公式为: LOC(aijk)=LOC(a000)+(i×n×p+j×p+k)×d。.

(3) 数据结构(C++版)(第二版). 76. 5.2.2 列优先顺序 1.存放规则 列优先顺序存储也称为高下标优先存储或右边下标优先于左边下标存储。具体实现时, 按列号从小到大的顺序,先将第一列中的元素按行号从小到大的顺序全部存放好,再存放第二 列元素、第三列元素,依此类推…… 在 FORTRAN 程序设计语言中,数组是按列优先顺序存放的。例如,对前面提到的 Am×n 二维数组,可以按如下的形式存放到内存中:a00,a10,…,am-10,a01,a11,…,am-1 1,…, a0 m-1,a1m-1,...,am-1 n-1。因此,二维数组按列优先顺序存放到内存后,也变成了一个线性序 列(线性表) 。 因此,可以得出多维数组按列优先顺序存放到内存的规律:最右边下标变化最慢,最左 边下标变化最快,左边下标变化一遍,与之相邻的右边下标才变化一次。因此,在算法中,若 用循环语句的嵌套来实现按列优先顺序存放,最右边下标可以看成是最外层循环,最左边下标 可以看成是最内层循环。 2.地址计算 同样与行优先顺序存储类似,若知道第一个元素的内存地址,则同样可以求得按列优先 顺序存储的某一元素 aij 的地址。 对二维数组 Am×n 有:LOC(aij)=LOC(a00)+(j×m+i)×d。 对三维数组 Am×n×p 有:LOC(aijk)=LOC(a000)+(k×m×n+j×m+i)×d。. 5.3. 特殊矩阵及其压缩存储. 矩阵是一个二维数组,它是很多科学与工程计算问题中研究的数学对象。矩阵可以用行 优先顺序或列优先顺序的方法顺序存储到内存中,但是,当矩阵的阶数很大时,将会占用较多 的存储单元。而当里面的元素分布呈现某种规律时,从节约存储单元的角度出发,可考虑若干 元素共用一个存储单元,即进行压缩存储。所谓压缩存储是指:为多个值相同的元素只分配一 个存储空间,值为 0 的元素不分配空间。或者理解为:将二维数组(矩阵)压缩到一个占用存 储单元数目较少的一维数组中。但是,在进行压缩存储时,虽然节约了存储单元,但怎样在压 缩存储后直接找到某元素呢?因此还必须给出压缩前下标(二维数组的行、列)和压缩后下标 (一维数组的下标)之间的变换公式,才能使压缩存储变得有意义。下面将分几种情况的特殊 矩阵来讨论。 5.3.1 特殊矩阵 1.对称矩阵 若一个 n 阶方阵 A 中的元素满足条件:aij=aji,其中 0≤i, j≤n-1,则称 A 为对称矩阵。 例如,如图 5-1 所示是一个 3×3 的对称矩阵。 2.三角矩阵 (1)上三角矩阵。. 1 2 3. A= 2 5 4   3 4 6. 图 5-1. 一个对称矩阵.

(4) 第 5 章 多维数组和广义表. 77. 即矩阵上三角部分元素是随机的,而下三角部分元素全部相同(为某常数 C 或全为 0), 具体形式如图 5-2(a)所示。 (2)下三角矩阵。 即矩阵的下三角部分元素是随机的,而上三角部分元素全部相同(为某常数 C 或全为 0), 具体形式如图 5-2(b)所示。  a 00  c   ...   c. a 01 ... a 0n 1  a11 ... a1n 1  ... ... ...   c c a n 1n 1 . c  a 00  a a 10 11   ... ...   a n 10 a n 11. (a)上三角矩阵. ... c  ... c  ... ...   ... a n 1n 1 . (b)下三角矩阵 图 5-2. 三角矩阵. 3.对角矩阵 若矩阵中所有非零元素都集中在以主对角线为中心的带状区域中,区域外的值全为 0,则 称为对角矩阵。 常见的对角矩阵有三对角矩阵、五对角矩阵、七对角矩阵等。 例如,如图 5-3 所示为 7×7 的三对角矩阵(即有 3 条对角线上的元素非零) 。  a 00 a  10  0   0  0   0  0 . a 01 0 a11 a12 a 21 a 22 0 a 32 0 0 0 0 0 0. 图 5-3. 0 0 a 23 a 33 a 43 0 0. 0 0 0 a 34 a 44 a 54 0. 0 0 0 0 a 45 a 55 a 65. 0  0  0   0  0   a 56  a 66 . 一个 7×7 的三对角矩阵. 5.3.2 压缩存储 在上面介绍的几种特殊矩阵(对称矩阵、上三角矩阵、下三角矩阵、三对角矩阵、多对 角矩阵等)中,元素的分布有规律,从节省存储单元的角度来考虑,可以进行压缩存储。 1.对称矩阵 若矩阵 Ann 是对称的,对称的两个元素可以共用一个存储单元,这样,原来 n 阶方阵需 2 要 n 个存储单元,若采用压缩存储,则仅需要 n(n+1)/2 个存储单元,将近节约一半的存储单 元,这就是实现压缩的好处。但是,将 n 阶对称方阵压缩存放到一个向量空间 s[0]到  n(n  1)  s -1 中,我们怎样找到 s[k]与 a[i][j]的一一对应关系,使我们在 s[k]中能直接找到  2  a[i][j]呢? 这里仅以行优先顺序存放为例且分两种方式讨论。 (1)只存放下三角部分。.

(5) 数据结构(C++版)(第二版). 78. 由于对称矩阵关于主对角线对称,因此只需要存放主对角线及主对角线以下的元素。这  n(n  1)  时,a[0][0]存入 s[0]中,a[1][0]存入 s[1]中,a[1][1]存入 s[2]中,…,a[n-1][n-1]存入 s  -1  2  中,具体过程如图 5-4 所示。  a 00   a  a 11  10   a 20  a 21 a 22   ... ... ... ...   ... a   n 10 a n 11 a n 12 ... a n 1n 1 . (a)一个下三角矩阵 0. 1. 2. 3. 4. 5. 6. 7. ……. n(n 1) -3 2. n(n 1) -2 2. n(n 1) -1 2. a00. a10. a11. a20. a21. a22. a30. a31. ……. an-1n-3. an-1n-2. an-1n-1. (b)下三角矩阵的压缩存储形式 图 5-4. 对称矩阵及用下三角压缩存储. 这时 s[k]与 a[i][j]的对应关系为: i(i+1)/2+j k  j(j+1)/2+i. (i ≥ j) (i<j). 由上面的对应关系读者很容易推出:当 i≥j 时,aij 在下三角部分中,aij 前面有 i 行,共有 1+2+3+…+i 个元素,而 aij 是第 i 行的第 j 个元素,即有 k=1+2+3+…+i+j=i(i+1)/2+j;当 i<j 时, aij 在上三角部分中,但与 aji 对称,故只需在下三角部分中找 aij,即只需将 i 与 j 交换,即 k=j(j+1)/2+i。 (2)只存放上三角部分。 对于对称矩阵,除了用下三角形式存放外,还可以用上三角形式存放,这时 a[0][0]存入 s[0]中,a[0][1]存入 s[1]中,a[0][2]存入 s[2]中,…,a[0][n-1]存入 s[n]中,a[1][1]存入 s[n+1]  n(n  1)  中,…,a[n-1][n-1]存入 s  -1 中,具体如图 5-5 所示。  2   a 00      ...  . a 01 a 02 ... a11 a12 ... .... a 22 .... a 0n 1  a1n 1  ... a 2n 1   ... ...  a n 1n 1 . (a)一个上三角矩阵 0. 1. 2. 3. 4. 5. 6. 7. a00 a01 a02 a03 a04 a05 a06 a07. ……. n(n 1) -3 2. n(n 1) -2 2. n(n 1) -1 2. ……. an-2n-2. an-2n-1. an-1n-1. (b)上三角矩阵的压缩存储形式 图 5-5. 对称矩阵及用上三角压缩存储.

(6) 第 5 章 多维数组和广义表. 79. 这时 s[k]与 a[i][j]的对应关系可以按下面的方法推出:当 i≤j 时,aij 在上三角部分中,前 面的行号从 0~i-1 共有 i 行,第 0 行有 n 个元素,第 1 行有 n-1 个元素,……,第 i-1 行有 n-(i-1) i(i -1) 个元素,因此,前面 i 行共有 n+n-1+…+n-(i-1)=i*n个元素,而 aij 是本行第 j-i 个元素, 2 j( j -1) j( j -1) 故 k=i*n+j-i;当 i>j 时,由对称关系,交换 i 与 j,即有 k=j*n+i-j。 2 2 故 s[k]与 a[i][j]的对应关系为: i(i -1)  (i ≤ j) i * n - 2  j - i k  j*n - j(j -1)  i - j (i>j)  2 2.三角矩阵 (1)下三角矩阵。 下三角矩阵的压缩存放与对称矩阵用下三角形式存放类似,但必须多一个存储单元存放 上三角部分元素,使用的存储单元数目为 n(n+1)/2+1,故可以将 nn 的下三角矩阵压缩存放到 只有 n(n+1)/2+1 个存储单元的向量中。假设仍按行优先存放,这时 s[k]与 a[i][j]的对应关系为: (i ≤ j) i(i+1)/2+j k=  (i<j) n(n+1)/2 (2)上三角矩阵。 和下三角矩阵的存储类似,共需要. n(n  1) +1 个存储单元,假设仍按行优先顺序存放,这 2. 时 s[k]与 a[i][j]的对应关系为: i * n - i(i -1) / 2  j - i k=  n(n  1) / 2. (i ≤ j) (i  j). 3.对角矩阵 这里仅讨论三对角矩阵的压缩存储。五对角矩阵、七对角矩阵等,读者可以作类似分析。 在一个 nn 的三对角矩阵中,只有 n+n-1+n-1 个非零元,故只需要 3n-2 个存储单元,零 元已不占用存储单元。 故可以将 nn 三对角矩阵 Ann 压缩存放到只有 3n-2 个存储单元的 s[3n-2]向量中,假设仍 按行优先顺序存放,则 s[k]与 a[i][j]的对应关系为: (i  j  1) 3i -1  k= 3i (i  j或k  2i  j) 3i  1 (i  j -1) . 5.4. 稀疏矩阵. 在上节提到的特殊矩阵中,元素的分布呈现某种规律,故一定能找到一种合适的方法将 它们进行压缩存放。但是,在实际应用中,还经常会遇到一类矩阵:其矩阵阶数很大,非零元 个数较少,零元很多,但非零元的排列(分布)没有一定规律,我们称这一类矩阵为稀疏矩阵。.

(7) 数据结构(C++版)(第二版). 80. 按照压缩存储的概念,要存放稀疏矩阵的元素,由于没有某种规律,除存放非零元的值 外,还必须存储适当的辅助信息(行、列号)才能迅速确定一个非零元是矩阵中的哪一个位置 上的元素。下面将介绍稀疏矩阵的几种存储方法及一些算法的实现。 5.4.1 稀疏矩阵的存储 1.三元组表 在压缩存放稀疏矩阵的非零元的同时,若还存放此非零元所在的行号和列号,则称为三 元组表法,即称稀疏矩阵可用三元组表进行压缩存储,但它是一种顺序存储(按行优先顺序存 放)。一个非零元有行号、列号、值,为一个三元组,整个稀疏矩阵中非零元的三元组合起来 称为三元组表。 此时,数据类型可描述如下: #include <iostream.h> const int maxsize=100; class node { public: int i , j; int v; }; class {. //定义非零元的最大数目 //定义一个三元组. //非零元的行、列号 //非零元值. sparmatrix. //定义稀疏矩阵. public: int rows,cols ; int terms; node data [maxsize];. //稀疏矩阵的行、列数 //稀疏矩阵的非零元个数 //三元组表. };. 图 5-6 和图 5-7 给出了两个稀疏矩阵。.  0 12 9 0 0 0 0 0   -3 0 0 0   0 0 24 0  0 18 0 0  15 0 0 -7. 图 5-6. 0 0 0 0 0 0  0 14 0   0 0 0 0 0 0  0 0 0 . 稀疏矩阵 M. 0 12  9  0 0  0 0 . 图 5-7. 0 0 0 0 0 0 0. -3 0 0 0 0 14 0. 0 0 24 0 0 0 0. 0 18 0 0 0 0 0. 15  0  0   -7  0   0  0 . 稀疏矩阵 N(M 的转置). 稀疏矩阵 M 和 N 的三元组表如图 5-8 所示。 2.带行指针的链表 把具有相同行号的非零元用一个单链表连接起来,稀疏矩阵中的若干行组成若干个单链 表,合起来称为带行指针的链表。例如,图 5-6 所示的稀疏矩阵 M 的带行指针的链表描述形 式如图 5-9 所示。.

(8) 第 5 章 多维数组和广义表. i 0 0 2 2 3 4 5 5. M 的三元组表 j 1 2 0 5 2 1 0 3. v. i. 12 9 -3 14 24 18 15 -7. 0 0 1 1 2 2 3 5. 图 5-8. N 的三元组表 j 2 5 0 4 0 3 5 2. 81. v -3 15 12 18 9 24 -7 14. 稀疏矩阵 M 和 N 的三元组表. 行指针 0. 0 1 2. 0 2 9 ^. 1 ^ 2. 2 0 -3. 2 5 4 ^. 3. 3 2 24 ^. 4. 4 1 18 ^. 5. 5 0 15 图 5-9. 5 3 -7 ^. 带行指针的链表. 3.十字链表 当稀疏矩阵中非零元的位置或个数经常变动时,三元组就不适合作稀疏矩阵的存储结构 了,此时采用链表作为存储结构更为恰当。 十字链表为稀疏矩阵的链接存储中的一种较好的存储方法,在该方法中,每一个非零元 用一个结点表示,结点中除了表示非零元所在的行、列和值的三元组(i,j,v)外,还需要增加两 个链域:行指针域(rptr),用来指向本行中的下一个非零元;列指针域(cptr),用来指向本列 中的下一个非零元。稀疏矩阵中同一行的非零元通过向右的 rptr 指针链接成一个带表头结点的 循环链表,同一列的非零元也通过 cptr 指针链接成一个带表头结点的循环链表。因此,每个 非零元既是第 i 行循环链表中的一个结点,又是第 j 列循环链表中的一个结点,相当于处在一 个十字交叉路口上,故称链表为十字链表。 另外,为了运算方便,我们规定行、列循环链表的表头结点和表示非零元的结点一样, 也定为 5 个域,且规定行、列、域值为 0,并且将所有的行、列链表和头结点一起链成一个循 环链表。 在行(列)表头结点中,行、列域的值都为 0,故两组表头结点可以共用,即第 i 行链表 和第 i 列链表共用一个表头结点,这些表头结点本身又可以通过 v 域(非零元值域,但在表头 结点中为 next,指向下一个表头结点)相链接。另外,再增加一个附加结点(由指针 hm 指示, 行、列域分别为稀疏矩阵的行、列数目) ,附加结点指向第一个表头结点,则整个十字链表可 由 hm 指针唯一确定。 例如,图 5-6 所示的稀疏矩阵 M 的十字链表描述形式如图 5-10 所示。.

(9) 数据结构(C++版)(第二版). 82. hm. 6 7. 0 0. 0 0. 0 0. 0 0. 1 2 12. 1 3 9. 0 0. 0 0. 0 0. 0 0. 0 0. 0 0. 3 6 14. 3 1 -3. 0 0. 4 3 24. 5 2 18. 0 0. 0 0. 6 4 -7. 6 1 15. 图 5-10. 稀疏矩阵的十字链表. 十字链表的数据类型描述如下: class linknode { public: int i, j; linknode *cptr, *rptr; union vnext { int v; linknode *next; } k; … };. //行、列号 //列、行指针 //定义一个共用体 //表结点使用 v 域,表示非零元值 //表头结点使用 next 域指向下一个表头结点 //成员函数的说明及定义. 5.4.2 稀疏矩阵的运算 1.稀疏矩阵的转置运算 下面将讨论三元组表上如何实现稀疏矩阵的转置运算。 转置是矩阵中最简单的一种运算。对于一个 mn 的矩阵 A,它的转置矩阵 B 是一个 nm 的矩阵,且 B[i][j]=A[j][i](0≤i<n,0≤j<m)。例如,图 5-6 给出的 M 矩阵和图 5-7 给出的 N 矩阵互为转置矩阵。 在三元组表表示的稀疏矩阵中,怎样求得它的转置呢?从转置的性质知道,将 A 转置为.

(10) 第 5 章 多维数组和广义表. 83. B,就是将 A 的三元组表 a.data 变为 B 的三元组表 b.data,这时可以将 a.data 中 i 和 j 的值互换, 则得到的 b.data 是一个按列优先顺序排列的三元组表,再将它的顺序适当调整,变成行优先排 列,即得到转置矩阵 B。 (1)按照 A 的列序进行转置。 由于 A 的列即为 B 的行,在 a.data 中,按列扫描,则得到的 b.data 必按行优先存放。但 为了找到 A 的每一列中所有的非零元,每次都必须从头到尾扫描 A 的三元组表(有多少列, 则扫描多少遍),这时算法描述如下: class node. //定义一个三元组. { public: int i , j; int v; }; class. //非零元的行、列号 //非零元值. sparmatrix. //定义稀疏矩阵. { public: int rows,cols ; int terms; node data [maxsize]; void transpose(sparmatrix a,sparmatrix b); void fastrans(sparmatrix a,sparmatrix b);. //稀疏矩阵的行、列数 //稀疏矩阵的非零元个数 //三元组表 //转置 //快速转置. }; void sparmatrix::transpose(sparmatrix a,sparmatrix b) { b.rows=a.cols; b.cols=a.rows; b.terms=a.terms; if(b.terms>0) { int bno=0; for (int col=0; col<a.cols; col++) //按列号扫描 for(int ano=0;ano<a.terms;ano++) //对三元组表扫描 if(a.data[ano].j==col) //进行转置 { b.data[bno].j=a.data[ano].i; b.data[bno].i=a.data[ano].j; b.data[bno].v=a.data[ano].v; bno++; } } for(int i=0;i<a.terms;i++) //输出转置后的三元组结果 cout<<b.data[i].i<<" "<<b.data[i].j<<" "<<b.data[i].v<<endl; } void main() { sparmatrix a,b;.

(11) 数据结构(C++版)(第二版). 84. cin>>a.rows>>a.cols>>a.terms;. //输入稀疏矩阵的行、列数及非零元的个数. for( int i=0;i<a.terms;i++) cin>>a.data[i].i>>a.data[i].j>>a.data[i].v; //输入转置前的稀疏矩阵的三元组 for(i=0;i<a.terms;i++) cout<<a.data[i].i<<" "<<a.data[i].j<<" "<<a.data[i].v<<endl; //输出转置前的三元组结果 a.transpose( a,b); //调用转置算法 }. 分 析 这 个 算 法 , 主 要 工 作 在 col 和 ano 二 重 循 环 上 , 故 算 法 的 时 间 复 杂 度 为 O(a.cols*a.terms)。通常的 m×n 阶矩阵转置算法可描述为: for(col=0; col<n; col++) for (row=0;row<m;row++) b[col][row]=a[row][col];. 它的时间复杂度为 O(m×n)。而一般的稀疏矩阵中非零元的个数 a.terms 远大于行数 m, 故压缩存储时,进行转置运算,虽然节省了存储单元,但增大了时间复杂度,故此算法仅适应 于 a.terns<<a.rows a.cols 的情形。 (2)按照 A 的行序进行转置。 即按 a.data 中三元组的次序进行转置,并将转置后的三元组放入 b 中恰当的位置。若能在 转置前求出矩阵 A 的每一列 col(即 B 中的每一行)的第一个非零元转置后在 b.data 中的正确 位置 pot[col](0≤col<a.cols),那么在对 a.data 的三元组依次作转置时,只要将三元组按列号 col 放置到 b.data[pot[col]]中,之后将 pot[col]内容加 1,以指示第 col 列的下一个非零元的正确 位置。为了求得位置向量 pot,只要先求出 A 的每一列中非零元的个数 num[col],然后利用下 列公式: pot[0]=0  pot[col]=pot[col-1]+num[col-1] (1dcol<a.cols) 为了节省存储单元,记录每一列非零元个数的向量 num 可直接放入 pot 中,即上面的式 子可以改为:pot[col]=pot[col-1]+pot[col],其中 1≤col<a.cols。 于是可用上面的公式进行迭代,依次求出其他列的第一个非零元转置后在 b.data 中的位置 pot[col]。例如,对图 5-6 给出的稀疏矩阵 M,有: 每一列的非零元个数为: pot[1]=2 第 0 列非零元个数 pot[2]=2 第 1 列非零元个数 pot[3]=2 第 2 列非零元个数 pot[4]=1 第 3 列非零元个数 pot[5]=0 第 4 列非零元个数 pot[6]=1 第 5 列非零元个数 pot[7]=0 第 6 列非零元个数 每一列的第一个非零元的位置为: pot[0]=0 第 0 列第一个非零元位置 pot[1]=pot[0]+pot[1]=2 第 1 列第一个非零元位置 pot[2]=pot[1]+pot[2]=4 第 2 列第一个非零元位置.

(12) 第 5 章 多维数组和广义表. 85. pot[3]=pot[2]+pot[3]=6 第 3 列第一个非零元位置 pot[4]=pot[3]+pot[4]=7 第 4 列第一个非零元位置 pot[5]=pot[4]+pot[5]=7 第 5 列第一个非零元位置 pot[6]=pot[5]+pot[6]=8 第 6 列第一个非零元位置 则 M 稀疏矩阵的转置矩阵 N 的三元组表很容易写出(如图 5-8 所示),算法描述如下: void sparmatrix::fastrans(sparmatrix a,sparmatrix b) { int pot[100],col,ano,bno; b.rows=a.cols; b.cols=a.rows; b.terms=a.terms; if(b.terms>0) { for(col=0;col<a.cols;col++) pot[col]=0; for(int t=0;t<a.terms;t++) //求出每一列的非零元个数 { col=a.data[t].j; pot[col+1]=pot[col+1]+1; } pot[0]=0; for(col=1;col<a.cols;col++) //求出每一列的第一个非零元在转置后的位置 pot[col]=pot[col-1]+pot[col]; for( ano=0;ano<a.terms;ano++) //转置 { col=a.data[ano].j; bno=pot[col]; b.data[bno].j=a.data[ano].i; b.data[bno].i=a.data[ano].j; b.data[bno].v=a.data[ano].v; pot[col]=pot[col]+1; } } for(int i=0;i<a.terms;i++) cout<<b.data[i].i<<" "<<b.data[i].j<<" "<<b.data[i].v<<endl; //输出转置后的三元组 } void main() { sparmatrix a,b; cin>>a.rows>>a.cols>>a.terms; //输入稀疏矩阵的行、列数及非零元的个数 for( int i=0;i<a.terms;i++) cin>>a.data[i].i>>a.data[i].j>>a.data[i].v; //输入转置前的三元组 for(i=0;i<a.terms;i++) cout<<a.data[i].i<<" "<<a.data[i].j<<" "<<a.data[i].v<<endl; //输出转置前的三元组 a.fastrans( a,b); //调用快速转置算法 }.

(13) 86. 数据结构(C++版)(第二版). 该算法比按列转置多用了辅助向量空间 pot,但它的时间为 4 个单循环,故总的时间复杂 度为 O(a.cols+a.terms),比按列转置算法效率要高。 2.稀疏矩阵的相加运算 当稀疏矩阵用三元组表进行相加时,有可能出现非零元的位置变动,这时候,不宜采用 三元组表作存储结构,而应该采用十字链表(为描述方便,假设稀疏矩阵的行列下标从 1 开始, 而不是从 0 开始)。 (1)十字链表的建立。 下面分两步讨论十字链表的建立算法。 1)建立表头的循环链表。 依次输入矩阵的行、列数和非零元个数:m、n 和 t。由于行、列链表共享一组表头结点, 因此表头结点的个数应该是矩阵中行、列数中较大的一个。假设用 s 表示个数,即 s=max(m,n)。 依次建立总表头结点(由 hm 指针指向)和 s 个行、列表头结点,并使用 next 域使 s+1 个头结 点组成一个循环链表,总表头结点的行、列域分别为稀疏矩阵的行、列数目,s 个表头结点的 行列域分别为 0。并且开始时,每一个行、列链表均是一个空的循环链表,即 s 个行、列表头 结点中的行、列指针域 rptr 和 cptr 均指向头结点本身。 2)生成表中结点。 依次输入 t 个非零元的三元组(i,j,v)生成一个结点,并将它插入到第 i 行链表和第 j 列链表 中的正确位置上,使第 i 个行链表和第 j 个列链表变成一个非空的循环链表。 算法描述如下: class linknode { public: int i,j; union vnext { int v; linknode *next; } k; linknode *rptr,*cptr; linknode *creatlindmat(); }; linknode *linknode::creatlindmat ( ) linknode *creatlindmat ( ) { int m, n, t, s, i, j, k; linknode *p , *q, *cp[100],*hm; //cp[100]中的 100 表示矩阵的行、列数不超过 100 cout<<"请输入稀疏矩阵的行、列数及非零元个数"<<endl; cin>>m>>n>>t; if (m>m) s=m; else s=n; hm=new linknode ; hm->i=m; hm->j=n; cp[0]=hm;.

(14) 第 5 章 多维数组和广义表 for (i=1; i<=s;i++) { p=new linknode; p->i=0; p->j=0; p->rptr=p; p->cptr=p; cp[i]=p; cp[i-1]->k.next=p; } cp[s]->k.next=hm; for(int x=1;x<=t;x++) { cout<<"请输入一个三元组(i,j,v)"<<endl; cin>>i>>j>>k; //输入一个非零元的三元组 p=new linknode; p->i=i; p->j=j; p->k.v=k; //生成一个三元组的结点 //以下是将 p 插入到第 i 行链表中 q=cp[i]; while ((q->rptr!=cp[i]) &&( q->rptr->j<j)) q=q->rptr; p->rptr=q->rptr; q->rptr=p; //以下是将 p 插入到第 j 列链表中 q=cp[j]; while((q->cptr!=cp[j]) &&( q->cptr->i<i)) q=q->cptr; p->cptr=q->cptr; q->cptr=p; } return hm; } void main() { linknode *p,*q ,T; linknode *hm=NULL; hm=T.creatlindmat( ); p=hm->k.next; while(p->k.next!=hm). //生成十字链表 //输出十字链表. { q=p->rptr; while(q->rptr!=p) //输出一行链表 { cout<<q->i<<" "<<q->j<<" "<<q->k.v<<" "; q=q->rptr; } if(p!=q) cout<<q->i<<" "<<q->j<<" "<<q->k.v<<" ";. 87.

(15) 数据结构(C++版)(第二版). 88 cout<<endl; p=p->k.next; } }. 在十字链表的建立算法中,建表头结点的时间复杂度为 O(s),插入 t 个非零元结点到相应 的行、列链表的时间复杂度为 O(t*s),因此算法的总时间复杂度为 O(t*s)。 (2)用十字链表实现稀疏矩阵的相加运算。 假设原来有两个稀疏矩阵 A 和 B,如何实现运算 A=A+B 呢?假设原来 A 和 B 都用十字 链表作存储结构,现要求将 B 中的结点合并到 A 中,合并后的结果有 3 种可能:①aij+bij;② aij(bij=0);③bij(aij=0)。由此可知当将 B 加到 A 中去时,对 A 矩阵的十字链表来说,或者 是改变结点的 v 域值(aij+bij≠0),或者不变(bij=0) ,或者插入一个新结点(aij=0),还可能 是删除一个结点(aij+bij=0) 。 于是整个运算过程可以从矩阵的第一行起逐行进行。 对每一行都从行表头出发分别找到 A 和 B 在该行中的第一个非零元结点后开始比较,然后按上述 4 种不同情况分别处理之。若 pa 和 pb 分别指向 A 和 B 的十字链表中行值相同的两个结点,则 4 种情况描述为: 1)pa->j=pb->j 且 pa->k.v+pb->k.v≠0,则只要将 aij+bij 的值送到 pa 所指结点的值域中, 其他所有域的值都不变化。 2)pa->j=pb->j 且 pa->k.v+pb->k.v=0,则需要在 A 矩阵的链表中删除 pa 所指的结点。这 时,需要改变同一行中前一结点的 rptr 域值以及同一列中前一结点的 cptr 域值。 3)pa->j<pb->j 且 pa->j≠0,则只要将 pa 指针往右推进一步,并重新加以比较。 4)pa->j>pb->j 或 pa->j=0,则需要在 A 矩阵的链表中插入 pb 所指的结点。 另外,为了插入和删除结点时方便,还需要设立一些辅助指针,在 A 的行链表上设 qa 以 指示 pa 所指结点的直接前驱;其二是,在 A 的每一列的列链表上设一个指针 hl[j],它的初值 是指向每一列的列链表的表头结点 cp[j]。 下面将对矩阵 B 加到矩阵 A 上面的操作过程进行大致描述。 设 ha 和 hb 分别为表示矩阵 A 和 B 的十字链表的总表头;ca 和 cb 分别为指向 A 和 B 的 行链表的表头结点,其初始状态为: ca=ha->k.next; cb=hb->k.next; pa 和 pb 分别为指向 A 和 B 的链表中结点的指针。开始时,pa=ca->rptr;,pb=cb->rptr;, 然后按下列步骤执行: ①当 ca->i=0 时,重复执行②、③、④步,否则,算法结束。 ②当 pb->j≠0 时,重复执行③步,否则转第④步。 ③比较两个结点的列序号,分 3 种情形:  若 pa->j<pb->j 且 pa->j≠0,则令 pa 指向本行的下一结点,即 qa=pa;,pa=pa->rptr;, 转②步。  若 pa->j>pb->j 或 pa->j=0,则需要在 A 中插入一个结点。假设新结点的地址为 P,则 A 的行表中的指针变化为 qa->rptr=p;,p->rptr=pa;。 同样,A 的列表中指针也应作相应改变,用 hl[j]指向本列中的上一个结点,则 A 的列表.

(16) 第 5 章 多维数组和广义表. 89. 中的指针变化为 p->cptr=hl[j]->cptr;,hl[j]->cptr=p;,转第②步。  若 pa->j=pb->j,则将 B 的值加上去,即 pa->k.v=pa->k.v+bp->k.v,此时若 pa->k.v≠0, 则指针不变,否则删除 A 中的该结点,于是行表中的指针变为 qa->rptr=pa->rptr;,同 时,为了改变列表中的指针,需要先找同列中的上一个结点,用 hl[j]表示,然后令 hl[j]->cptr=pa->cptr,转第②步。 ④一行中的元素处理完毕后,按着处理下一行,指针变化为 ca=ca->k.next;,cb=cb->k.next;, 转第①步。 稀疏矩阵十字链表相加算法如下: //假设 ha 为 A 稀疏矩阵十字链表的头指针,hb 为 B 稀疏矩阵十字链表的头指针 class linknode { public: int i,j; union vnext { int v; linknode *next; } k; linknode *rptr,*cptr; linknode *creatlindmat(); linknode *matadd(linknode *ha , linknode *hb) }; linknode *linknode::creatlindmat() {… //前面已有 } linknode *linknode::matadd(linknode *ha, linknode *hb) { linknode *pa, *pb, *qa, *ca,*cb,*p,*q; linknode *hl[100]; int i , j, n; if((ha->i!=hb->i)||(ha->j!=hb->j)) cout<<"矩阵不匹配,不能相加"<<endl; else { p=ha->k.next; n=ha->j; for (i=1;i<=n; i++) { hl[i]=p; p=p->k.next; } ca=ha->k.next; cb=hb->k.next; while(ca->i==0) { pa=ca->rptr; pb=cb->rptr; qa=ca; while(pb->j!=0).

(17) 数据结构(C++版)(第二版). 90. { if((pa->j<pb->j)&&(pa->j!=0)) { qa=pa; pa=pa->rptr;} else if ((pa->j>pb->j)||(pa->j==0)) { p=new linknode; p->i=pb->i; p->j=pb->j; p->k.v=pb->k.v; qa->rptr=p; p->rptr=pa; qa=p; pb=pb->rptr; j=p->j; q=hl[j]->cptr; while((q->i<p->i)&&(q->i!=0)) { hl[j]=q; q=hl[j]->cptr;} hl[j]->cptr=p; p->cptr=q; hl[j]=p; } else { pa->k.v=pa->k.v+pb->k.v; if(pa->k.v==0) { qa->rptr=pa->rptr; j=pa->j; q=hl[j]->cptr; while (q->i<pa->i) {hl[j]=q; q=hl[j]->cptr;} hl[j]->cptr=q->cptr; pa=pa->rptr; pb=pb->rptr; delete q; } else { qa=pa; pa=pa->rptr; pb=pb->rptr; } } } ca=ca->k.next; cb=cb->k.next; } } return ha; } void print(linknode *ha) { linknode *p,*q,*r; p=ha->k.next;r=p; while(p->k.next!=r). //输出十字链表. //插入一个结点. //删除一个结点.

(18) 第 5 章 多维数组和广义表. 91. { q=p->rptr; while(q->rptr!=p) { cout<<q->i<<" "<<q->j<<" "<<q->k.v<<" "; q=q->rptr; } if(p!=q) cout<<q->i<<" "<<q->j<<" "<<q->k.v<<" "; cout<<endl; p=p->k.next; } } void main() { linknode T1,T2; linknode *ha=NULL,*hb=NULL,*hc=NULL; ha=T1.creatlindmat( ); //生成一个十字链表 ha hb=T2.creatlindmat( ); //生成另一个十字链表 hb print(ha);cout<<endl; //输出十字链表 ha print(hb);cout<<endl; //输出十字链表 hb hc=T1.matadd(ha,hb); //十字链表相加 print(hc);cout<<endl; //输出相加后的结果 }. 通过算法分析可知,进行比较、修改指针所需的时间是一个常数,整个运算过程在于对 A 和 B 的十字链表逐行扫描,其循环次数主要取决于 A 和 B 矩阵中的非零元个数 na 和 nb,故 算法的时间复杂度为 O(na+nb)。. 5.5. 广义表. 5.5.1 基本概念 广义表是第 2 章提到的线性表的推广。线性表中的元素仅限于原子项,即不可以再分, 而广义表中的元素既可以是原子项,也可以是子表(另一个线性表)。 1.广义表的定义 广义表是 n(n≥0)个元素 a1,a2,…,an 的有限序列,其中每一个 ai 或者是原子,或者 是一个子表。广义表通常记为 LS=(a1,a2,…,an),其中 LS 为广义表的名字,n 为广义表的长度, 每一个 ai 为广义表的元素。但在习惯中,一般用大写字母表示广义表,小写字母表示原子。 下面将给出广义表的一些例子及表示方法。 2.广义表举例 (1)F=( ),F 为空表,长度为 0。 (2)G=(a,(b,c)),G 是长度为 2 的广义表,第一项为原子,第二项为子表。 (3)H=(x,y,z),H 是长度为 3 的广义表,每一项都是原子。.

(19) 数据结构(C++版)(第二版). 92. (4)D=(B,C),D 是长度为 2 的广义表,每一项都是上面提到的子表。 (5)E=(a,E),E 是长度为 2 的广义表,第一项为原子,第二项为它本身。 3.广义表的表示方法 (1)用 LS=(a1,a2,…,an)形式,其中每一个 ai 为原子或广义表。 例如 A=(b,c)、B=(a,A)、C=(A,B)都是广义表。 (2)将广义表中的所有子表写到原子形式,并利用圆括号嵌套。 例如,上面提到的广义表 A、B、C 可以描述为: A(b,c) B(a,A(b,c)) C(A(b,c),B(a,A(b,c))) (3)将广义表用树和图来描述。 上面提到的广义表 A、B、C 的描述如图 5-11 所示。 A. b. C. B A. a. c. c. b b (a)A=(b,c). B. A. (b)B=(a,A) 图 5-11. a. c (c)C=(A,B). 广义表用树或图来表示. 4.广义表的深度 一个广义表的深度是指该广义表展开后所含括号的层数。 例如,A=(b,c)的深度为 1,B=(A,d)的深度为 2,C=(f,B,h)的深度为 3。 5.广义表的分类 (1)线性表:元素全部是原子的广义表。 (2)纯表:与树对应的广义表,如图 5-11 中的(a)和(b)所示。 (3)再入表:与图对应的广义表(允许结点共享),如图 5-11 中的(c)所示。 (4)递归表:允许有递归关系的广义表,例如 E=(a,E)。 这 4 种表的关系满足:递归表再入表纯表线性表。 5.5.2 存储结构 由于广义表的元素类型不一定相同,因此,难以用顺序结构存储表中的元素,通常采用 链接存储方法来存储广义表中的元素,并称之为广义链表。 1.单链表表示法 即模仿线性表的单链表结构,每个原子结点只有一个链域 link,结点结构如下: atom. data/slink. link. 其中 atom 是标志域,若为 0,则表示为子表;若为 1,则表示为原子,data/slink 域用来.

(20) 第 5 章 多维数组和广义表. 93. 存放原子值或子表的指针,link 存放下一个元素的地址。 数据类型描述如下: class node { public: int atom; node *link; union { node *slink; elemtype data; }ds; }. 例如,设 L=(a,b),A=(x,L)=(x,(a,b)),B=(A,y)=((x,(a,b)),y),C=(A,B)=((x,(a,b)),((x,(a,b)),y)), 可用如图 5-12 所示的结构描述广义表 C,设头指针为 hc。 hc. A. B 0. 0. ^ A. 0. 1 y ^ L. 1 x. 0. ^. 1 a 图 5-12. 1 b ^. 广义表的单链表表示法. 用此方法存储有两个缺点: (1)在某一个表(或子表)的开始处插入或删除一个结点,修改的指针较多,耗费大量 时间。 (2)删除一个子表后,它的空间不能很好地回收。 2.双链表表示法 每个结点含有两个指针及一个数据域,每个结点的结构如下: link1. data. link2. 其中,link1 指向该结点子表,link2 指向该结点的后继。 数据类型描述如下: class node { public: elemtype data; node *link1,*link2; }.

(21) 数据结构(C++版)(第二版). 94. 例如,对图 5-12 所示用单链表表示的广义表 C 可以用如图 5-13 所示的双链表方法表示。 hc. A. B ^ A. ^ y ^. x. L ^ ^ a. 图 5-13. ^ b ^. 广义表的双链表表示法. 广义表的双链表表示法较单链表表示法方便。 5.5.3 基本运算 广义表有许多运算,现仅介绍常见的几种。 1.求广义表的深度 depth(LS) 假设广义表以刚才的单链表表示法作存储结构,则它的深度可以递归求出,即广义表的 深度等于它的所有子表的最大深度加 1。设 dep 表示任一子表的深度,max 表示所有子表中表 的最大深度,则广义表的深度为:depth=max+1,算法描述如下: int depth(node *LS) { int max=0; while(LS!=NULL) { if(LS->atom==0) //有子表 { int dep=depth(LS->slink); if(dep>max) max=dep; } LS=LS->link; } return max+1; }. 该算法的时间复杂度为 O(n)。 2.广义表的建立 creat(LS) 假设广义表以单链表的形式存储,广义表的元素类型 elemtype 为字符型 char,广义表由 键盘输入,假定全部为字母,输入格式为:元素之间用逗号分隔,表元素的起止符号分别为左、 右圆括号,空表在其圆括号内使用一个“#”字符表示,最后使用一个分号作为整个广义表的 结束。 例如,给定一个广义表 LS=(a,( ),b,c (d,(e))),则从键盘输入的数据为:(a,(#),b,c(d,(e)));↙, 其中↙表示回车换行。具体算法描述如下: void creat(node *LS).

(22) 第 5 章 多维数组和广义表 { char ch; cin>>ch; if(ch=='#') LS=NULL; else if(ch=='(') { LS=new node; LS->atom=0; creat(LS->slink); } else { LS=new node; LS->atom=1; LS->data=ch; } cin>>ch; if(LS==NULL); else if(ch==',') creat(LS->link); else if((ch==')')||(ch==';')) LS->link=NULL; }. 该算法的时间复杂度为 O(n)。 3.输出广义表 print(LS) void print(node *LS) { if(LS->atom==0) { cout<<'('; if(LS->slink==NULL) cout<<'#'; else print(LS->slink); } else cout<<LS->data; if(LS->atom==0) cout<<')'; if(LS->link!=NULL) { cout<<','; print(LS->link); } }. 95.

(23) 96. 数据结构(C++版)(第二版). 该算法的时间复杂度为 O(n)。 4.取表头运算 head 若广义表 LS=(a1,a2,…,an),则 head(LS)=a1。 取表头运算得到的结果可以是原子,也可以是一个子表。 例如,head((a1,a2,a3,a4))=a1,head(((a1,a2),(a3,a4),a5))=(a1,a2)。 5.取表尾运算 tail 若广义表 LS=(a1,a2,…,an),则 tail(LS)=(a2,a3,…,an)。 即取表尾运算得到的结果是除表头以外的所有元素,取表尾运算得到的结果一定是一个 子表。例如 tail((a1,a2,a3,a4))=(a2,a3,a4),tail(((a1,a2),(a3,a4),a5))=((a3,a4),a5)。 值得注意的是广义表( )和(())是不同的,前者为空表,长度为 0,后者的长度为 1,可得到 表头、表尾均为空表,即 head((( )))=( ),tail((( )))=( )。. (1)多维数组在计算机中有两种存放形式:行优先和列优先。 (2)行优先规则是左边下标变化最慢,右边下标变化最快,右边下标变化一遍,与之相 邻的左边下标才变化一次。 (3)列优先规则是右边下标变化最慢,左边下标变化最快,左边下标变化一遍,与之相 邻的右边下标才变化一次。 (4)对称矩阵关于主对角线对称。为了节省存储单元,可以进行压缩存储,对角线以上 n(n  1) 的元素和对角线以下的元素可以共用存储单元,故 nn 的对称矩阵只需要 个存储单元。 2 (5)三角矩阵有上三角矩阵和下三角矩阵之分,为了节省内存单元,可以采用压缩存储, n(n  1) nn 的三角矩阵进行压缩存储时,只需要 +1 个存储单元。 2 (6)稀疏矩阵的非零元排列无任何规律,为了节省内存单元,进行压缩存储时可以采用 三元组表示方法,即存储非零元的行号、列号和值。若干个非零元有若干个三元组,若干个三 元组称为三元组表。 (7)广义表为线性表的推广,里面的元素可以为原子,也可以为子表,故广义表的存储 采用动态链表较方便。. 5-1 按行优先存储方式,写出三维数组 A[3][2][4]在内存中的排列顺序及地址计算公式 (假设每个数组元素占用 L 个字节的内存单元,a[0][0][0]的内存地址为 Loc(a[0][0][0]))。 5-2 按列优先存储方式,写出三维数组 A[3][2][4]在内存中的排列顺序及地址计算公式 (假设每个数组元素占用 L 个字节的内存单元,a[0][0][0]的内存地址为 Loc(a[0][0][0]))。 5-3 设有上三角矩阵 Ann,它的下三角部分全为 0,将其上三角元素按行优先存储方式 存入数组 B[m]中(m 足够大),使得 B[k]=a[i][j],且有 k=f1(i)+f2(j)+c。试推出函数 f1、f2 及.

(24) 第 5 章 多维数组和广义表. 97. 常数 c(要求 f1 和 f2 中不含常数项)。 5-4 若矩阵 Amn 中的某个元素 A[i][j]是第 i 行中的最小值,同时又是第 j 列中的最大值, 则称此元素为该矩阵中的一个马鞍点。假设以二维数组存储矩阵 Amn,试编写求出矩阵中所 有马鞍点的算法,并分析你的算法在最坏情况下的时间复杂度。 5-5 试写一个算法,查找十字链表中某一非零元 x。 5-6 给定矩阵 A 如下: 1 0 0 0 0  0 0 2 3 0    A = 0 4 0 0 5    0 0 0 0 0  0 0 0 0 6  写出它的三元组表和十字链表。 5-7 对上题中的矩阵,画出它的带行指针的链表,并给出算法来建立它。 5-8 试编写一个以三元组形式输出用十字链表表示的稀疏矩阵中非零元及其下标的算法。 5-9 给定一个稀疏矩阵如下: 11 0 0 0 0 -9 0   0 23 0 0 7 0 0    0 0 5 8 0 0 2   0 0 0 0 0 0 0  1 6 0 33 88 0 0    0 0 4 0 0 0 0  0 0 0 0 0 0 99   65 0 78 0 0 86 0 . 用快速转置实现该稀疏矩阵的转置,写出转置前后的三元组表及开始的每一列第一个非 零元的位置 pot[col]的值。 5-10 广义表是线性结构还是非线性结构?为什么? 5-11 求下列广义表运算的结果: (1)head((p,h,w)) (2)tail ((b,k,p,h)) (3)head(((a,b),(c,d))) (4)tail (((b),(c,d))) (5)head (tail(((a,b),(c,d)))) (6)tail (head (((a,b),(c,d)))) (7)head (tail (head(( (a,d),(c,d))))) (8)tail (head (tail (((a,b),(c,d))))) 5-12 画出下列广义表的图形表示: (1)A(b,(A,a,C(A)),C(A)) (2)D(A( ),B(e),C(a,L(b,c,d))).

(25) 数据结构(C++版)(第二版). 98. 5-13 5-14. 画出题 5-12 中的广义表的单链表表示法和双链表表示法。 假设一个准对角矩阵,如下:  a11 a12 a  21 a 22           . a 33. a 34. a 43. a 44 ... a ij ... a 2m 1,2m 1 a 2m,2m 1.            a 2m 1,2m   a 2m,2m . 按 a11,a12,a21,a22,a33,a34,a43,a44,…,a2m-1,2m-1,a2m-1,2m,a2m,2m-1,a2m,2m 的顺序存放到一维数组 B[4m] 中,试写出二维数组下标(i,j)与一维数组下标 k 的对应公式。.

(26)

參考文獻

相關文件

按行业及在职员工数目抽选。对于在职员工为 20 人或以上的店铺,以及场所总 数较少的分层会进行全面统计。. 统计结果推算

统计范围包括参考年在本澳注册的225间旅行社。是次调查把旅行社的主场所、分社及服务柜台合并为一个单位计算;为方便

按缴纳物业转移印花税及建成年份统计的住宅单位每平方米平均成交价(不包括中间移转及尚待重估价值的楼

企業 指單一的法律實體 具有組織主權及擁有運用資源的決策權 在一個或多個地點從事經濟活動。當 同機構單位存在於 同一地點 但只要每一個機構單位

注1:资讯科技应用调查的相关定义是参考国际电信联盟(International Telecommunication Union)的“Core list of ICT indicators

(non-earned

衡量一个地区或一个国家水资源的丰歉 程度的指标:

微积分的创立是数学发展中的里程碑, 它的发展 和广泛应用开启了向近代数学过渡的新时期, 为研究 变量和函数提供了重要的方法和手段. 运动物体的瞬