第6天数组和广义表
数组的类型定义、数组的存储表示、特殊矩阵的压缩存储表示方法、随机稀疏矩阵的压缩存储表示方法
5.1 数组的类型定义
5.2 数组的顺序表示和实现
5.3 稀疏矩阵的压缩存储
5.4 广义表的类型定义
5.5 广义表的表示方法
5.7广义表的递归算法
5.1 数组的类型定义
数组是所有高级编程语言中都已实现的固有数据类型,因此凡学习过高级程序设计语言的读者对数组都不陌生。但它和其它诸如整数、实数等原子类型不同,它是一种结构类型。
换句话说,"数组"是一种数据结构。
例如:一个C语言风格的 n 维数组的定义,数组中共有b1 ×b2× ... ×bn 个元素,每个元素都处在 n 个关系中,
但因为每个关系本身都是"线性关系",所以数组也是线性结构。
5.2 数组的顺序表示和实现
类型特点: 1) 只有引用型操作,没有加工型操作;
2) 数组是多维的结构,而存储空间是一个一维的结构。
有两种顺序映象的方式:
1)以行序为主序;
2)以列序为主序。
推广到一般情况,可得到 n 维数组数据元素存储位置的映象关系:
5.3 稀疏矩阵的压缩存储
何谓稀疏矩阵?
假设 m 行 n 列的矩阵含 t 个非零元素,则称 为稀疏因子。 通常认为的矩阵为稀疏矩阵。
以常规方法,即以二维数组表示 高阶的稀疏矩阵时产生的问题:
1) 零值元素占了很大空间;
2) 计算中进行了很多和零值的运算,遇除法,还需判别除数是否为零。
解决问题的原则:
1) 尽可能少存或不存零值元素;
2) 尽可能减少没有实际意义的运算;
3) 操作方便。 即:能尽可能快地找到与 下标值(i,j)对应的元素, 能尽可能快地找到同一行或同一列的非零值元。
有两类稀疏矩阵:
1) 特殊矩阵
非零元在矩阵中的分布有一定规则
例如: 三角矩阵和对角矩阵
2) 随机稀疏矩阵
非零元在矩阵中随机出现
三角矩阵如何压缩存储?
对于一个n阶三角矩阵满足,(1≤i,j≤n),其中下三角的元素个数为n(n+1)/2,可以用一维数组sa[n(n+1)/2]来存放 ,则sa[k]和矩阵元素之间存在着一一对应的关系:
随机稀疏矩阵的压缩存储方法:
一、三元组顺序表
二、行逻辑联接的顺序表
三、 十字链表
一、三元组顺序表
1 #define MAXSIZE 12500 2 typedef struct {
int i, j; //该非零元的行下标和列下标 3 ElemType e; // 该非零元的值 4 } Triple; // 三元组类型 5 6 typedef union { 7 Triple data[MAXSIZE + 1]; 8 int mu, nu, tu; 9 } TSMatrix; // 稀疏矩阵类型
如何求转置矩阵?
用常规的二维数组表示时的算法
1 for (col=1; col<=nu; ++col)
2 for (row=1; row<=mu; ++row)
3 T[col][row] = M[row][col];
4 其时间复杂度为: O(mu×nu)
即完成矩阵的转置,要:
1)交换矩阵的行、列值 mu nu
2)每个三元组中的i、j互换
3)重排三元组的次序(新行序为旧列序)
由于A的列是B的行,因此,按a.data的列序转置,所得到的转置矩阵B的三元组表b.data必定是按行优先存放的。
1 //用“三元组”存储时的算法
2 Status TransposeSMatrix(TSMatrix M, TSMatrix &T)
3 {T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
4 if (T.tu) { q=1;
5 for (col=1; col<=M.nu; ++col)
6 for (p=1; p<=M.tu; ++p)
7 if (M.data[p].j==col)
8 {T.data[q].i=M.data[p].j;
9 T.data[q].j=M.data[p].i;
10 T.data[q].e=M.data[p].e;
11 ++q;
12 } // if
13 return OK;
14 } // TransposeSMatrix 时间复杂度为O(nu*tu)
首先应该确定每一行的第一个非零 元在三元组中的位置。
改进以后的“三元组”存储实现思想:
首先应该确定每一行的第一个非零元在三元组中的位置。
1 Num[col]表示矩阵M中第col列中非零元的个数
2 Cpot[col]表示矩阵M中第col列的第一个非零元在b.data中的位置
3 cpot[1] = 1;
4 for (col=2; col<=M.nu; ++col)
5 cpot[col] = cpot[col-1] + num[col-1];
1 Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T)
2 {T.mu = M.nu; T.nu = M.mu; T.tu = M.tu;
3 if (T.tu) {
4 for (col=1; col<=M.nu; ++col) num[col] = 0;
5 for (t=1; t<=M.tu; ++t) ++num[M.data[t].j];
6 cpot[1] = 1;
7 for (col=2; col<=M.nu; ++col)
8 cpot[col] = cpot[col-1] + num[col-1];
9 for (p=1; p<=M.tu; ++p) { }
10 } // if
11 return OK;
12 } // FastT
分析算法FastTransposeSMatrix的时间复杂度:
1 for (col=1; col<=M.nu; ++col) … …
2 for (t=1; t<=M.tu; ++t) … …
3 for (col=2; col<=M.nu; ++col) … … f
4 or (p=1; p<=M.tu; ++p) … …
5 时间复杂度为: O(M.nu+M.tu)
二、行逻辑联接的顺序表
三元组顺序表又称有序的双下标法,它的特点是,非零元在表中按行序有序存储,因此便于进行依行顺序处理的矩阵运算。
然而,若需随机存取某一行中的非零元,则需从头开始进行查找。
修改前述的稀疏矩阵的结构定义,增加一个数据成员rpos,其值在稀疏矩阵的初始化函数中确定。
1 #define MAXMN 500
2 typedef struct {
3 Triple data[MAXSIZE + 1];
4 int rpos[MAXMN + 1];
5 int mu, nu, tu;
6 } RLSMatrix; // 行逻辑链接顺序表类型
矩阵乘法的精典算法:
1 for (i=1; i<=m1; ++i)
2 for (j=1; j<=n2; ++j) {
3 Q[i][j] = 0;
4 for (k=1; k<=n1; ++k)
5 Q[i][j] += M[i][k] * N[k][j];
6 }
其时间复杂度为: O(m1×n2×n1)
两个稀疏矩阵相乘(Q=M×N)的过程可大致描述如下:
Q初始化;
if Q是非零矩阵 { // 逐行求积
for (arow=1; arow<=M.mu; ++arow)
{ // 处理M的每一行ctemp[] = 0; // 累加器清零计算Q中第arow行的积并存入ctemp[] 中;将ctemp[] 中非零元压缩存储到Q.data;} // for arow
} // if
矩阵相乘的算法:Status MultSMatrix (RLSMatrix M, RLSMatrix N, RLSMatrix &Q) {
1 if (M.nu != N.mu) return ERROR;
2 Q.mu = M.mu;
3 Q.nu = N.nu;
4 Q.tu = 0;
5 if (M.tu*N.tu != 0) {
6 // Q是非零矩阵
7 for (arow=1; arow<=M.mu; ++arow) {
8 ctemp[] = 0; // 当前行各元素累加器清零
9 Q.rpos[arow] = Q.tu+1;
10 for (p=M.rpos[arow]; p<M.rpos[arow+1];++p) {
11 //对当前行中每一个非零元
12 brow=M.data[p].j;
13 if (brow < N.nu )
14 t = N.rpos[brow+1];
15 else{t = N.tu+1 }
16 for (q=N.rpos[brow]; q< t; ++q) {
17 ccol = N.data[q].j; // 乘积元素在Q中列号
18 ctemp[ccol] += M.data[p].e * N.data[q].e;
19 } // for q
20 } // for arow
21 } // if
22 return OK;
23 } // MultSMatrix
三、 十字链表
1 Typedef struct OLode{
2 int i , j;
3 Elemtype e;
4 Struct OLnode *right,*down;
5 } OLnode , *Olink;
6
7 Typedef struct {
8 OLink *rhead,*chead;
9 int mu ,nu,tu;
10 } CrossList
11
5.4 广义表的类型定义
ADT Glist {
数据对象:D={ei | i=1,2,..,n; n≥0; ei∈AtomSet 或 ei∈GList,AtomSet为某个数据对象 }
数据关系: LR={<ei-1, ei >| ei-1 ,ei∈D, 2≤i≤n}
} ADT Glist
广义表是递归定义的线性结构,
例如:
A = ( )
F = (d, (e))
D = ((a,(b,c)), F)
C = (A, D, F)
B = (a, B) = (a, (a, (a, , ) ) )
广义表 LS = ( a1, a2, …, an )的结构特点:
1) 广义表中的数据元素有相对次序;
2) 广义表的长度定义为最外层包含元素个数;
3) 广义表的深度定义为所含括弧的重数;
注意:“原子”的深度为 0
“空表”的深度为 1
4) 广义表可以共享;
5) 广义表可以是一个递归的表。
递归表的深度是无穷值,长度是有限值。
6) 任何一个非空广义表 LS = ( a1, a2, …, an) 均可分解为
表头 Head(LS) = a1 和 表尾 Tail(LS) = ( a2, …, an) 两部分。
例如: D = ( E, F ) = ((a, (b, c)),F )
Head( D ) = E Tail( D ) = ( F )
Head( E ) = a Tail( E ) = ( ( b, c) )
Head( (( b, c)) ) = ( b, c) Tail( (( b, c)) ) = ( )
Head( ( b, c) ) = b Tail( ( b, c) ) = ( c )
5.5 广义表的表示方法
通常采用头、尾指针的链表结构
表结点:
原子结点:
构造存储结构的两种分析方法:
1) 表头、表尾
分析法:
空表 ls=NIL
非空表 ls
广义表的头尾链表存储表示:
1 typedef enum {ATOM, LIST} ElemTag; // ATOM==0:原子, LIST==1:子表
2 typedef struct GLNode {
3 ElemTag tag; // 标志域
4 union{ AtomType atom; // 原子结点的数据域
5 struct {struct GLNode *hp, *tp;} ptr;
6 };
7 } *GList
广义表 头尾链表存储表示特点:
1)空表的表头指针为空,否则表头指针指向 一个表结点;且该结点中的hp域指示列表表头(或为原子结点,或为表结点),tp域指向列表表尾。(当表尾为空时,指针 为空,否则必为表结点)
2) 容易分清列表中原子和子表所在的层次;
3) 最高层的表结点个数即为列表的长度。
5.7 广义表的递归算法
递归的设计思想为:
假如某个问题的求解过程可以分成若干步进行,并且当前这一步的解可以直接求得,
则先求出当前这一步的解,对于余下的问题,若问题的性质和原问题类似,则又可递归求解。
递归的终结状态是,当前的问题可以直接求解,对原问题而言,则是已走到了求解的最后一步。
链表是可以如此求解的一个典型例子。
例如:编写“删除单链表中所有值为x 的数据元素”的算法。
分析:1) 单链表是一种顺序结构,必须从第一个结点起,逐个检查每个结点的数据元素;
2) 从另一角度看,链表又是一个递归结构,若 L 是线性链表 (a1, a2,...., an) 的头指针,则 L->next是线性链表 (a2, ...., an)的头指针。
1 void delete(LinkList &L, ElemType x)
2 // 删除以L为头指针的带头结点的单链表中
3 // 所有值为x的数据元素
4 { if (L->next) {
5 if (L->next->data==x) {
6 p=L->next;
7 L->next=p->next;
8 free(p);
9 delete(L, x);
10 }
11 else delete(L->next, x);
12 }
13 } // delete
14
求广义表的深度:
将广义表分解成 n 个子表,分别(递归)求得每个子表的深度,
广义表的深度=Max {子表的深度} +1
可以直接求解的两种简单情况为: 空表的深度 = 1 原子的深度 = 0
1 int GlistDepth(Glist L) {
2 // 返回指针L所指的广义表的深度
3 for (max=0, pp=L; pp; pp=pp->ptr.tp){
4 dep = GlistDepth(pp->ptr.hp);
5 if (dep > max)
6 max = dep;
7 }
8 return max + 1;
9 } // GlistDepth
10
厚积薄发,行胜于言@飞鸟各投林