数组、特殊矩阵、稀疏矩阵、广义表

 

文章目录

  • 一、数组
    • 基本知识
    • 存储方式及地址计算
      • 一维数组
      • 地址计算
    • 二维数组
      • 地址计算
      • 例题
        • 例一
        • 例二
  • 二、特殊矩阵
    • 对称矩阵
    • 地址计算
  • 三、稀疏矩阵
    • 三元组
    • 三元组顺序表
    • 稀疏矩阵转置算法
      • 算法一
      • 算法二
  • 四、广义表
    • 什么是广义表
    • 广义表的存储结构
      • 第一种
      • 第二种

 
 

一、数组

基本知识

  数组(array)是具有相同类型的数据的集合,也就是说数组的所有元素的类型都是相同的,在所有的数据结构中,数组算是最常见也是最简单的一种数据结构,我们最常见的也就是一维数组,当然还有二维,三维……,数组需要先声明才能使用,数组的大小一旦确定就不可以在变了。
  数组中的每个数据元素都对应于一组下标(j1,j2,…,jn),每个下标的取值范围是0 <= ji <= bi-1,bi称为第 i 维的长度(i=1,2,…,n)。显然,当 n=1 时,n 维数组就退化为定长的线性表。反之,n维数组也可以看成时线性表的推广。
  数组一旦被定义,它的维数和维界就不再改变。因此,除了结构的初始化和销毁之外,数组只有存取元素和修改元素值的操作。
 
 

存储方式及地址计算

  由于数组一般不作插入或删除操作,也就是说,一旦建立了数组,则结构中的数据元素个数和元素之间的关系就不再发生变动。因此,采用顺序存储结构表示数组是自然的事了。
  顺序存储结构特点:数据元素的存储对应一块连续的存储空间,数据元素之间的前驱和后续关系通过数据元素在存储器的相对位置来反映。

一维数组

  采用顺序存储结构表示数组,所以一维数组的存储方式就十分易懂了。数组地址顺序排列,邻元素之间的内存地址的间隔一般就是数组数据类型的大小。
数组、特殊矩阵、稀疏矩阵、广义表_第1张图片
  我们看看C语言 int 数组的地址情况。定义一个 int 数组,大小为3,分别输出这三个元素的地址。可以发现三个元素地址是连续的,且间隔相同。

#include

int main()
{
	int a[] = {1,2,3};
	printf("%p\n",a[0]);
	printf("%p\n",a[1]);
	printf("%p\n",a[2]);
	return 0;
}

数组、特殊矩阵、稀疏矩阵、广义表_第2张图片
 

地址计算

a[i]的存储地址:a+i*len (a是数组首地址,len是每个数组元素所占长度)

 
 

二维数组

  我们可以吧二维数组看成时这样一个定长线性表:它的每个数据元素也是一个定长线性表。例如,一个二维数组,m行n列的矩阵形式。它可以看成是一个线性表
数组、特殊矩阵、稀疏矩阵、广义表_第3张图片

          A = (α0,α1,…,αp) (p = m - 1 或 n - 1)

其中每个数据元素αj是一列向量形式的线性表,如下图
          αj = (α0j,α1j,…,αm-1,j) (0 <= j <= n-1)

数组、特殊矩阵、稀疏矩阵、广义表_第4张图片
或者αi是一个行向量形式的线性表,如下图
        αi = (αi0,αi1,…,αi,n-1) (0 <= i <= m-1)

行
  对应地,对二维数组可有两种存储方式:一种以列序为主序的存储方式;一种是以行序为主序的存储方式
数组、特殊矩阵、稀疏矩阵、广义表_第5张图片

  在扩展BASIC、PL/1、COBOL、PASCAL和C语言中,用的都是以行序为主序的存储结构,而在FORTRAN语言中,用的是以列序为主序的存储结构。
 
 

地址计算

按行存储:
  a[i][j]的存储地址:a+(i*m+j)*len (a是数组首地址,len是每个数组元素所占长度,数组n行m列)
行存储

按列存储:
  a[i][j]的存储地址:a+(j*n+i)*len (a是数组首地址,len是每个数组元素所占长度,数组n行m列)
列存储

int 占4位二进制位,以4行3列数组位例,查看C语言的地址。

#include

