Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

第五章(数组)目录


数组(Array)

定义

  • 数组:是由类型相同的数据元素构成的有限集合,每个数据元素称为一个数组元素(简称元素),每个元素受n(n≥1)个线性关系的约束,每个元素在n个线性关系中的序号j1,j2,…,jn,称为该元素的下标,并称该数组为n维数组。

    • j的下标n与线性表重元素的序号不同,该n表示每个元素受n个线性关系的约束。而j表示数组中元素的下标。
    • 例如,对于一个m行n列的二维数组Am×n可以看成是长度为m的线性表(每一行为线性表中的一个元素),它本身又是一个长度为n的线性表(每一列为线性表中的一个元素,它本身又是一个长度为m的线性表)。
    - 对于上述数组中的某元素Aij(0≤i≤m-1, 0≤j≤n-1)则受m和n两个线性关系的约束。 - 数组是线性表的推广,线性表中的元素是原子元素(非结构的原子类型),而数组(非结构的扩展类型)中的元素本身可以是一个种数据结构。
  • 数组的操作

    • 数组的操作只能是存取和修改,而线性表除此之外还可以做插入与删除等操作。
    • 存取(读写):给定一组下标,存入或去除相应的数组元素。
    • 修改(写):给定一组下标,修改相应的数组元素。
    • 存取和修改在本质上都是一种操作:寻址,即根据一组下标定位相应的元素。
  • 注意事项:

    • 元素推广性:元素本身可以具有某种结构,而不限定是单个的数据元素。
    • 元素同一性:元素具有相同的数据类型。
    • 关系确定性:每个元素均受n(n≥1)个线性关系的约束,元素个数和元素之间的关系一般不发生变动。

数组与线性表的区别(简答题)

  • 数组是线性表的推广
    • 线性表:元素的线性排列(有序),其元素为原子类型。
    • 数组:将线性表中数据元素的类型扩充为线性表。
    • 对于一个m行n列的二维数组Am×n可以看成(1)长度为m的线性表 (2)长度为n的线性表,如上图。
    • 即此时的线性表中的元素本身也是一个线性表。
  • 数组的操作只能是存取和修改,而线性表除此之外还可以做插入与删除等操作。

数组的ADT

ADT Array{
    Data: 
        数据元素具有相同类型。
        每个元素均受n(n≥1)个线性关系的约束并由一组下标唯一标识。
    Operation:
        InitArray(&A,dim,bound1,…,boundn)
           操作结果:若维数dim和各维长度合法,则构造相应的数组A。
        DestroyArray(&A)
           初始条件:A是n维数组。
           操作结果:撤销数组A。
        Value(A,&e,index1,…,indexn)
            初始条件:A是n维数组,e为元素变量,随后是n个下标值。
            操作结果:若各下标不超界,则e赋值为所指定的A的元素值。
         Assign(A,e,index1,…,indexn)
            初始条件:A是n维数组,e为元素变量,随后是n个下标值。
            操作结果:若下标不超界,则将e的值赋给所指定的A的元素。
         Locate(A,va_list ap,int &off)
            初始条件:A是n维数组,ap指示一组下标。                       
            操作结果:若ap指示的各下标值合法,则求出该元素在A中的相对地址off
}ADT Array

数组的存储结构

  • 数组通常没有插入和删除操作(一经建立,关系固定)
  • 元素在数组中的位置通常称为数组下标
  • 通过下标,可以找到

一维数组的存储

  • 一维数组就顺序地存放在一段连续的存储单元中
  • 如上图,设数组a[n]的每个元素占有L个存储单元,其第一个元素的存储首址表示为Loc(a0),则数组a中第i个元素(0≤i≤n-1)的存储首址为: Loc(ai)=loc(a0)+i×L

二维数组的存储

  • 二维数A(m×n)组可以看成是以m个单位元素构成一维数组(每个单位有n个元素),也可以看成是以n个元素为单位构成的一维数组(每个单位有m个元素)
  • 用一组连续的存储单元存放二维数组的数据元素,一般有两种存储方式:
    • 以行序为主序的存储方式(先存储第0行,然后存储第1行...)
      • a00, a01 , ... , a0,n-1 , a10 , a11 , ... , a1,n-1 , ..., am-1,0 , am-1,1 , ... , am-1,n-1
      • 公式:Loc(aij)=Loc(a00)+(i×n+j)×L
      • 行乘以列加列
    • 以列序为主序的存储方式(先存储第0列,然后存储第1列...)
      • a00, a10 , ... , am-1,0 , a0,1 , a11 , ... , am-1,1 , ..., a0,n-1 , a1,n-1 , ... , am-1,n-1
      • 公式:Loc(aij)=Loc(a00)+(j×m+i)×L
      • 列乘以行加行

