数据结构与算法:线性表之数组


1. 概念

1.1 什么叫数组:

数组(Array)是一种线性表数据结构。它是一组连续的内存空间,来存储一组具有相同类型的数据。

什么是线性表跟非线性表:

线性表:通俗一点就是数据像一条线一样排成的结构,线性表上的数据只有前后两个方向,另外像链表,队列,栈等也是线性表的数据结构。
非线性表:像二叉树,图,堆等,数据之间不只是前后的关系。

数组是连续的内存空间和相同类型的数据。这让它有着高效地随机访问和低效地删除跟添加数据。为了保证连续性,就需要大量地数据搬移,效率就很差。

高效地数据访问是如何做到的?

比如我们申请了一个长度为10的int型的数组,(说到这儿,数组的长度是指存放线性表的存储空间的长度,线性表的长度指的是线性表中数据元素的个数),那么计算机分配了一块连续的内存,假设1000~1038,内存的首地址1000。那么计算机通过地址来访问内存中的数据,a[i] = 1000 + i*4,4是数组中每个元素的大小。

低效地插入与删除:

插入:为了保证内存数据的连续性,将一个数据插入到第k位置的大小为n的数组中,那么k~n的元素都要顺序地往后移动,最好的时间复杂度为1,最坏为n,平均为O(n)。
改进:如果一个数组储存的元素并不是有序的,只是简单的用来存储元素,那么可以将新的元素放在k的位置,原来的元素放到最后。

删除:删除也需要搬数据,不然内存中出现空洞导致不连续。最好时间复杂度1,最坏为n,平均为n。

数组越界:
int main(int argc, char* argv[])
{
    int i = 0;
    int arr[3] = {0};
    for(; i<=3; i++)
    {
        arr[i] = 0;
        printf("hello world\n");
    }
    return 0;
}

因为,数组大小为 3,a[0],a[1],a[2],而我们的代码因为书写错误,导致 for 循环的结束条件错写为了 i<=3 而非 i<3,所以当 i=3 时,数组 a[3]访问越界。
我们知道,在 C 语言中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的。根据我们前面讲的数组寻址公式,a[3]也会被定位到某块不属于数组的内存地址上,而这个地址正好是存储变量 i 的内存地址,那么 a[3]=0 就相当于 i=0,所以就会导致代码无限循环。数组越界在 C 语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。
因为,访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序就可能不会报任何错误。

2. 数组的顺序储存

原因: 一旦给定数组的维数n及各维长度bi(1≤i≤n),则该数组中元素的个数就是确定的,数组的基本操作不涉及数组结构的变化。因此对于数组而言,采用顺序存储表示比较适合。
方法: 内存储器的结构是一维的,对于一维数组可直接采用顺序存储,用一维的内存存储表示多维数组,就必须按照某种次序将数组中元素排成一个线性序列,然后将这个线性序列存放在一维的内存储器中,这就是数组的顺序存储结构。
分类: 按行序存储和按列序存储
二维数组Am✖n 以行为主的存储序列为:
a11,a12,…,a1n,a21,a22,…,a2n,…,am1,am2,…,amm
以列为主的存储序列为:
a11,a21,…,am1,a12,a22,…,am2,…,a1n,a2n,…,amn
计算
(1)一维数组的地址计算
Loc(A[i])=Loc(A[1])+(i-1)✖size
(2)二维数组的地址计算
如果每个元素占size个存储单元:
Loc(A[i][j])=Loc(A[1][1])+((i-1)✖n+(j-1))✖size
如果每个元素占一个存储单元:
Loc(A[i][j])=Loc(A[1][1])+(i-1)✖n+(j-1)
(3)三维数组的地址计算
Loc(A[i][j][k])=Loc(A[1][1][1])+((i-1)✖m✖n+(j-1)✖n+(k-1))✖size
当 j1,j2,j3的下限分别为c1,c2,c3,上限分别为d1,d2,d3时
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
(4)n维数组的地址计算
Loc(A[j1][j2]…[jn])=Loc(A[c1][c2]…[cn])+Σ(i=1到n)ai✖(ji-ci)
其中,ai=size✖Π(k=i+1到n)(dk-ck+1),1≤i≤n
特殊矩阵的压缩存储
(1)规律分布的特殊矩阵
这类矩阵中元素分布的规律可以用数学公式来反映,通常利用这些规律将其压缩存储于一维数组中。
在这里插入图片描述

三角矩阵

下三角矩阵中元素aij(i≥j),在一维数组中的存储单元的下标为:
Loc(A[i][j])=Loc(A[1][1])+(i✖(i-1)/2+j-1)
注意:对于对称矩阵,因其元素满足aij=aji,我们可以为每一对相等的元素分配一个存储空间,即只存下三角(或上三角)矩阵,从而将n²个元素压缩到n(n-1)/2个空间中
带状矩阵
在这里插入图片描述

Loc(A[i][j])=Loc(A[1][1])+(3(i-1)-1+j-i+1)✖size
=Loc(A[1][1])+(2(i-1)+j-1)✖size

稀疏矩阵
在这里插入图片描述

稀疏矩阵指的是大多数元素为0的矩阵。从直观上讲,当非零元素个数低于总元素的30%时,这样的矩阵为稀疏矩阵。
稀疏矩阵的三元组表表示法:
稀疏矩阵的三元组表类型定义

#define MAXSIZE 1000			/*非零元素的最大个数*/
typedef struct
{
	int row,col;				/*非零元素的行下标和列下标*/
	ElementType e;				/*非零元素的值*/
}Triple;
typedef struct
{
	Triple data[MAXSIZE+1];		/*非零元素的三元组表,data[0]未用*/
	int m,n,len;				/*矩阵的行数,列数和非零元素的个数*/
}TSMatrix;

对于稀疏矩阵的压缩存储,采取只存储非零元素的方法。由于稀疏矩阵中非零元素aij的分布没有规律,因此,在存储非零元素值的同时,还必须存储非零元素在矩阵中所处的行号和列号,这就是稀疏矩阵的三元组表表示法。
在这里插入图片描述

稀疏矩阵的转置运算
第一步:将三元组表中的行列对换
第二步:若转置后的三元组表以行序为主序存放,则对转置后的三元组表按行标递增排列,反之亦然。
稀疏矩阵的链式存储结构:十字链表
十字链表的类型定义:

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;

为了避免大量移动元素,使用十字链表,能够灵活插入因运算而产生的新的非零元素,删除因运算而产生的新的非零元素,实现矩阵的各种运算。

在这里插入图片描述

除了(row,col,value)以外,还要添加两个链域:right用于链接同一行中的下一个非零元素,down用于链接同一列中的下一个非零元素。表示如下:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值