- 数组是一种线性表结构,可以用一组连续的内存空间,来存储一组具有相同类型的数据。
- 关于线性表:就是数据排成像一条线一样的结构,线性表上的数据方向只有向前和向后。数组、链表、队列和栈都是线性表。非线性表:就是数据之间不是简单的前后关系。比如二叉树、堆和图。
- 关于连续的内存空间:计算机在分配内存空间的时候都会对应分配一个内存地址,连续的内存空间对应着连续的内存地址,计算机通过访问内存地址获取内存中的值。
- 关于相同类型的数据:就是数据存储所占用的内存大小是一样的。
- 随机访问:在存取第N个数据时,不需要访问前(N-1)个数据,就可直接对第N个数据进行操作。非随机访问:在存取第N个数据时,必须先访问第(N-1)个数据。因为数组有着连续的内存空间和存储着相同类型的数据,因此,数组支持随机访问。而链表则不支持随机访问。对随机访问的寻址公式是a[n]_address=base_address+n*data_type_size.其中,data_type_size指每个元素的大小,比如int类型的,data_type_size为4个字节。base_address为首地址。常见的数据类型如下表所示。
数据类型 data_type_size int 4 char 1 double 8 float 4 short 2 long 4 -
纠正一个错误说法:常常问链表和数组的区别时,很多人回答说:“链表适合插入和删除操作,时间复杂度为O(1);数组适合查找操作,时间复杂度为O(1)。这种描述是不准确的。因为数组虽然适合查找,但是时间复杂度不是O(1),即使是排好序的数组,用二分查找,其时间复杂度也只是O(logn)。所以正确的描述应为:”数组支持随机访问,根据下标进行随机访问的时间复杂度为O(1)“。
- 为什么数组的“插入、删除”操作很低效?
- 对于插入操作而言:在长度为n的数组的第k个位置插入一个数据时,为了保持线性结构,需要将第k个位置空出来,而相应的第k~n个数据需要向后腾挪一位。这样操作的最好时间复杂度为O(1),最坏时间复杂度为O(n),平均时间复杂度为O(n)。
- 对于删除操作而言:与插入操作类似,当删除第k个数据时,为了保持数组内存空间的连续性,避免出现内存空洞现象,需要将第k~n个数据依次向前腾挪一位。其最好时间复杂度为O(1),最坏时间复杂度为O(n),平均时间复杂度为O(n)。
- 综上可见,无论插入还是删除都会出现大规模的数据搬移现象。而链表就不会出现这种数据搬移现象。因此链表更适合插入删除操作。
- 在一些实际应用场景中,对于要删除的数据,并不是首先就将其从内存空间中删除,而是先将其标记,当内存满了的时候,在将这些标记删除的数据进行删除,这样就减少了数据搬移现象,提升了效率。这也就是JVM垃圾标记回收算法的原理。
- 为什么大多数编程语言中,数组的下标要从0开始,而不是从1开始?
数组随机访问的公式是a[n]_address=base_address+n*data_type_size。
当从1开始访问的公式是a[n]_address=base_address+(n-1)*data_type_size。
可以发现,下标从1开始进行访问的结果就是,每进行一次访问,就多了一次减肥计算,cpu就多了一次减法指令。因此 更优的做法就是下标从0开始。
4.二维数组的寻址方式,对于m*n的数组:a[i][j](i<m,j<n)_address=base_address+(i*n+j)*data_type_size。