数组
数组是一种基本数据结构,在编程中用于存储相同数据类型的元素集合。这些元素存储在连续内存位置,这允许高效的索引和迭代。数组因其简单性和性能优势而被广泛使用。
数组的关键特性
- 固定大小(在静态数组中): 在许多编程语言中,一旦数组被声明,其大小就是固定的,并且在运行时不能更改。这在C和Java等语言中很常见,在这些语言中你需要预先知道数组的大小。
- 同构元素: 数组中的所有元素都具有相同的数据类型(例如,整数、浮点数、字符),确保了一致的行为和高效的内存分配。
- 基于索引的访问: 元素可以通过其索引直接访问,允许常数时间访问,表示为 。第一个元素的索引是0,第二个的索引是1,依此类推。
- 连续内存分配: 元素存储在连续的内存位置中,由于更好的缓存利用率而提高了性能。这意味着顺序访问元素会更快,因为它们很可能一起被加载到缓存中。
数组的常见操作
初始化数组
数组可以初始化为空或具有一组指定的初始值。在Python等语言中,列表(功能类似于动态数组)可以使用各种方法进行初始化。
# 初始化一个包含五个元素且都设置为0的列表
arr = [0] * 5 # arr = [0, 0, 0, 0, 0]
# 使用预定义值初始化一个列表
nums = [1, 3, 2, 5, 4]
在C等语言中:
// 使用预定义值初始化一个数组
int nums[] = {1, 3, 2, 5, 4};
// 初始化一个固定大小的数组
int arr[5]; // 元素未初始化
访问元素
访问数组中的任何元素都是一个常数时间操作,。元素的内存地址可以使用基地址和元素的索引计算:
Python示例:
def random_access(nums):
import random
random_index = random.randint(0, len(nums) - 1)
return nums[random_index]
插入元素
- 静态数组: 在使用静态数组的语言中,你不能在运行时增加数组的大小。要插入一个元素,你可能需要移动后续元素以腾出空间,这 具有 的时间复杂度。
- 动态数组(例如 Python 列表、Java ArrayLists): 你可以使用内置方法插入元素。然而,在非末尾位置插入需要移动元素,导致时间复杂度为 。
def insert(nums, num, index):
nums.insert(index, num) # 在指定索引处插入 num
# 由于元素移动,时间复杂度为 O(n)
删除元素
删除一个元素涉及移动后续元素以填补空缺,这具有 的时间复杂度。
def remove(nums, index):
nums.pop(index) # 移除指定索引处的元素
# 由于元素移动,时间复杂度为 O(n)
遍历数组
遍历数组涉及顺序访问每个元素,通常具有 的时间复杂度。
def traverse(nums):
for num in nums:
print(num)
查找元素
- 线性搜索: 如果数组是未排序的,你需要执行线性搜索,逐个检查每个元素,这具有 的时间复杂度。
- 二分搜索: 如果数组是已排序的,你可以使用二分搜索通过重复将搜索区间一分为二,在 时间内查找元素。
def linear_search(nums, target):
for i, num in enumerate(nums):
if num == target:
return i
return -1
def binary_search(nums, target):
low, high = 0, len(nums) - 1
while low <= high:
mid = (low + high) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
扩展数组
- 静态数组: 由于大小是固定的,扩展需要创建一个更大的新数组并复制元素,这是一个 操作。
- 动态数组: Python等语言会自动处理大小调整。当数组达到其容量时,会分配一个新的更大的数组,并复制元素。这种大小调整操作具有 的时间复杂度,但由于摊销,每次插入的平均时间仍为 。
def extend(nums, enlarge):
nums.extend([0] * enlarge) # 在末尾添加 'enlarge' 个零
# 时间复杂度为 O(k),其中 k 是添加的元素数量
优点与缺点
优点
- 常数时间访问: 直接基于索引的访问允许在 时间内检索和修改。
- 缓存效率: 连续内存分配提高了缓存性能,由于空间局部性加快了数据访问速度。
- 内存效率: 与链表等更复杂的数据结构相比,数组的开销最小。
缺点
- 静态数组中的固定大小: 大小必须在编译时已知。如果需要更多空间,你必须创建一个新数组并复制数据,这效率低下。
- 插入和删除效率低下: 插入或删除元素(末尾除外)需要移动元素,导致时间复杂度为 。
- 内存浪费: 过度估计所需大小会导致未使用的内存分配,而低估则需要重新调整大小,成本很高。