int main()
{
	int a[4][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
	printf("int占2进制位:%d",sizeof(int));
	printf("16进制\n");
	printf("%p\n",a[0][0]);
	printf("%p\n",a[1][2]);
	printf("%p\n",a[3][2]);
	return 0;
}

数组、特殊矩阵、稀疏矩阵、广义表_第6张图片
 
 

例题

例一

题目: 设有数组 A[n, m],数组的每个元素长度为 3 字节,n 的值为 1~8,m 的值为 1~10,数组内存首地址 BA 开始顺序存放,请分别用列存储和行存储方式,求 A[5, 8] 的存储首地址为多少?
(1)行存储:A[5, 8] = A[0, 0] + [4 * (10) + 7] * 3 = BA + 141
(2)列存储:A[5, 8] = A[0, 0] + [7 * 8 + 4] * 3 = BA + 180
 

例二

数组、特殊矩阵、稀疏矩阵、广义表_第7张图片

 
 

二、特殊矩阵

  在数值分析中经常出现一些阶数很高的矩阵,同时在矩阵中有许多值相同的元素或者是零元素。有时为了节省存储空间,可以对这类矩阵进行压缩存储。所谓压缩存储是指:为多个值相同的元只分配一个存储空间;对零元不分配空间。
  假若值相同的元素或者零元素在矩阵中的分布有一定规律,则我们称此类矩阵为特殊矩阵;反之,称为稀疏矩阵
 

对称矩阵

若 n 阶矩阵 A 中的元满足性质:aij = aji 1 <= i,j <= n 则称为 n 阶对称矩阵
  对于对称矩阵,我们可以为每一对对称元分配一个存储空间,则可将 n2个元压缩存储到 n(n+1)/2 个元的空间中。不失一般性,我们可以行序为主序存储其下三角(包括对角线)中的元。
  假设以一维数组 sa[n(n+1)/2] 作为 n 阶对称矩阵 A 的存储结构,则 sa[k] 和矩阵元 aij之间存在着一一对应的关系:
数组、特殊矩阵、稀疏矩阵、广义表_第8张图片
对于任意给定一组下标 (i,j) ,均可在 sa 中找到矩阵元 aij,反之,对所有的 k=0,1,2,…,n(n+1)/2 - 1,都能确定 sa[k] 中的元在矩阵中的位置 (i,j)。由此,称 sa[n(n+1)/2] 为 n 阶对称矩阵 A 的压缩存储。
在这里插入图片描述
这种压缩存储的方法同样也适用于三角矩阵。

 

地址计算

按行优先存储,并存储下三角: sa[k] 和 矩阵元素 aij之间的对应关系 (1 <= i,j <= n)
数组、特殊矩阵、稀疏矩阵、广义表_第9张图片
 
按行优先存储,并存储上三角: sa[k] 和 矩阵元素 aij之间的对应关系 (1 <= i,j <= n)
数组、特殊矩阵、稀疏矩阵、广义表_第10张图片
 
按列优先存储,并存储下三角: sa[k] 和 矩阵元素 aij之间的对应关系 (1 <= i,j <= n)
数组、特殊矩阵、稀疏矩阵、广义表_第11张图片

 
按列优先存储,并存储上三角: sa[k] 和 矩阵元素 aij之间的对应关系 (1 <= i,j <= n)
数组、特殊矩阵、稀疏矩阵、广义表_第12张图片
 
 

三、稀疏矩阵

  假设在m×n得矩阵中,有 t 个元素不为零。令 δ = t / (m×n),称 δ 为矩阵的稀疏因子。通常认为 δ <= 0.05 时称为稀疏矩阵。
 

三元组

  压缩存储的概念,只存储稀疏矩阵的非零元。所以,除了存储非零元的值之外,还必须同时记下它所在行和列的位置 (i,j)。反之,一个三元组(i,j,aij)唯一确定了矩阵 A 的一个非零元。由此,稀疏矩阵可由表示非零元的三元组及其行列数唯一确定
数组、特殊矩阵、稀疏矩阵、广义表_第13张图片
三元组:( (1,2,12) , (1,3,9) , (3,1,-3) , (3,6,14) , (4,3,24) , (5,2,18) , (6,1,15) , (6,4,-7) )
 

三元组顺序表

//稀疏矩阵的三元组顺序表存储表示
#define MAXSIZE 12500 //假设非零元素个数的最大值为12500 
typedef struct{
	int i,j;//该非零元的行下标和列下标 
	ElemType v;//非零元值 
}Teiple;
typedef struct{
	Triple data[MAXSIZE + 1];//非零元三元组,data[0]未用 
	int mu, nu, tu;//矩阵的行数、列数和非零元个数 
}TSMatrix;

data域中表示非零元的三元组是以行序为主序顺序排列的。

我们看看转置矩阵三元组顺序表的对比。以上面的矩阵M和矩阵T为例。

i j v i j v
1 2 12 1 3 -3
1 3 9 1 6 15
3 1 -3 2 1 12
3 6 14 2 5 18
4 3 24 3 1 9
5 2 18 3 4 24
6 1 15 4 6 -7
6 4 -7 6 3 14

我们可由发现两者的差异:(1)将矩阵的行列值相互交换;(2)将每个三元组中的 i 和 j 相互调换;(3)重排三元组之间的次序便可实现矩阵的转置。

 

稀疏矩阵转置算法

算法一

  1. 由 M 的行数、列数以及非 0 元素数可以直接得到 T 的列数、行数和非 0 元素数。
  2. 对 M 中的数据进行遍历,即依次扫描第 0 列、第 1 列、……、最后一列,扫描过程交换行和列的顺序,并存储到 T 中对应的位置即可。
Status TransposeSMatrix(TSMatrix M, TSMatrix &T){
	//采用三元组表存储表示,求稀疏矩阵M的转置矩阵T 
	T.mu = M.nu;	T.nu = M.mu;	T.tu = M.tu;
	if(T.tu){
		q = 1;
		for(col=1; col<=M.nu; col++)//n是列数 
			for(p=1; p<=M.tu; p++)
				if(M.data[p].j == col){
					T.data[q].i = M.data[p].j;
					T.data[q].j = M.data[p].i;
					T.data[q].v = M.data[p].v;
					q++;
				}
	}
}

  两重循环,算法时间复杂度:O(nu * tu),即和 M 的列数及非零元的个数的乘积成正比。
  当非零元的个数 tu 和 mu×nu 同数量级时,时间复杂度就为 O(mu×nu2),这样时间复杂度就较大了,所以该算法仅适用于 tu≪mu×nu 的情况。
 
 

算法二

  1. 由 M 的行数、列数以及非 0 元素数可以直接得到 T 的列数、行数和非 0 元素数。
  2. 要想扫描一次 M 就能得到 T,必须每次扫描到一个三元组就直接将其放到 b 中相应的位置上,因此,需要知道 M 中的元素在 T 中的存储位置,这就要预先确定矩阵 M 的每一列的第一个非 0 元素在 T 中相应的位置。为此,需要附设两个数组,num 和cpot,分别用于存储矩阵 M 中每一列的非 0 元素个数和矩阵 M 中每一列第 1 个非0 元素在 T 中的存储位置。

显然,有如下的公式成立:

  • cpot[0] = 1
  • cpot[col] = cpot[col - 1] + num[col -1] 2<= col <= a.nu

矩阵 M的 num 和 cpot 的值,如下表:

col 1 2 3 4 5 6 7
num[ col ] 2 2 2 1 0 1 0
cpot[ col ] 1 3 5 7 8 8 9
Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T){
	//采用三元组表存储表示,求稀疏矩阵M的转置矩阵T
	T.mu = M.nu;	T.nu = M.mu;	T.tu = M.tu;
	if(T.tu){
		for(col=1; col<= M.nu; col++)
			num[col] = 0;
		for(t=1; t<=M.tu; t++)
			num[M.data[t].j]++;	//求M中每一列含非零元个数 
		cpot[1] = 1;
		//求第col列中第一个非零元在b.data中的序号 
		for(col=2; col<=M.nu; col++)
			cpot[col] = cpot[col-1] + num[col-1];
		for(p=1; p<=M.tu; p++){
			col = M.data[p].j;
			q = cpot[col];
			T.data[q].i = M.data[p].j;
			T.data[q].j = M.data[p].i;
			T.data[q].v = M.data[p].v;
			cpot[col]++;
		}
	}
} 

  时间复杂度:O(nu+tu)
  三元组顺序表又称有序的双下标法,它的特点是,非零元在表中按行序有序存储,因此便于进行依行顺序处理的矩阵运算。然而,若需按行号存取某一行的非零元,则需从头开始进行查找。

 

  

