数组的下标,为什么从0开始?

本文是学习算法的笔记,《数据结构与算法之美》,极客时间的课程

为什么数组的下标是从0开始,而不是从1开始呢?从1开始不是更符合人们的习惯么。

这个问题,稍后回答,先聊聊数组的基本特性。

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

线性表(Linear List),每个线性表上最多有前后两个方向。 数组、队列、链表、栈都是线性表结构

非线性表,如二叉树、堆、图等。在非线性表中,数据不是简单的前后关系。

连续的内存空间和相同的数据结构,使其可以“随机访问”,弊端也很明显,某些操作十分低效。在数组中删除或插入数据时,要做大量的数据搬移工作。

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

以一个长度为10的int型数组为例 int[] a = new int[10]
计算机给a[10]分配了一块连续的内存空间1000~1039,内存块的首地址 base_address = 1000
在这里插入图片描述
根据下标来访问数组元素,计算该元素的内存地址:用这么一个公式
a[i]_address = base_address + i*data_type_size

本例中数组中的元素为int型,data_type_size 大小就是4字节。这个公式很好理解。

数组支持随机访问,根据下标随机访问的时间复杂度为O(1)

扫描二维码关注公众号,回复: 4168939 查看本文章

数组插入操作:假设一个长度为n的数组,如果将一个数据插入到数组中第k个位置,需要把原数组第k~n这部分的元素往后移一个位置。

操作的时间复杂度为O(n)。分析:假设数组长度为n,在数组头部插入数据,需要把n个数据都往后移动一位,即最坏情况时间复杂度为O(n),在数组尾部插入一个数据,不需要移动,直接插入即可,即最好情况时间复杂度为o(1)。平均情况时间复杂度如何呢?每个位置插入的概率是一样的,都为1/n ,加权平均数为 (n + (n-1) + ……+1)*1/n 去掉系数,复杂度o(n)

如果数组里的数据是有序的,那么在第k个位置插入数据时,就需要把k~n的元素全部往后移一位,时间复杂度为o(n)。如果数组仅作用于存储数据,不关注顺序,那么在第k个位置插入一个数据时,可以先把这个数据放到数组末尾,再把要插入的数据放到k位置上。此时时间复杂度为o(1)。

再看数组的删除操作。删除数组中的某个数据,保证内存空间是连续的,就需要将后面的数据移动。和插入类似,如果是在数组末尾删除数据,得最好情况时间复杂度,是O(1),如果在数组头部删除数据,得最坏情况时间复杂度,是O(n)。平均情况时间复杂度为o(n)。

实际上,在某些特殊的场景,为了提高效率,将多次删除操作放在一起执行。看下面的例子,数组a[10]中,存了8个元素,:a,b,c,d,e,f,g,h。现在,我们要依次删除 a,b,c,为了避免三次移动 d,e,f,g,h,我们并不真正删除数据,只是记录被删除的数据。当数组的空间不够用的时候,才会触发真正的删除。这样减少了大量的因删除数据所造成的数据移动。
在这里插入图片描述
ArrayList 底层实现,有用到数组,当数组空间不足里,会自动扩容到原来的1.5倍。这时就会有大量的数据移动操作。如果,我们要从数据库里,取出1000个数据,放到ArrayList中。那我们在创建它的时候,就指定了数据大小,就可以减少大量扩容和数据移动的性能消耗。

现在回答开篇的那个问题,数组为什么下标是从0开始?

从数组中存储的数据模型来看,下标最精确的意思是”偏移量“,a[0]的偏移量是0,即为首地址。a[i]的偏移量是i,寻址公式就是a[i]_address = base_address + i*data_type_size

如果下标从1开始,那对应的寻址公式a[i]_address = base_address + (i-1)*data_type_size
对CPU来说,每次随机访问,就多了一次运算,多发一条指令。

上面的解析,算不上压倒性的证明。当初C语言的设计者用0开始计数数组下标,之后java、javaScript等高级语言都仿效了C语言,这也减少了C语言程序员学习java的成本。

猜你喜欢

转载自blog.csdn.net/every__day/article/details/83114080