5.1 数组的定义
数组:由一组类型相同、下标不同的变量构成。
特点:各个元素具有统一类型、下标 具有固定上界和下界、基本操作简单(初始化、销毁、修改、存取)
N维数组:n个下标,每个元素受到n个关系约束;一个n维数组可以看成是由若干个n-1维数组成的线性表。
5.2 数组的顺序存储
计算机的存储结构是一维的,而数组一般是多维的,那么就要进行一维化:事先约定按某种次序将数组元素排成一列序列,然后将这个线性表存入存储器中。例如:二维数组可以规定按行存储,也可以规定按列存储。C中采用行优先顺序。
利用一维线性存储的特性,可以计算数组元素的地址,必须知道的参数:①开始节点的存放地址即基地址②维数和每维的上下界③每个数组元素所占用的单元数。
n维数组任意元素的地址计算公式:
Loc(j1,j2,j3,…,jn)=Loc(0,0,..,0)+ 其中cn=L,ci-1=bi*ci , 1<i≤n。
N维数组的顺序存储表示:
#define MAX_ARRAY_DIM 8 //假设最大维数为8
typedef struct{
ElemType *base; //数组元素基址
int dim;//数组维数
int *bound;//数组各维长度信息保存区基址
int *constants;//数组映像函数常量的基址
}Array;
数组的链式存储方式—用带行指针向量的单链表表示。
5.3 矩阵的压缩存储
为了节省数组元素的存储空间,所谓的压缩存储就是为多个值相同的元素只分配一个存储空间;对0元素不分配空间。若值相同的元素或0元素在矩阵中的分布有一定规律,则称此类矩阵为特殊矩阵;反之,成为稀疏矩阵(非0元素少)。
那么在稀疏矩阵中如何存储那些非0元素,稀疏矩阵的表示方法:
① 三元组:(i,j,aij)
② 十字链表:
i | J | V | |
Down | right | ||
down:同一列中下一非零元素的指针
right:同一行中下一非零元素的指针
每行非零元素链接成带表头结点的循环链表;每列非零元素也链接成带表头结点的循环链表。
③ 三元组矩阵表:失去时机存储功能
④ 带辅助向量的三元组表示。增加两个辅助向量。
记录每行非0元素个数,用NUM(i)表示;每行第一个非0元素在压缩后的三元组中的行号,用POS(i)表示。示例:
对于稀疏矩阵:
0 12 9 0 0 0
0 0 0 0 0 0
-3 0 0 0 14 0
0 0 24 0 0 0
0 18 0 0 0 0
15 0 0 -7 0 0
辅助向量表:
i | 1 | 2 | 3 | 4 | 5 | 6 |
NUM(i) | 2 | 0 | 2 | 1 | 1 | 2 |
POS(i) | 1 | 3 | 3 | 5 | 6 | 7 |
POS(i)=POS(i-1)+NUM(i-1)
三元组矩阵表:
i | j | v |
6 | 6 | 8 |
1 | 2 | 12 |
1 | 3 | 9 |
3 | 1 | -3 |
3 | 5 | 14 |
4 | 3 | 24 |
5 | 2 | 18 |
6 | 1 | 15 |
6 | 4 | -7 |
稀疏矩阵的转置,实现方法:压缩转置和快速转置。(以三元组表的形式表示稀疏矩阵)
压缩转置:反复扫描原表序列,从j=1-n依次进行转置。
快速转置:生成矩阵三元组表的按列优先的辅助向量,然后实现快速转置。 该辅助矩阵中num行记录第col列的非零个数,第一个cpos默认为1,之后的cpos为前一项的col和num之和。
col | 1 | 2 | 3 | 4 | 5 | 6 |
num[col] | 2 | 2 | 2 | 1 | 1 | 0 |
cpos[col] | 1 | 3 | 5 | 7 | 8 | 8 |
利用上面的辅助向量进行转置:遍历非零元素,通过元素的col列查找到相应的cpos[col]即其在转置矩阵中的位置为cpos[col],将该元素与三元矩阵相应的第cpos[col]交换后将矩阵表中的cpos[col]++和num[col]--。
两个算法的比较:记矩阵中列数为n,非零元素为t,m为总行数,则压缩转置时间复杂度为O(m*t),快速转置则为O(n+t)。快速转置增设辅助向量,空间换取时间。
5.4 广义表的定义
广义表:元素的值非原子类型,可以再分解,表中元素也可是一个线性表;所有数据元素仍然属于同一数据类型。
定义:广义表是线性表的推广,也称为列表。记为:LS=(a1,a2,…,an);其中表名为LS,表头为a1,表尾包括了从a2到an。
约定:①用小写字母表示原子类型,用大写字母表示列表;②第一个元素是表头,而其余元素则组成表尾。
广义表中元素既可是原子类型,也可是列表;当每个元素都为原子且类型相同时,就是线性表。
特点:1.次序性:每个元素都有一个直接前驱和一个直接后继,首尾有点特殊性2.有长度,表中元素个数3.有深度,表中括号重数4.可递归,自己作为自己的字表5.可共享
5.5 广义表的存储结构
通常用链式结构,每个元素用一个结点表示。
原子结点:表示原子,可设2个域或三个域。例如(tag=0,value)或(tag=0,atom,tp),其中tp是指向表尾的指针域。
表结点:表示列表,若表不空,可以分解为表头和表位,三个域:tag=1,表头指针(指向表头元素),表尾指针(指向表尾列表)。
5.6 总结
1. 数组可以视为一种广义线性表
2. 数组的存储有行/低地址优先和列/高地址优先两种不同的顺序
3. 对于稀疏矩阵,有较好的压缩存储和运算方法
4. 广义表是线性表的推广,也是一种线性结构
5. 任何一个非空表,表头可能是原子,也可能是列表,但是表尾一定是列表
5.7相关试题
1. 现在有一数组,已知一个数出现的次数超过一半,请用O(n)的复杂度的算法找出这个数。
答案1:创建一个hash_map,key为数组中的数,value为此数出现的次数。遍历一遍数组,用hash_map统计每个数出现的次数,并用两个值存储目前出现次数最多的数和对应出现的次数。这样可以做到O(n)的时间复杂度和O(n)的空间复杂度。
答案2:使用两个变量A和B,其中A存储某个数组中的数,B用来计数。开始时将B初始化为0。遍历数组,如果B=0,则令A等于当前数,令B等于1;如果当前数与A相同,则B=B+1;如果当前数与A不同,则令B=B-1。遍历结束时,A中的数就是要找的数。 这个算法的时间复杂度是O(n),空间复杂度为O(1)。
2. 一个文件中有40亿个整数,每个整数为四个字节,内存为1GB,写出一个算法,求出这个文件的整数里不包含的一个整数。
答:申请一个大数组,数组中的每一位表示一个int型数字。由于每个整数为四个字节,总共只有2^32约等于4G种可能,故需要512MB个bit,所以数组的大小为512MB,并初始化为0。首先遍历40亿个整数,并将对应的bit置为1。对于重复的数字只统计一次,可以使用或操作来实现。然后遍历一下数组的各个bit,是0就将对应的数字打印出来或保存起来。
3. 1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现
一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空
间,能否设计一个算法实现?。
答:将1001个元素相加减去1,2,3,……1000数列的和,得到的差即为重复的元素。
4.题目:求1+2+…n
要求:不能使用乘法、for、while、if、else、switch、case等关键字,以及条件判断语句(A?B:C).
答:定义一个类,new 一含有n 个这种类型元素的数组,那么该类的构造函数将确定会被调用n 次。我们可以将需要执行的代码放到构造函数里。