四、广义表

什么是广义表

  广义表是线性表的推广,是由 0 个或多个单元素或子表组成的有限序列。
  广义表与线性表的区别在于:线性表的元素都是结构上不可分的单元素,而广义表的元素既可以是单元素,也可以是有结构的表。
  广义表一般记作 LS = (α1, α2, … , αn)
其中,LS 是广义表 (α1, α2, … , αn) 的名称,n 是它的长度。线性表的定义中,αi(1≤i≤n)只限于是单个元素。而在广义表的定义中,αi 可以是单个元素,也可以是广义表,分别称为广义表 LS 的原子子表。习惯上,用大写字母表示广义表的名称,用小写字母表示原子。当广义表 LS 非空时,称第一个元素 α1 为 LS 的表头,称其余元素组成的表 (α1, α2, … , αn) 是 LS 的表尾。

广义表的定义是一个递归的定义。
广义表的例子:

  1. A = ()------A 是一个空表,它的长度为零。
  2. B = (e)------列表 B 只是一个原子 e,B 的长度为 1。
  3. C = (a, (b, c, d))------列表 C 的长度为 2,两个元素分别为原子 a 和子表 (b, c, d)。
  4. D = (A, B, C)------列表 D 的长度为 3,3 个元素都是列表。显然,将子表的值代入后,则有 D = ((), (e), (a, (b, c, d)))。
  5. E = (a, E)------这是一个递归的表,它的长度为 2。E 相当于一个无限的列表 E = (a, (a, (a, …)))。

