1.数组是如何实现随机访问的?
数组的概念: 数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
那么什么是线性表呢?
顾名思义,线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。
而与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。
数组如何实现根据下标随机访问?
我们拿一个长度为 10 的 int 类型的数组 int[] a = new int[10]来举例。在我画的这个图中,计算机给数组 a[10],分配了一块连续内存空间 1000~1039,其中,内存块的首地址为 base_address = 1000。
我们知道,计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素存储的内存地址:
a[i]_address = base_address + i * data_type_size
数组与链表的区别?
数组支持随机访问,根据下标随机访问的时间复杂度为O(1)
2.低效的插入和删除
每次插入操作都会移动元素,平均时间复杂度为O(n)
存在一个简单的方法,若数组本身无序, 如果要将某个数据插入到第 k 个位置,为了避免大规模的数据搬移,我们还有一个简单的办法就是,直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位置。如下图
删除操作也会移动元素,平均时间复杂度也为O(n)
存在一个改进的方法,将多次删除操作集中在一起执行,先记录下已经删除的数据, 每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。
3.容器与数组
容器指的是java中的ArrayList,或是C++STL中的vector
ArrayList 最大的优势就是可以将很多数组操作的细节封装起来。比如前面提到的数组插入、删除数据时需要搬移其他数据等。另外,它还有一个优势,就是支持动态扩容(每次存储空间不够时,它都会将空间自动扩容为1.5倍大小)。虽然动态扩容很厉害,但我们还是建议事先指定一个合理的数据大小,避免底层多次数据搬运操作
什么时候用数组更好?
1.Java ArrayList 无法存储基本类型,比如 int、long,需要封装为 Integer、Long 类,而 Autoboxing、Unboxing 则有一定的性能消耗,所以如果特别关注性能,或者希望使用基本类型,就可以选用数组。
2. 如果数据大小事先已知,并且对数据的操作非常简单,用不到 ArrayList 提供的大部分方法,也可以直接使用数组。
3. 当要表示多维数组时,用数组往往会更加直观。比如 Object[][] array;而用容器的话则需要这样定义:ArrayList > array。
业务开发,直接容器就够了,底层开发,性能的优化需要做到极致,那么数组当之无愧
4.为什么数组要从0开始?
理由一:0更快
若采用1开始,那么随机访问公式就变为
a[k]_address = base_address + (k-1)*type_size
每次访问都会多计算一次减法
理由二:习惯了
4.思考题
二维数组的随机访问公式
address = base_address + ( i * n + j) * type_size
参考课程:极客时间王争老师的《数据结构与算法之美》