链表和数组是两种截然不同的内存组织方式,正因如此,它们插入、删除、随机访问的时间复杂度正好相反。
数组使用的是连续的内存空间,可以利用空间局部性原理,借助 CPU cache进行预读,所以访问效率更高。而链表不是连续存储,无法进行缓存,随机访问效率也较低。
数组的缺点是大小固定,一经声明就要占用整块连续的内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间用于分配,就会导致“内存不足(out of memory)”。而如果声明的数组过小,当不够用时,又需要重新申请一块更大的内存,然后进行数据拷贝,非常费时。
而链表则没有大小限制,支持动态扩容。当然,因为链表中每个结点都需要存储前驱 / 后继结点的指针,所以内存消耗会翻倍。而且,对链表频繁的插入、删除操作会导致频繁的内存申请和释放,容易造成内存碎片和触发垃圾回收(Garbage Collection, GC)
数组和链表插入删除操作的时间复杂度对比:
在只知道下标时
数组:O(n) 需要移动其后所有项的位置
链表:O(n) 需要遍历其前面所有项以得到下标对应的项
在拥有要操作的项的引用时
数组:O(n) 需要移动其后所有项的位置
链表:O(1) 无需遍历
其实直接就插入删除的执行函数来看的话,链表和数组在只知道下标的情况下,其时间复杂度都是O(n),性能上是不会有太大差别的。因为数组不需要遍历,能直接取得要操作的对象,但是需要移动其后的所有项,而链表无法直接获取要操作的对象,需要遍历获取,之后直接删除或者插入,而不需要移动其他项。之所以说链表的插入和删除比数组的性能好,并不是说在任何情况下链表的插入和删除效率都要比数组的高,而是链表插入删除的最差时间复杂度也就是O(n),而在已得到要操作的结点的引用时,它就能省去遍历的步骤直接插入删除,时间复杂度为O(1),并且数组会有比如扩容等操作造成很多额外的时间支出,以及内存碎片所导致的空间支出。倘若不考虑这些,在只有下标的情况下执行插入和删除,它们的性能其实是没有太大区别的,就时间复杂度上来看都是O(n),然而实际情况下是不可能不考虑这些的。
所以我们可以简单的记住结论:
插入删除:链表性能好
查询修改:数组性能好