当前位置: 首页 > news >正文

NumPy 创建空数组并逐个添加元素的深度解析

在科学计算和数据分析中,NumPy 数组的高效操作是核心需求之一。本文将深入探讨如何在 NumPy 中创建空数组并逐个添加元素的各种方法,分析其性能差异,并提供最佳实践建议。

空数组的初始化

NumPy 提供了多种初始化空数组的方式,它们在内存分配和行为上略有不同。从技术上讲,这些方法创建的都是形状为 0 的数组:

import numpy as np# 三种初始化空数组的方法
arr1 = np.array([])       # 显式空数组,默认float64类型
arr2 = np.empty((0,))     # 未初始化的空数组
arr3 = np.zeros((0,))     # 初始化为0的空数组

数学上,这些空数组可以表示为 A=∅A = \emptysetA=,其中 AAA 的维度为 dim(A)=1dim(A)=1dim(A)=1,形状为 shape(A)=(0,)shape(A)=(0,)shape(A)=(0,)。虽然这些数组在内存中占用很少空间,但它们为后续操作提供了正确的数组结构。

逐个添加元素的实现方法

方法1:使用 np.append()

arr = np.array([])
for i in range(3):arr = np.append(arr, i)

这种方法虽然直观,但存在严重的性能问题。每次调用 np.append() 都会创建一个新数组,时间复杂度为 O(n2)O(n^2)O(n2)。对于 nnn 次添加操作,总时间复杂度为:

T(n)=∑i=1ni=n(n+1)2=O(n2) T(n) = \sum_{i=1}^{n} i = \frac{n(n+1)}{2} = O(n^2) T(n)=i=1ni=2n(n+1)=O(n2)

方法2:预分配数组

n = 3
arr = np.empty(n)  # 预分配空间
for i in range(n):arr[i] = i

这种方法的时间复杂度为 O(n)O(n)O(n),因为每个赋值操作都是 O(1)O(1)O(1)。预分配的关键在于提前知道数组的最终大小 nnn。数学上,这相当于先定义 A∈RnA \in \mathbb{R}^nARn,然后逐个填充元素 aia_iai

方法3:列表转换法

temp_list = []
for i in range(3):temp_list.append(i)
arr = np.array(temp_list)

这是最推荐的方法,因为 Python 列表的 append() 操作摊销时间复杂度为 O(1)O(1)O(1),最后的转换操作为 O(n)O(n)O(n),总体保持 O(n)O(n)O(n) 的时间复杂度。

多维数组的处理

对于多维数组,特别是需要动态添加行或列的情况,NumPy 提供了专门的函数:

动态添加行

arr = np.empty((0, 2))  # 初始为0行2列
for i in range(2):new_row = np.array([[i, i*2]])arr = np.vstack((arr, new_row))

vstack 操作的时间复杂度也是 O(m)O(m)O(m),其中 mmm 是当前行数。对于 nnn 次添加,总时间复杂度为 O(n2)O(n^2)O(n2),类似于一维情况的 np.append()

预分配多维数组

rows, cols = 2, 2
arr = np.empty((rows, cols))
for i in range(rows):arr[i, :] = [i, i+1]

这种方法的时间复杂度为 O(n)O(n)O(n),其中 n=rows×colsn=rows \times colsn=rows×cols

性能对比与分析

我们通过实验来验证不同方法的性能差异:

import timedef test_append(n):arr = np.array([])start = time.time()for i in range(n):arr = np.append(arr, i)return time.time() - startdef test_list(n):temp_list = []start = time.time()for i in range(n):temp_list.append(i)arr = np.array(temp_list)return time.time() - startn = 10000
t1 = test_append(n)
t2 = test_list(n)
print(f"np.append() 耗时: {t1:.4f}秒")
print(f"列表转换 耗时: {t2:.4f}秒")

实验结果通常显示,列表转换法比 np.append() 快 1-2 个数量级。这是因为:

  1. np.append() 每次都需要:

    • 分配新内存 Mnew∈Ri+1M_{new} \in \mathbb{R}^{i+1}MnewRi+1
    • 复制旧数据 Aold∈RiA_{old} \in \mathbb{R}^iAoldRiMnewM_{new}Mnew
    • 添加新元素 ai+1a_{i+1}ai+1
  2. 列表的 append() 使用动态数组实现,分摊时间复杂度为 O(1)O(1)O(1)

