数组

大约 3 分钟

数组

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

  • 线性表,数据排成像一条线一样的结构,每个线性表上的数据最多只有前和后两个方向。数组,链表,队列和栈等都是线性表结构;

    非线性表,如二叉树,堆,图,数据之间并不是简单的前后关系

  • 连续的内存空间和相同类型的数据,可以随机访问,但是删除插入数据,为了保证连续性,需要大量的数据搬移导致低效

数组如何实现根据下标随机访问数组元素?

计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它会首先通过寻址公式计算出该元素存储的内存地址:

a[i]_address = base_address + i * data_type_size

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

插入操作

如果在数组的末尾插入元素,不需要移动数据,时间复杂度是O(1),如果在数组的开头插入元素,那么所有的数据都需要依次往后移动一位,最坏时间复杂度是O(n),因为在每个位置插入元素的概率是一样的,所有平均情况时间复杂度是(1+2+...+n)/n = O(n)

如果数组的数据是有序的,在某个位置插入一个新元素时,必须搬移;

如果数组中的数据是没有规律的(特定场景),为了避免大规模的数据搬移,可以直接将第k位的数据搬移到数组元素的最后,把新的元素直接放到k的位置,如

a,b,c,d,e  -> a,b,x,d,e,c

删除操作

如果删除数组末尾的数据,则最好情况时间复杂度为 O(1);如果删除开头的数据,则最坏情况时间复杂度为 O(n);平均情况时间复杂度也为 O(n)

在某些特殊场景下,并不一定非得追求数组中数据的连续性,将多次删除操作集中在一起执行提升效率,如先标记删除,当数组没有更多的空间存储数据时触发真正的删除操作(类似JVM的标记-清除)

需要注意数组越界,在Java中会抛出java.lang.ArrayIndexOutOfBoundsException

容器能否完全替代数组

ArrayList封装了很多数组的细节,支持动态扩容(1.5倍),两者比较:

  • ArrayList无法存储基本类型,需要对应的拆箱和装箱,有性能损耗,

  • 如果数据大小已知,且操作简单,用不到ArraList的大部分方法,可以直接使用数组,

  • 如果是多维数组,使用数组更直观

为什么大多数编程语言中,数组要从0开始编号,而不是1

历史原因,和

a[k]_address = base_address + k * type_size

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

从1开始编号,每次随机访问数组元素都多一次减法运算,对于CPU来说,就是多了一次减法指令