定义
数组是一种线性表数据结构,它使用连续的内存空间,存储相同类型的数据。
算法
随机访问
设随机访问下标为i,数组的首地址为base,数组每个数据单元占用字节为k,那么有
//数组下标从0开始
addr = base + i * k
- 时间复杂度:O(1)
- 空间复杂度:O(1)
查找元素
在数组中查找一个元素,需要从头开始遍历
func find(nums int[], target int) int {
for i:=0; i<len(nums); i++ {
if nums[i] == target {
return i
}
}
return -1
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
插入元素
(数组空间足够)往数组插入一个元素,需要搬迁数据
func insert(nums int[], int ti, element int) {
for i:=len(nums); i>ti; i-- {
nums[i] = nums[i-1]
}
nums[ti] = element
//len(nums) += 1
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除元素
func remove(nums int[], int di) {
for i:=di; i<len(nums); i++ {
nums[i] = nums[i+1]
}
//len(nums) -= 1
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
其他算法
作用在数组上的算法还有很多,比较典型的当属排序和二分查找了,后续独立文章讲解。
优点和缺点
优点
空间连续,所以有以下两个优点
- 支持随机访问
- 可以配合CPU预读特性实现更高的访问性能
缺点
- 插入/删除慢,需要搬迁数据
- 空间大小固定,需要手动扩容
适用场景
以数组的优缺点为指导思想,具体场景具体分析。
拓展问题
为什么很多编程语言数组的下标从0开始
假设数组下标从1开始,那么随机访问地址的计算公式为
//数组下标从1开始
addr = base + (i-1) * k
相比于从0开始,每次要多计算一次i-1;换句话说,下标从0开始,运算指令更少,可以提高随机访问的性能。
往数组插入元素时,如果空间不足怎么办?
数组申请时即确定了长度,往一个已经被完全使用的数组中插入元素,需要手动扩容,否则会发生数组越界问题或者数据丢失(看具体实现)。作为学习,我们可以对数组进行封装,使其支持自动扩容;实际开发中,我们可以使用现成的库,很多编程语言都提供了支持自动扩容的数组,比如Java的ArrayList。