数组介绍

倾然丶 夕夏残阳落幕 提交于 2020-03-03 02:19:54

什么是数组?

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

数组如何实现随机访问?

基于数组的两大特性:线性表结构和连续的内存空间和相同类型的数据,使得数组可以通过下标直接访问数组元素,从而具有“随机访问”的特性。

具体实现方法:

当计算机需要随机访问数组中的某个元素时,通过寻址公式计算出该元素存储的内存地址,寻址公式如下所示:

a[i]_address = base_address + i * data_type_size

其中 i 为数组元素的下标(从0开始),data_type_size 表示数组中每个元素的大小,假如数组中存储的是 int 类型数据,那么 data_type_size 就为4个字节。

数组查找的时间复杂度

根据下标随机访问的时间复杂度为 O(1)。

注意:前提条件是根据下标进行的随机访问,如果是在下标不确定的情况下查找某个元素,那么即便是已经排好序的数组,你用二分查找,时间复杂度也是 O(logn)。

低效的“插入”和“删除”

 数组为了保持内存数据的连续性,会导致插入、删除操作比较低效,具体是为什么呢?

插入操作

每当在数组中插入一个新元素的时候,为了给这个新插入的元素挪出一个位置,我们可能需要移动大量的元素。

假设数组的长度为 n,如果在数组的末尾插入元素,那就不需要移动数组了,所以最好时间复杂度为 O(1)。但如果在数组的开头插入元素,那所有的元素都要一次往右移动一位,所以最坏时间复杂度为 O(n)。

因为我们在每个位置插入元素的概率是一样的,所以平均时间复杂度为 (1+2+3+……+n) / n = O(n)。

删除操作

和插入数据类似,如果我们要删除某个位置的数据,为了内存的连续性,也需要搬移数据。

类比插入数据,如果删除数组末尾的数据,那么就不需要移动数组了,所以最好时间复杂度为 O(1);如果删除开头的元素,则整个数组都要移动,所以最坏时间复杂度为 O(n);同理可知,平均时间复杂度为 O(n)。

警惕数组的访问越界问题

数组越界在 C 语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。因此,在 C 语言中出现数组访问越界的情况时,会出现莫名其妙的逻辑错误,例如,访问了非法地址、陷入死循环等等。

不同于 C 语言,Java 本身就会做数组越界检查,比如下面几行代码,就会抛出 java.lang.ArrayIndexOutOfBoundsException。

1 int[] a = new int[3];
2 a[3] = 10;

容器能否完全替代数组?

针对数组类型,很多语言都提供了容器类。比如 Java 中的 ArrayList、C++ STL 中的 vector。

与数组相比,容器有哪些优势呢?

以 Java 的 ArrayList 为例,ArrayList 最大的优势就是可以将很多数组操作的细节封装起来,比如前面所说的数组插入、删除数据时需要移动其他数据等;另外,它还支持动态扩容。

我们在定义数组的时候通常需要预先指定大小,以便计算机分配连续的内存空间,当预先指定的空间不够用时,则需要重新分配一块更大的空间并将原来的数据复制过去,然后再继续插入新的数据。如果使用 ArrayList,每次内存空间不够用的时候,它都会讲空间自动扩容为 1.5 倍大小,这样我们就完全不需要关心底层的扩容逻辑了。

数组有哪些优势?

1. 以 Java 的 ArrayList 为例,ArrayList 无法存储基本数据类型,比如 int、long,需要封装为 Integer、Long 类。这种时候,选用数组可减少一定的性能消耗。

2. 如果数据大小事先已知,并且对数据的操作非常简单,用不到 ArrayList 提供的大部分方法,也可以直接使用数组。

总结一下

对于业务开发,直接使用容器就足够了,只需要牺牲一点点的性能,就能省下大量时间。但如果是做一些非常底层的开发,比如开发网络框架,性能的优化需要做到极致,这个时候就应该使用数组。

为什么数组要从 0 开始编号?

前面我们讲过寻址公式:

a[i]_address = base_address + i * data_type_size

所谓下标,其实是一个“偏移量”,如果数组从 1 开始计数,那我们计算数组元素 a[k] 的内存地址就会变为:

a[i]_address = base_address + (i-1) * data_type_size

对比两个公式,我们不难发现,从 1 开始编号,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令,在一定程度上降低了一点效率。

还有一个历史原因:C 语言设计者用 0 开始计数数组下标,之后的许多高级语言都沿用了这一习惯。

内容小结

  • 数组是最基础、最简单的数据结构。
  • 数组用一块连续的内存空间来存储相同类型的一组数据。
  • 数组最大的特点是支持随机访问,但其插入、删除操作较为低效。
  • 在平时的业务开发中,我们可以使用编程语言提供的容器类来代替数组;但是对于底层的开发,直接使用该数组更有利用提升性能和效率。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!