n维数组的存储(见课本)

  • 静态存储
    • 相关操作的实现较为简单
  • 动态存储
    • 较灵活,但相关操作的实现较复杂。
#define MAX_ARRAY_DIM 8 // 假设数组维数的最大值为8
typedef struct {
   ElemType *base; // 数组元素基址,由InitArray分配
   int dim; // 数组维数
   int *b; // 数组维界基址,由InitArray分配
   int *c; // 数组映象函数常量基址,由InitArray分配
 }Array;

初始化操作

若维数dim和各维长度合法,则构造相应的数组A,并返回true

bool InitArray(Array &A,int dim,...)
 { 
   int elemtotal=1,i;      // elemtotal是数组元素总数,初值为1(累乘器)
   va_list ap;          // 变长参数表类型,在stdarg.h中,是存放变长参数表信息的数组
   if(dim<1||dim>MAX_ARRAY_DIM)      // 数组维数超出范围
     return false;
   A.dim=dim;                  // 数组维数
   A.b=(int*)malloc(dim*sizeof(int)); // 动态分配数组维界基址
   if(!A.b)     exit(1);                // 存储分配失败
   va_start(ap,dim);            // 变长参数“...”从形参dim之后开始
   for(i=0;i<dim;++i)
   { A.b[i]=va_arg(ap,int);       // 逐一将变长参数赋给A.b[i]
     if(A.b[i]<0)
       return false;            // 长度不合法
	 // 若各维长度合法,则存入数组A.b中,并求出A的元素总数
     elemtotal*=A.b[i]; 
   }	
   va_end(ap);            // 结束提取变长参数
   A.base=(ElemType*)malloc(elemtotal*sizeof(ElemType));          // 动态分配数组存储空间
   if(!A.base)     exit(1);     // 存储分配失败
   // 求映象函数的常数Ci,并存入数组A.c[i-1],i=1,…,dim
   A.c=(int*)malloc(dim*sizeof(int)); // 动态分配数组偏移量基址
   if(!A.c)     exit(1);  // 存储分配失败
   A.c[dim-1]=1; // 最后一维的偏移量为1(L=1,指针的增减以元素的大小为单位)
   for(i=dim-2;i>=0;--i)
     A.c[i]=A.b[i+1]*A.c[i+1];   // 每一维的偏移量
    return true;
 }

元素定位操作

若ap指示的各下标值合法,则求出该元素在A中的相对地址off

 bool Locate(Array A,va_list ap,int &off) 
 { 
   int i,ind;
   off=0;
   for(i=0;i<A.dim;i++)
   { ind=va_arg(ap,int); // 逐一读取各维的下标值
     if(ind<0||ind>=A.b[i]) // 各维的下标值不合法
       return false;
     off+=A.c[i]*ind; // 相对地址=各维的下标值*本维的偏移量之和
   }
   return true;
 }

取元素操作

“...”依次为各维的下标值,若各下标合法,则e被赋值为A的相应的元素值

 bool Value(ElemType &e,Array A,...) // 在VC++中,“...”之前的形参不能是引用类型
 { 
   va_list ap; // 变长参数表类型,在stdarg.h中
   int off;
   va_start(ap,A); // 变长参数“...”从形参A之后开始
   if(!Locate(A,ap,off)) // 调用Locate(),求得变长参数所指单元的相对地址off
     return false;
   e=*(A.base+off); // 将变长参数所指单元的值赋给e
   return true;
 }

存元素操作

“...”依次为各维的下标值,若各下标合法,则将e的值赋给A的指定的元素。

 bool Assign(Array A,ElemType e,...) 
 {  
   va_list ap; // 变长参数表类型,在stdarg.h中
   int off;
   va_start(ap,e); // 变长参数“...”从形参e之后开始
   if(!Locate(A,ap,off)) // 调用Locate(),求得变长参数所指单元的相对地址off
     return false;
   *(A.base+off)=e; // 将e的值赋给变长参数所指单元
   return true;
 }

撤销操作

销毁数组A。

 void DestroyArray(Array &A)
 { 
   if(A.base) // A.base指向存储单元
     free(A.base); // 释放A.base所指向的存储单元
   if(A.b)
     free(A.b);
   if(A.c)
     free(A.c);
     A.base=NULL;    // 使它们不再指向任何存储单元
	 A.b=A.c=NULL; 
   A.dim=0;
 }

矩阵的压缩存储

  • 压缩存储一般是针对矩阵中包含了大量值相同的元素或零元素的矩阵,其基本思想是:
    • 同值压1:为多个值相同的元素只分配一个存储空间;
    • 零值不分:对零元素不分配存储空间。
  • 矩阵的分类
    • 特殊矩阵:矩阵中有许多值相同的元素或零元素且它们的分布有一定规律。
    • 稀疏矩阵:矩阵中有许多零元素并且零元素的分布没有规律。

