什么是数组?

业余杂谈 herman 66浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:codedq,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:codedq,之前的微信号好友位已满,备注:返现
饿了么大量招人,我内推!Java 方向!薪资不设上限,工作年龄不限!工作地点限魔都,可电话面试!简历,发我微信:codedq
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

今天要介绍的主角就是-数组,数组也是数据呈线性排列的一种数据结构。与前一节中的链表不同,在数组中,访问数据十分简单,而添加和删除数据比较耗工夫。这和什么是数据结构那篇文章中讲到的姓名按拼音顺序排列的电话簿类似。

数组

数组结构

如上就是数组的概念图,Blue、Yellow、Red 作为数据存储在数组中,其中 a 是数组的名字,后面 [] 中的数字表示该数据是数组中的第几个数据,该数字也就是数组下标,下标从 0 开始计数,比如 Red 就是数组 a 的第 2 个数据。

那么为什么许多编程语言中的数组都从 0 开始编号的呢?先别急,可以先自己思考下,将会在文末进行讲解。

数组内存结构

从图中可以看出来,数组的数据是按顺序存储在内存的连续空间内的。

数组使用顺序内存

由于数据是存储在连续空间内的,所以每个数据的内存地址(在内存上的位置)都可以通过数组下标算出,我们也就可以借此直接访问目标数据,也就是随机访问。

数组的随机访问

比如现在我们想要访问 Red,如果是链表的话,只能使用指针就只能从头开始查找,但在数组中,只需要指定 a[2],便能直接访问 Red。

数组的下标访问

但是,如果想在任意位置上添加或者删除数据,数组的操作就要比链表复杂多了。这里我们尝试将 Green 添加到第 2 个位置上。

输入的插入

首先,在数组的末尾确保需要增加的存储空间。

数组的空间是有限的

为了给新数据 Green 腾出位置,要把已有数据一个个移开,首先把 Red 往后移。

数组元素的移动

然后把 Yellow 往后移。

数组

最后在空出来的位置上写入 Green。

存储满了的数组

添加数据的操作就完成了。

反过来,如果想要删除 Green 呢?

数组删除元素

首先,删掉目标数据 Green。

删除数组元素

然后把后面的数据一个个往空位移,先把 Yellow 往前移。

数组移动

接下来移动 Red。

未满的数组

最后再删掉多余的空间,这样一来 Green 便被删掉了。

补充

这里讲解一下对数组操作所花费的运行时间,假设数组中有 n 个数据,由于访问数据时使用的是随机访问(通过下标可计算出内存地址),所以需要的运行时间仅为恒定的 O(1)。

通过数组下标计算出内存地址的寻址公式如下:

a[i]_address = base_address + i * data_type_size

其中 base_address 为内存块的首地址,data_type_size 表示数组中每个元素的大小。

但另一方面,想要向数组中添加新数据时,必须把目标位置后面的数据一个个移开。所以,如果在数组头部添加数据,就需要 O(n) 的时间,删除操作同理。

在链表和数组中,数据都是线性地排成一列。在链表中访问数据较为复杂,添加和删除数据较为简单;而在数组中访问数据比较简单,添加和删除数据却比较复杂。

我们可以根据哪种操作较为频繁来决定使用哪种数据结构。

数组与链表的比较

最后,让我们一起来思考下刚开始提到的问题:为什么很多编程语言中数组都从 0 开始编号?

解惑

从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。如果用 a 来表示数组的首地址,a[0] 就是偏移为 0 的位置,也就是首地址,a[k] 就表示偏移 k 个 type_size 的位置,所以计算 a[k] 的内存地址只需要用这个公式:

a[k]_address = base_address + k * type_size

但是,如果数组从 1 开始计数,那我们计算数组元素 a[k] 的内存地址就会变为:

a[k]_address = base_address + (k-1)*type_size

对比两个公式,可以发现,从 1 开始编号,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。

数组作为非常基础的数据结构,通过下标随机访问数组元素又是其非常基础的编程操作,效率的优化就要尽可能做到极致。所以为了减少一次减法操作,数组选择了从 0 开始编号,而不是从 1 开始。

除此之外还有历史原因,C 语言设计者用 0 开始计数数组下标,之后的 Java、JavaScript 等高级语言都效仿了 C 语言,或者说,为了在一定程度上减少 C 语言程序员学习 Java 的学习成本,因此继续沿用了从 0 开始计数的习惯。实际上,很多语言中数组也并不是从 0 开始计数的,比如 Matlab。甚至还有一些语言支持负数下标,比如 Python。

总结

这篇文章主要介绍了数据结构中常用的数组,数组用一块连续的内存空间,来存储相同类型的一组数据,最大的特点就是支持随机访问,但插入、删除操作也因此变得比较低效,平均情况时间复杂度为 O(n)。有一种高效的查找算法是二分查找法,就是利用了数组随机访问的特性。

总得来说,数组适用于多操作多、写操作少的场景,和我们上一篇文章中的链表正好相反。

参考资料

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号1:xmtxtt(5000人已满),微信号2:xttblog(5000人已满),微信号3:codedq(超3800)。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » 什么是数组?