数组
实际上是一组有固定个数的元素的集合。看成一般线性表的扩充,一维数组就是线性表,二维数组定义为:每个数据元素都是一个一维数组的的线性表。 所以一般操作只有两类:获取和修改特定位置的值,所以主要采用顺序结构。
元素总数=上限-下限+1
存储和实现
由于多维数组还是要存在线性区域内,所以先存行还是列的存储分为两种
- 行序存储:BASIC、COBOL、C语言和PASCAL等高级语言
- 列序存储:FORTRAN等高级语言
若三维数组: 行序就是一层一层楼,列序就是一片一片切菜
一维数组的地址计算
数组中每个元素占size个存储单元,则元素ai存储地址为:
Loc(a[i])=Loc(a[1])+(i-1)*size
所求地址是基地址+该下标变量与基地址之间序号。 由于同类型,所以size确定。
二维数组的地址计算
例如二维数组Amn(m行n列),以行序存储,此处注意设首元素下标从1开始:a[1][1]。
所求位置=基地址(数组名A[1][1])+前i-1行所占空间+不足一行的列零头。
设每个元素占一个存储单元:
Loc(a[i][j])=Loc(a[1][1]) + n*(i-1) + (j-1)
设每个元素占size个存储单元:
Loc(a[i][j])=Loc(a[1][1]) + n*(i-1)*size + (j-1)*size
三维数组的地址计算
- 特殊情况
例如三维数组Amnr(m行n列r层),以行序存储,下限从首元素:a111开始,地址为LOC[1,1,1]。
每个元素占size个存储单元(1≤i≤r,1≤j≤m,1≤k≤n),i是层数,j是行数,k是列数
所求位置=首地址+(i-1)整层数+(j-1)整行数+不足一行的列零头(从1,1,1开始的特殊情况)
Loc[i][j][k]=Loc[1,1,1]+(i-1)*m*n*size+(j-1)*n*size+(k-1)*size
- 普遍情况
将三维数组推广到一般情况
j1,j2,j3的下限为c1,c2,c3上限为d1,d2,d3,j1是层数,j2是行数,j3是列数。每个元素占size个存储单元a(j1,j2,j3)的地址为:
Loc(a[j1][j2][j3])=Loc(a[c1][c2][c3])+((j1-c1)*(d2-c2+1)*(d3-c3+1)+(j2-c2)*(d3-c3+1)+(j3-c3))*size
这里+1是因为包含上下限
其实就是某一块不规则体积
压缩存储
原则:对有规律的元素和值相同的元素只分配一个存储空间,对于零元素不分配空间
三角矩阵(分界线左上到右下)
下三角矩阵
-
当i<j时,aij=0,上面都是0
-
开辟一个B[k]数组,k=n*(n+1)/2(等差数列)。分别存a11,a21,a22,a31...
需要将aij和B[i]联系起来:
对于下三角矩阵,按“行序为主序”进行存储,得到的序列 为:a11,a21,a22,a31,a32,a33...an1,an2...ann由于下三角矩阵的元素个数为n(n+1)/2,所以可压缩存储到一个大小为n(n+1)/2的一维数组中。下三角矩阵中元素aij(i>j),在一维数组A中的位置为:
LOC[i,j]=LOC[1,1]+i(i-1)/2+j-1 == 基地址+整行数+不足的列数
上三角矩阵
若i>j时,有aij=0,下面都是0
- 对称矩阵:所有元素满足aij=aji,对角线轴对称
LOC[i,j]=LOC[1,1]+j(j-1)/2+i-1
基地址+所求左上三角形+所求列左边的
带状矩阵(对角线对称分布)
所有非零元素都集中在以对角线为中心的带状区域。(下图是常见的三对角带状矩阵)
三对角带状矩阵下标特征:
i=1 , j=1,2;
1<i<n , j=i-1,j=i,j=i+1;
i=n , j=i-1,i;
因此三对角带状矩阵所需要的空间大小为:3n-2
计算列数:若j-i=-1,元素是一行最左边,需要+1;若j-i=0,元素是一行中间,需要+1;若j-i=-1,元素是一行最右边,需要+1;
LOC[i,j]=LOC[1,1]+(i-1)-1+j(j-1)/2+i-1=LOC[1,1]+2(i-1)+j+1
稀疏矩阵
非零元素个数一般低于总数的1/4到1/3
三元组表表示
将一个元素的位置和值储存在一个结构体内:
而三元组表形式的一个重要操作就是对稀疏矩阵进行转置操作:
最简单的矩阵转置算法:
void TranMatrix(ElementType source[n][m],ElementType dest[n][m])
{int i,j;
for(i=0;i<m;i++)
{ for(j=0;j<n;j++)
dest[i][j]=source[j][i];
}
}
默认A矩阵是源矩阵,B矩阵是转置后的目标矩阵。
为了保证转置后的矩阵的三元组表B也是以“行序为主序”进行存放,则需要对行、列互换后的三元组B,按B的行下标(即A的列下标)大小重新排序,但排序时间开销较大。
列序递增转置法
默认A矩阵是源矩阵,B矩阵是转置后的目标矩阵。且转置后的列序其实就是转置前的行序。
思想:按照三元组表A的列序递增的顺序转置,当B中行号为1时扫描A三元组,从小到大找出A中列号为1的所有元素;然后当B中行号为2...
算法主体:
void TransposeTSMatrix(TSMatrix A,TSMatrix *B)
{/*矩阵A转置到矩阵B中,矩阵使用三元组表表示,长宽分别存在m,n中,行数row,列数col,值v*/
int i,j,k; //i是A表中元素的位置,j是B表中元素的位置,k是A的外部循环
B->m=A.n; //B的行数=A的列数
B->n=A.m; //B的列数=A的行数
B->len=A.len; //B的三元组表长=A的表长
if(B->len>0)
{j=1; //三元组表B的行号从1开始
for(k=0;k<=A.n;k++) //A的列数1,2,3...n列
{
for(i=1;i<=A.len;i++) //从A的表头找到表尾,看谁等于k
{ if(A.data[i].col==k) //当A的列数等于k时,把这个元素存入B
{ B->data[j].row=A->data[i].col; //B第i个元素的行数等于A[j]的列数,i是断的,j是一个一个接着的
B->data[j].col=A->data[i].row; //B的列数等于A的行数
B->data[j].v=A->data[i].v; //B的值等于A的值
j++; //B三元组表进入下一个元素
}
}
}
}
}
时间复杂度=O(A.n x A.len)
未深入讨论快速转置算法,相关代码及讲解:
www.bilibili.com/video/BV1kx… www.bilibili.com/video/BV1kx…
算法思想:
插入一个“统计选票”的例子:20000张选票,都是有效票,投给10个候选人,统计各人所得票数
思路:给十个人编号并将选票数分别存在c[10]的10个元素中
主体:
for(i=0;i<20000;i++)
{
scanf("%d",&x); //投给编号为x的人一票
c[x]++; //直接给该编号内数值+1
}
通过一重循环完成转置,即对A中所有非零元“一次定位”直接放到B三元组表的正确位置。故需要设置position[ ]和num[ ]两个数组,存放如下预先计算的值,以实现一次定位。
position[col] 存放A三元组第col列中第一个非零元素的位置,即A三元组中第某列在B三元组中第一个元素的位置。
num[col] 存放A三元组第col列非零元素个数,即1列有多少个元素,多少个元素后换列。
类似之前的统计选票问题,若循环到某列的元素,就给num[某]的值加1,最后循环存入的时候每存一个就给position[某]的值加1
时间复杂度=O(A.n + A.len)
十字链表
该结构除了和三元组一样的 行,列,值 三个内容,还存储两个指针,分别名叫down和right,意为链接同一行的下一个非零元素/链接同一列的下一个非零元素
举个例子!
其最终十字链表形式为下图:
结点结构声明
typedef struct OLNode
{int row,col; //行下标和列下标
ElementType value;
struct OLNode *right,*down;
}OLNode,*OLink;
链表结构声明
typedef struct
{OLink *row_head,*col_head; //分别为行链表头指针和列链表头指针
int m,n,len; //存储行数,列数,非零元素个数
}CrossList;
存入(插入)算法描述
CreateCrossList (CrossList*M)
{ scanf(&m,&n,&t); //输入M的行数,列数和非零元素的个数
M->m=m;M->n=n;M->len=t;
if(!(M->row_head=(OLink*)malloc((m+1)sizeof(OLink)))) exit(OVERFLOW); //申请了m+1个OLink结构的链表并赋给m的行头指针,若不成功则退出
if(!(M->col_head=(OLink*)malloc((n+1)sizeot(OLink)))) exit(OVERFLOW); //与上同理,意为创建相应个数的表头结点
M->row_head[]=M->col_head[]=NULL; //初始化行、列头指针,使各行列链表为空
for(scanf(&i,&j,&e);i!=O;scanf(&i,&j,&e)) //若输入i不为0,就不断输入
{
if(!(p=(OLNode *) malloc(sizeof(OLNode)) exit(OVERFLOw);
p->row=i; //生成结点并赋值
p->col=j;
p->value=e;
/*以下为分别插入行和列*/
if(M->row_head[i]==NULL) //如果行表是空的就直接插
M->row_head[i]=p;
else
{/*若行表不是空的就需要寻找行表中的插入位置*/
for(q=M->row_head[i];q->right&&q->right->col<j;q=q->right) //若右边仍有值并且小于列下标j,则右移
p->right=q->right;q->right=p; //完成插入
}
if(M->col_head[j]==NULL) //如果列表是空的就直接插
M->col_head[j]=p;
else
{/*若列表不是空的就需要寻找行表中的插入位置*/
for(q=M->col_head[j];q->down&&q->down->row<i;q=q->down) //若下边仍有值并且小于行下标i,则下移
p->down=q->down;q->down=p; //完成插入
}
}
}
时间复杂度= O(t*s) s=max(m,n)
广义表
广义表的概念
广义表也是线性表的一种推广。广义表也是n个数据元素(d1,d2,d3,....dn)的有限序列,但不同的是,广义表中的di,既可以是单个元素,还可以是一个广义表,通常记作: GL=(d1,d2,d3,....dn),可以像套娃一样一个元素里面塞一个表。
广义表中默认:大写ABC...是表名,小写abc...是元素名
GL是广义表的名字,通常用大写字母表示。n是广义表的长度,这个广义表的表头是d1,表尾是除了表头以外其余元素构成的表,一定是个表。 若 di 是一个广义表,则称表 di 是广义表GL的子表。
一个练习题,其中每句话并不独立:
补充:head(C)=a,tail(C)=空
- 广义表的元素可以是子表,而子表还可以是子表....由此,广义表是一个多层的结构。
- 广义表可以被其他广义表共享。如:广义表B就共享表A。在表B中不必列出表A的内容,只要通过子表的名称就可以引用该表。
- 广义表具有递归性,如上图的广义表C。
广义表的存储结构
广义表中有两种结点:单个元素结点 和子表结点。任何一个非空广义表都可以分解成表头和表尾,同样,一对确定的表头和表尾可唯一确定一个广义表。
-
元素结点 需要两个域:标志域和值域,其中标志域=0
-
表结点 需要三个域:标志域,指向表头的指针域,指向表尾的指针域,其中标志域=1
-
广义表的长度定义为最外层所包含元素个数
-
广义表的深度定义为该表展开后括号的层数,A= (b, c)只有1层括号,深度为1。B=(A, d=((b,c)c)有2层括号,深度为2。C=(f,B,h)=(f,((b,c)c),h)有3层括号,深度为3。D=()深度为0
-
递归表深度无限,长度是有限值
-
优先满足表头,表尾更可以空
举个例子:
-
A(a,(b,c));
解读:A先是一个表,表头指向元素a,表尾指向一个表,然后这个表内又有表头和表尾,表头是b,表尾必是一个表,然后表头指针指向c,表尾指针指向空。长度为2
-
B(A,A,D); D( ); D是空表
解读:B先是一个表,表头指向表A,表尾指向一个表,这个表的表头指向A,表尾必指向一个表,指向的表D是空表,所以剩下两个域都是空。 长度为3
-
C=(a,C);
解读:这个广义表中包含了递归操作,首先C是一个表,表头指向元素a,表尾必指向一个表,这个表的表头是C,实现嵌套内含a和C的表,表尾无穷尽,所以表尾为空。 长度为2
E=(( )) 这是长度为1,表头和表尾均为()
头尾链表存储结构:
扩展线性链表存储结构:
以上为广义表结构,不详细展开。 www.bilibili.com/video/BV1kx…
广义表的操作(了解)
表头指针hp,表尾指针tp
求广义表表头
GList Head(GList L)
{ if(L==NULL)
return(NULL); //若L空,则返回空
if(L->tag==ATOM)
exit(0); //标志位等于元素结点,说明本身就是单元素,返回0
else
return (L->atom_htp.htp.hp); //若不是这些,则返回表头指针
}
复制广义表
求广义表的长度
int Length(GList L)
{ int n=0; GLNode *s;
if(L==NULL)
return(0);
if(L->tag==ATOM)
exit(O);
/*并非一个元素结点的时候*/
s=L;
while(s!=NULL)
{ k++;
s=s->atom_htp.htp.tp;
}
return(k);
}
总结
数组
- n维数组可以看成是每个数据元素均是一个n-1维数组的线性表。
- 数组是一组有固定个数元素的集合。给出维数和每一维的上下限,数组中的元素个数就固定了。
- 数组采用顺序存储结构,主要操作是随机存取,即给定元素的下标,得到该元素在计算机中的存放位置。
特殊矩阵:
- 元素分布有规律的矩阵。只需找到对应规律的函数,就可由二维矩阵A中元素aij的下标计算出一维内存空间地址值K,实现二维矩阵到压缩存储后的一维数组的存储映射。
- 非零元素很少的稀疏矩阵,只存非零元素所在的行号,列号及元素值来实现压缩存储。
广义表:
- 广义表是n个元素(dt,d,d3,...,dn)的有限序列,d既可以是单个元素,也可以是广义表。广义表的定义具有递归性,其操作通常采用递归实现。
- 一个非空的广义表GL可以看成是由表头和表尾构成。第一个元素称为表头,除表头以外的其余元素构成表尾。
- 常用的存储方式有:头尾链表存储结构和扩展线性链表存储结构。