3 个重要结论:

  1. 列表的元素可以是子表,而子表的元素还可以是子表… …由此,列表是一个多层次的结构,可以用图形象地表示。D = (A, B, C)------D = ((), (e), (a, (b, c, d)))
    数组、特殊矩阵、稀疏矩阵、广义表_第14张图片
  2. 列表可为其他列表所共享。例如在上述例子中,列表 A、B 和 C 为 D 的子表,则在 D 中可以不必列出子表的值,而是通过子表的名称来引用。
  3. 列表可以是一个递归的表,即列表也可以是其本身的一个子表。例如列表 E 就是一个递归的表。

  

广义表的存储结构

第一种

数组、特殊矩阵、稀疏矩阵、广义表_第15张图片

  • 表结点:用以表示列表。由 3 个域组成:标志域、指示表头的指针域和指示表尾的指针域。
  • 原子结点:用以表示原子。由 2 个域组成:标志域和值域。
//广义表的头尾链表存储表示 
typedef enum {ATOM, LIST}ElemTag;//ATOM==0:原子,LIST==1:子表 
typedef struct GLNode{
	ElemTag tag;//公共部分,用于区分原子结点和表结点 
	union{//原子结点和表结点的联合部分 
		AtomType atom;//atom是原子结点的值域,AtomType由用户定义 
		struct {struct GLNode *hp, *tp;}ptr;//ptr是表结点的指针域,ptr.hp和ptr.tp分别指向表头和表尾 
	};
}*GList;//广义表类型 

上例的存储结构:
数组、特殊矩阵、稀疏矩阵、广义表_第16张图片

  • 除空表的表头指针为空外,对任何非空列表,其表头指针均指向一个表结点,且该结点中的 hp 域指示列表表头(或为原子结点,或为表结点),tp 域指向列表表尾(除非表尾为空,则指针为空,否则必为表结点)。
  • 容易分清列表中原子和子表所在层次。如在列表 D 中,原子 a 和 e 在同一层次上,而 b、c 和 d 在同一层次且比 a 和 e 低一层,B 和 C 是同一层的子表。
  • 最高层的表结点个数即为列表的长度。

  

第二种

数组、特殊矩阵、稀疏矩阵、广义表_第17张图片

//广义表的扩展线性链表存储表示 
typedef enum {ATOM, LIST}ElemTag;//ATOM==0:原子,LIST==1:子表 
typedef struct GLNode{
	ElemTag tag;//公共部分,用于区分原子结点和表结点 
	union{//原子结点和表结点的联合部分 
		AtomType atom;//原子结点的值域 
		struct GLNode *hp;// 表结点的表头指针 
	};
	struct GLNode *tp;//相当于线性链表的next,指向下一个元素结点 
}*GList;//广义表类型GList是一种扩展的线性链表 

数组、特殊矩阵、稀疏矩阵、广义表_第18张图片

你可能感兴趣的:(数据结构与算法,数据结构,数组,特殊矩阵,稀疏矩阵,广义表)