特殊矩阵

  • 常见的特殊矩阵有三种:对称矩阵三角矩阵对角矩阵
  • 压缩他们的方法就是找到其规律,根据分布规律压缩到一维数组中,并找到每个非零元素在一维数组中的对应关系。

对称矩阵

  • 各元素非零,各元素可能不同,沿主对角线对称分布。
  • 假设一个n×n的矩阵A,其中任意一元素Aij对应的一维压缩数组Va[n(n+1)/2]的任意一个元素Vk(0 <= k < n(n+1)/2)下标k的定位为:
    • k = i(i+1)/2 + j (当i>=j) 此时Aij位于下三角或对角线
    • k = j(j+1)/2 + i (当i<j) 此时Aij位于上三角

三角矩阵

  • 有上三角矩阵,下三角矩阵之分
  • 上三角矩阵,矩阵的上三角(不包括对角线的全部三角)内所有元素均为一常数c。
  • 下三角矩阵,矩阵的上三角(不包括对角线的全部三角)内所有元素均为一常数c。
  • 假设一个n×n的矩阵A,其中任意一元素Aij对应的一维压缩数组Va[n(n+1)/2+1]的任意一个元素Vk(0 <= k < n(n+1)/2+1)下标k的定位为:
    • k = i(i+1)/2 + j (当i>=j) 此时Aij位于三角以外
    • k = n(n+1)/2 (当i<j) 此时Aij位于三角以内

对角矩阵

  • 所有非零元素均分布在以主对角线为中心的带状区域内。
  • 带宽带状区域(包括对角线)的条数s。
  • 半带宽带状区域的上(或下)一半(不包括)的条数b。
  • 假设一个n×n的矩阵A,其中任意一元素Aij对应的一维压缩数组Va[2nb+n-b2-b]的任意一个元素Vk(0 <= k < 2nb+n-b2-b)的下标k的定位为:
    • k = j (当i=0&&j<b+1时,此时Aij位于第一行带状区域内)
    • k = b+1+(2b+1)*i+(j-n+b) (当0<i<n&&|i-j|<=1,此时Aij位于除第一行外的带状区域内)

以上三种特殊矩阵均以行为主的顺序进行存储。(行存储)

特殊矩阵总结

对特殊矩阵如对称矩阵、三角矩阵、对角矩阵等的压缩存储方法是:找出这些特殊矩阵中特殊元素的分布规律,把那些有一定分布规律的、值相同的元素(包括零)压缩存储到一个存储空间中。这样的压缩存储只需在算法中按公式作一映射即可实现矩阵元素的随机存取。

稀疏矩阵

  • 稀疏矩阵是只含有少量非零元素的矩阵A[m×n],并且稀疏因子δ = t/(m+n)的(t为非零元素个数)的矩阵
  • 稀疏矩阵的压缩存储方法是只存储非零元素。
  • 每一个非零元素需由一个三元组(行下标,列下标,非零元素值) 确定,
  • 稀疏矩阵中所有非零元素构成一个三元组线性表。

三元组线性表

  • 三元组顺序表(TSMatrix)

- 三元组线性表中一个元素是用来描述一个非零元素的信息,其结构描述: ```c++ typedef struct { int i; // 行下标 int j; // 列下标 ElemType e; // 非零元素值 }Triple; ``` - 稀疏矩阵的顺序存储就是用一段连续的存储单元依次存储其对应的三元组线性表,并称这种存储结构的三元组线性表为三元组顺序表。

- 说明: - 稀疏矩阵进行压缩存储后,矩阵的行数和列数不能显式的反映出来,这样,如果压缩存储时仅仅存储稀疏矩阵中非零元素,就有可能出现不同的稀疏矩阵对应同一个三元组线性表,因此,要唯一地表示一个稀疏矩阵,还必须在存储三元组线性表的同时存储整个矩阵的行数和列数以及非零元素的个数。 - 虽然稀疏矩阵中没有插入和删除操作,但是,稀疏矩阵在进行相关运算如两个矩阵相加、矩阵的修改等操作都有可能使得稀疏矩阵的非零元素的个数发生改变,因此,描述三元组顺序表的存储结构时需要为稀疏矩阵的相关操作预留存储空间。
  • 三元组顺序表的结构描述:
# define MAX_SIZE 100
typedef struct {
    int m,n,t;        
   Triple data[MAX_SIZE];      
}TSMatrix;   
  • 三元组单链表(TLink) -用指针依次把稀疏矩阵中非零元素的三元组链接起来,就构成了稀疏矩阵的三元组单链表。在三元组单链表中,每个结点的数据域由稀疏矩阵中非零元素的行下标、列下标和元素值组成,单链表的头结点中存放稀疏矩阵的行数和列数。其结点的结构描述如下: typedef struct TripleNode{ int i;
    int j; ElemType e;
    struct TripleNode *next;
    }TLink;