数组

112 阅读3分钟

数组是一种线性表数据结构,使用一片连续内存来存储相同数据类型的数据。

一、数组的访问

数组是可以直接用下标来访问元素的,那么它是如何做到的?

当我们定义一个长度为10的int类型的数组时,第一个元素的地址是base_address = 1000,所以当我们直接用下标访问元素时,利用这个公式:

a[i]_address = base_address + i * data_type_size

i为下标,也意味着偏移量,i的值为0,1,2......。

那么为什么不直接使用1,2,3......呢?

  • 如果使用123的话,上面的公式就会变成

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

那么每次访问数组元素,CPU都要多进行一步减法操作。

  • 因为C语言使用的是从0开始的,所以后面很多变成语言都从0开始,所以是历史问题。

访问元素的时间复杂度为O(1)。

二、元素的添加

对于元素的添加,如果我们要插入的位置刚好时数组的末尾,那么时间复杂度就为O(1),如果要插入的位置为数组首部,那么时间复杂度为O(n),因为数组整个要向后移动一位。所以最好时间复杂度为O(1),最坏时间复杂度为O(n)。每个位置上被插入的概率是1/n,平均时间复杂度为(1 + 2 + 3 + 4 + ...... + n)/n,所以平均时间复杂度为O(n)。

上面的非尾部插入情况是针对数组是有序的情况,如果是无序数组,那么我们可以将要插入位置的元素直接放到数组的尾部,再加入要插入的元素,此时的时间复杂度就为O(1)了。

三、元素的删除

对于元素的删除:

  • 如果删除的是数组尾部的元素,那么直接删除就可,时间复杂度为O(1)。
  • 如果删除的数组非尾部的元素,那么在删除完之后,后面的元素要整体向前移动,填补空缺,此时的时间复杂度为O(n)。

所以最好时间复杂度为O(1),最坏时间复杂度为O(n),平均时间复杂度为O(n)(查考加数组)。

如果删除的是非尾部元素,那么每一次的删除操作都要导致后续元素的移动,在一些不那么要求数组的连续性情况下,我们可以将每次要删除的元素进行记录,之后一次性删除,这样大大减少了数据搬移次数。(标记清除机制的思想

四、数组的访问越界

例子:

int main(int argc, char* argv[]){
    int i = 0;
    int arr[3] = {0};
    for(; i<=3; i++){
        arr[i] = 0;
        printf("hello world\n")
    }
    return 0;
}

在C语言中,这段代码会一直打印hello world,而js不会。

这个问题和编译器分配的内存有关,上面的数组长度是3,占6个字节,变量arr占两个字节,一共8个字节,刚好能满足8字节对齐,所以i就跟在arr之后了,当访问到arr[3]时,就访问到了i的地址,这个地址不受限,可以自由访问。