1. 数组基础
数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
第一是线性表:线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链接、队列、栈也是线性表结构。而与线性表对立的是非线性表结构,比如二叉树、堆、图等。在非线性表中,数据之间并不是简单的前后关系。
第二是连续的内存空间和相同类型的数据,因此数组具有“随机访问”的特性。
2. 数组元素的查询
数组如何实现根据下标随机访问数组元素
举个栗子
计算机给数组a[10]分配了一块内存连续空间1000-1039。内存块的首地址为base_address = 1000。
计算机会给每个内存单元分配一个地址,通过地址访问内存中的数据。
当计算机随机访问数组中的某个元素时,会首先通过寻址公式计算出该元素的内存地址:a[i]_address = base_address + i * data_type_size。data_type_size表示数组中每个元素的大小。比如数组中存储的数据类型为int,那么data_type_size = 4。
数组支持随机访问,并且根据数组下标随机访问的时间复杂度是O(1)。
3. 数组元素的插入和删除
如果数组中的数据是有序的:
数据插入/ 删除的最好时间复杂度为O(1)(在数组的末尾插入元素)。
数据插入/ 删除的最坏时间复杂度为O(n)(在数组的开头插入元素)。
数据插入/ 删除的平均情况时间复杂度为O(1+2+3+4+…+n)/n = O(n)。
如果数组中的数据是无序的:
进行数据插入时,可以直接将第k位的数据搬到最后一位,而要插入到第k位的数据直接插入。此时插入一个元素的时间复杂度就是O(1)。
进行多个数据一起删除时,每次的删除操作并不是真正的搬移数据,只是记录数据已经被删除,当数组没有更多的空间存储数据时,再触发一次真正的删除操作。
3. 数组下标
为什么数组下标从0开始编号不是从1开始呢?
从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移”。如果用 a 来表示数组的首地址,a[0] 就是偏移为 0 的位置,也就是首地址,a[k]表示偏移k * data_type_size的位置,
a[k]_address = base_address + k * data_type_size。
如果数组从1开始计数,寻址公式将会变为:
a[k]_address = base_address + (k-1) * data_type_size。
如果数组从1开始计数,每次随机访问数组元素都会多一次减法运算,对于CPU而言,就会多一次减法指令。
此外:
C 语言设计者用 0 开始计数数组下标,之后的 Java、JavaScript 等高级语言都效仿了 C 语 言,或者说,为了在一定程度上减少 C 语言程序员学习 Java 的学习成本,因此继续沿用了 从 0 开始计数的习惯。实际上,很多语言中数组也并不是从 0 开始计数的,比如 Matlab。甚至还有一些语言支持负数下标,比如 Python。