数学上,设每次内存分配和复制的成本为 ccc,则 np.append() 的总成本为:

Tappend(n)=c⋅∑i=1ni=c⋅n(n+1)2 T_{append}(n) = c \cdot \sum_{i=1}^{n} i = c \cdot \frac{n(n+1)}{2} Tappend(n)=ci=1ni=c2n(n+1)

而列表转换法的总成本为:

Tlist(n)=c1⋅n+c2⋅n T_{list}(n) = c_1 \cdot n + c_2 \cdot n Tlist(n)=c1n+c2n

其中 c1c_1c1 是列表追加的成本,c2c_2c2 是最终转换的成本。

最佳实践建议

基于上述分析,我们得出以下建议:

  1. 已知最终大小时:使用预分配方法

    n = 1000
    arr = np.empty(n)
    for i in range(n):arr[i] = compute_value(i)  # 假设的计算函数
    
  2. 未知大小时:使用列表收集法

    temp_list = []
    while condition:  # 某种条件value = get_next_value()  # 获取下一个值temp_list.append(value)
    arr = np.array(temp_list)
    
  3. 多维数组时

    • 如果能预估大小,预分配
      rows, cols = 100, 50
      arr = np.empty((rows, cols))
      for i in range(rows):arr[i, :] = compute_row(i)
      
    • 如果不能预估,考虑使用列表的列表
      temp_list = []
      while condition:row = compute_next_row()temp_list.append(row)
      arr = np.array(temp_list)
      
  4. 避免频繁使用

    • np.append()
    • np.vstack()
    • np.hstack()
    • np.concatenate()

高级技巧:预分配与内存视图

对于性能要求极高的场景,可以考虑使用内存视图来填充数组:

n = 100000
arr = np.empty(n)
view = arr[:]  # 创建视图
for i in range(n):view[i] = i  # 通过视图操作

这种方法避免了每次索引时的边界检查,可以略微提升性能。数学上,这相当于直接操作内存空间 MMM 而不经过额外的安全检查。

总结

NumPy 数组的设计初衷是固定大小的数值计算,其性能优势在于连续内存布局和向量化操作。当需要动态添加元素时:

  1. 列表转换法是通用且高效的选择
  2. 预分配方法在已知大小时最优
  3. 避免频繁使用连接操作(append/stack等)

理解这些方法背后的时间复杂度 OOO 和内存管理机制,可以帮助我们在实际应用中做出更好的选择。记住,在科学计算中,预先分配往往比动态增长更符合 NumPy 的设计哲学。

http://www.dtcms.com/a/328451.html

相关文章:

  • java理解
  • 解决微前端子应用嵌入后样式被覆盖
  • 深度解析 AS32S601 芯片 CAN Bus Off 机制:从原理到应用的全流程指南
  • 浏览器CEFSharp+X86+win7 之 全球外贸电商平台订单管理(十)
  • 前后端分离项目中Spring MVC的请求执行流程
  • uni-app实战教程 从0到1开发 画图软件 (学会画图)
  • Ceph BlueStore存储引擎详解
  • 【数据结构】并查集:从入门到精通
  • 《Linux基础知识-1》
  • docker-compose搭建 redis 集群
  • 阿里巴巴开源多模态大模型-Qwen-VL系列论文精读(一)
  • VBS 时间函数
  • 基于 libwebsockets 库实现的 WebSocket 服务器类
  • Shader warning in ‘Universal Render Pipeline/Particles/Simple Lit‘
  • provide()函数和inject()函数
  • 【UEFI系列】Super IO
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-语音评测
  • 嵌入式学习(day25)文件IO:open read/write close
  • VGG改进(2):基于Local Attention的模型优化
  • 书籍数组中未出现的最小正整数(8)0812
  • 《飞算JavaAI:新一代智能编码引擎,革新Java研发范式》
  • 跑腿平台开发实战:同城O2O系统源码的模块化与可扩展性方案
  • 每日一练:将一个数字表示成幂的和的方案数;动态规划、深度优先搜索
  • 【Altium designer】快速建立原理图工程的步骤
  • 2025开放计算技术大会|开源开放推动系统创新 加速AIDC全球协作
  • 过拟合、欠拟合与方差/偏差的关系
  • Langchain结合deepseek:框架+模型的AI测试实践
  • 小白学习pid环控制-实现篇
  • 杰里平台7083G 如何支持4M flash
  • 【oracle闪回查询】记录字段短时间被修改的记录