0%

为什么很多编程语言中数组从0开始编号

为什么很多编程语言中数组从0开始编号?而不是从1呢? 从1开始不是更符合人类的思维习惯吗?

什么是数组?

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

线性表

顾名思义,线性表就是数据排成一条线一样的结构,每个线性表最多只有两个方向。

除了数组,链表、队列、栈也是线性表结构

image

非线性表

比如二叉树、堆、图等。之所以是非线性,因为里面数据之间并不是简单的前后关系
image

连续的内存空间和相同类型的数据

这两个特性给了数组一个杀手锏的特性:随机访问

有利有弊,这两个限制让数组很多操作非常低效
比如在数组添加或删除一个数据,为了保证连续性,需要大量的数据搬移工作。

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

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

根据寻址公式:

1
a[i]_address = base_address + i * data_type_size

比如C语言, 且此时为int类型。此时data_type_size为4个字节。

纠正错误

链表适合插入、删除、时间复杂度为O(1),数组适合查找,查找复杂度为O(1)

正确表达是:
数组支持随机访问,根据下表随机访问的时间复杂度为O(1)

低效的插入和删除

如果在数组的开头插入数据,此时时间复杂度为O(n)
如果在数组的尾部插入数据,此时时间复杂度为O(1)

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

如果说数组有序,我们在某个位置插入一个新的元素,就必须按照上面的方法搬移k之后的数据。

如果数组中存储的数据并没任何规律,此时要将某个数组插入到第k个位置,为了避免大规模的数据搬移,还有个简单的办法是,直接将第k位的数据半到数组元素的最后,把新的元素直接放入第k个位置。
此时在第k个位置插入一个元素的时间复杂度为O(1)

image

删除操作

与添加操作类似

警惕数组访问越界

容器是否能完全替代数组

ArrayList可以将数组操作的细节封装起来,且支持动态扩容

大部分方法的话,比如申请了一个大小为10的数组,当第11个数据需要存储到数组中,我们就重新分配一块更大的空间,将原来的数据复制过去,再将新的数据插入。

但扩容操作涉及内存申请和数据搬移,是比较耗时的,所以最好实现确定需要存储的数据大小。

总结一下:业务开发用容器省时省力,会损失一些性能。但完全不会影响到系统整体的性能。
若是开发底层的,比如并发网络框架,性能的优化就必须要做到极致。

解答开篇

数组存储的内存模型上,”下标”k 就是”偏移(offset)”

寻址公式:

1
a[k]_address = base_address + k * type_size

但要是按照从1开始计数:

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

此时多了一条减法指令, CPU就多执行一次减法运算. 所以最好从0开始编号.