数组
是什么?
数组是用一组连续
的内存空间,来存储一组具有相同类型
的数据的线性表
优缺点:
优点:
随机访问速度快,时间复杂度是O(1)
缺点:
-
删除、插入速度慢,因为需要从前往后遍历到指定位置,时间复杂度是O(n)
-
需要事先设置内存空间大小
因为数组的空间在编译阶段
就需要进行确定(在运行阶段是不允许改变的)
所以在使用前需要提前申请所占内存的大小,如果提前不知道需要的空间大小,预先申请就可能会浪费内存空间
,即数组的空间利用率较低
。
插入时间复杂度分析(删除和插入一样)
-
末尾插入元素,那就不需要移动数据了,这时的时间复杂度为
O(1)
-
在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以
最坏时间复杂度是 O(n)
。 -
因为我们在每个位置插入元素的概率是一样的,所以
平均时间复杂度
为(1+2+...n)/n=O(n)
。
为什么数组从 0 开始编号?
首先说结论:为了极致的性能优化
下面进行分析:
从数组存储的内存模型上来看,“下标”
最确切的定义应该是“偏移
”。a[0]
就是偏移为 0
的位置,也就是首地址,a[k]
就表示偏移 k
的位置
对比两个公式,我们不难发现,从 1 开始编号,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。
数组作为非常基础的数据结构,通过下标随机访问数组元素又是其非常基础的编程操作,效率的优化就要尽可能做到极致。所以为了减少一次减法操作,数组选择了从 0 开始编号,而不是从 1 开始。
链表
是什么?
链表是可以用一组非连续
的内存空间,来存储一组具有相同类型
数据的线性表
。
数据元素的逻辑顺序是通过链表中的指针链接
实现的。
链表由一系列结点组成,结点可以在运行时动态生成。每个结点包括两个部分:
- 一个是存储数据元素的
数据域
, - 另一个是存储下一个结点地址的
指针域
。
数组和链表的对比
内存分配
-
数组的大小固定,一经声明就要占用整块连续内存空间。
-
链表本身没有大小的限制,但并不是无限的
2)连续
-
数组是用一块连续的内存空间,来存储相同类型的一组数据。
随机访问快,增删慢
可以借助 CPU 的缓存机制+空间局部性原理,预读数组中的数据,所以访问效率更高。 -
链表在内存中并不是连续存储
访问慢,增删快
对 CPU 缓存不友好,没办法有效预读。