二、数组

什么是数组?

  • 数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据
  • 线性表:数据排成像一条线一样的结构(数组、链表、队列、栈)
  • 连续的内存空间和相同的数据类型:因为这两个限制,数组有“随机访问”的特性,但是让数组的很多操作变得低效,如 删除、插入,为了保证连续性,需要大量数据搬迁

image.png

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

image.png

访问数组元素的原理

如 一个长度为10 的 int 类型数组

Java:int[] a = new int[10],分配了一块连续内存空间 1000~1039(Java 的 int 的长度为 4 字节),内存首地址为 base_address=1000

Go: var a:=[10]int{1,2,3,4,5,6,7,8,9,10} ,分配一块连续内存空间 32位系统下 1000~1039,64位系统下 1000~1079 (Go 的 int 的长度是根据系统位数来决定的,32位系统是4字节,64位系统是8字节)

image.png

寻址公式:

a[i]_address = base_address + i * data_type_size

data_type_size 是int类型字节大小

低效的“插入”和“删除”

插入

有序数组

  • 往数组插入一个数据到 K 位置,为了给第 K 个位置腾出来给新数据,需要将第 K~N这部分的数据顺序的往后挪一位,时间复杂度为 O(n)
  • 如果在末尾插入元素,不需要移动数据,时间复杂度为 O(1)
  • 如果在数据开头插入元素,那所有的数据都要依次往后挪一位,所以最坏时间复杂度为O(n)
  • 因为在每个位置插入元素的概率是一样的,所以平均时间复杂度为(1+2+...+n)/n=O(n)

无序数组

  • 往数组插入一个数据到 K 位置,可以把 K 位置的数据搬到数组元素的最后,把新的元素直接放入到 K 位置
image.png

删除

删除第 K 个位置的数据,为了内存的连续性,需要搬移数据,不然中间会出现空洞,内存就不连续了

特殊场景下,并不一定非要追求数组中数据的连续性。如果将多次删除操作集中在一起执行,删除效率会提高

image.png

为了避免d,e,f,g,h被多次搬移,可以先记录下已经被删除的数据。每次的删除操作并不是真正的搬移数据,只是记录数据已经被删除。让数组没有更多的空间存储数据时,再出发执行一次真正的删除操作,这样就减少了删除操作导致的数据搬移。

数组越界

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;
}
代码运行图.png

栈图.png

运行代码会无限打印“ hello world”,因为数组大小为3,a[0],a[1],a[2],for 循环条件写错 i<=3,当i=3时,数组a[3]访问越界

在C中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的,所以,根据数组寻址公式,a[3]也会被定位到某块不属于数组的内存地址上,而这个地址正好是存储变量 i 的内存地址,那么 a[3]=0 就相当于 i=0,所以会导致代码无限循环(函数体内的局部变量存在栈上,且是连续压栈。在Linux进程的内存布局中,栈区在高地址空间,从高向低增长。变量i和arr在相邻地址,且i比arr的地址大,所以arr越界正好访问到i。当然,前提是i和arr元素同类型,否则那段代码仍是未决行为。操作系统或计算机体系结构的教材应该会讲到

数组越界在 C 语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。因为,访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序就可能不会报任何错误。

数组优化

  • 支持动态扩容
  • 事先指定数据大小

为什么大多数编程语言数组要从 0 开始编号

  • 为了性能
  • 如果从0开始编号, a[k]_address = base_address + k * type_size
  • 如果从1开始编号, a[k]_address = base_address + (k-1) * type_size

从 1 开始编号每次随机访问对CPU来说都会多一次减法运算,数组作为非常基础的数据结构,通过下标是非常基础的操作,效率的优化得做到极致,所以为了减少一次减法操作,数组选择了从0开始编号

还有一种说法是C设计用 0 开始计数数组下标,之后的语言为了降低学习成本,所以沿用了数组下标从0开始计数。

image

你可能感兴趣的:(二、数组)