Array 数组
数组(Array)是一种线性表数据结构.它用一组连续的内存空间,来存储一组具有相同类型的数据.
- 线性表数据结构:素之间是简单的线性关系, 每个元素至多有一个前继元素, 至多有一个后继元素(首位和末位只是单向的).像线段一样, 可以从入口或出口无限增减元素的数据结构.
包括: 数组,链表、队列、栈等 - 连续的内存空间, 保证数组的数据都连续的分布在物理介质上
- 存储的数据具有相同类型, 保证了每个数据元素占用的物理空间大小都是相等的
特性
- 内存连续和类型相同的限制使得数组拥有特性: 随机访问, 按下标随机访问数组元素:
一维数组: a[i]_address = base_address + i * data_type_size
二维m * n数组: a[i][j] = base_address + (i * n + j) * data_type_size - 假设要访问第3个元素, 通过公式可以直接得到内存地址:base_address+3*data_type_size
- 为了保持数组随机访问的特性, 插入和删除要付出很大的代价维护数组.
插入: 在末位插入,不需要移动数据, 是O(1), 但首位需要将所有元素都后移一位, 就是O(n).
因为在每个位置插入的概率是一样的: 1/n + 2/n + 3/n+ … n/n = (n+1)/2, 平均时间复杂度是O(n)
针对元素乱序的数组, 可以通过将指定位置元素复制到末位, 然后将插入元素放到指定位置, 数组本身是乱序的, 不会受太大影响, 操作的时间复杂度降到O(1)
删除: 掉首位元素后, 剩余的元素都要依次前进一个位置, 同插入一样也是O(n)
优化的方法: 记录每次要删除元素,标记为删除状态,达到数组存储上限执行真正的删除 - 数组简单易用,在实现上使用的是连续的内存空间,可以借助 CPU 的缓存机制,预读数组中的数据,所以访问效率更高.
而链表在内存中并不是连续存储,所以对 CPU 缓存不友好,没办法有效预读 - 缺点是大小固定,一经声明就要占用整块连续内存空间.如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致“内存不足(out of memory)”.如果声明的数组过小,则可能出现不够用的情况.这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常费时
Note
- 数组自身没有查找, 其实是按下标访问数组元素, 查找的时间复杂度指的是基于数组结构的查找算法的复杂度, 比如二分查找算法的时间复杂度是O(logn)
- c语言里下面代码会一直打印: 函数体内的局部变量会连续压栈, 像数组一样连续的存在内存空间上,
比如…|i|arr[2]|arr[1]|arr[0]|…, 执行arr[3]=0时发生了数组溢出, arr[3]实际是i所在的位置, 相当于执行i=0.
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;
}
- 为什么数组的索引从0开始
可能有历史原因, 但个人倾向于是因为便于计算物理位置.
索引从0开始: a[i]_address = base_address + i * data_type_size
索引从1开始: a[i]_address = base_address + (i - 1) * data_type_size
寻址公式是基础中的基础, 执行的频率肯定也很高. 多出的一步计算消耗, 完全可以通过程序猿的条件设置抵消掉.