飞鸟各投林

导航

第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  

 

 

 
 
 
 
 
 
 
 
 
 
 
 

posted on 2016-04-11 16:40  飞鸟各投林  阅读(168)  评论(0编辑  收藏  举报