时间复杂度和空间复杂度 part2
一,空间复杂度
空间复杂度是衡量一个算法在执行过程中所需内存空间的量度。它反映了算法随着输入数据规模(通常是 nn)的增加,所消耗的内存量如何变化。空间复杂度是分析算法效率的一个重要方面,尤其是在内存资源有限的环境中,如嵌入式系统、移动设备等。
空间复杂度的定义
空间复杂度指的是算法在执行过程中所使用的额外空间,包括以下几类空间:
- 输入数据空间:这是存储输入数据所占的空间,通常在空间复杂度的计算中不考虑,假设输入数据已经提供。
- 辅助空间:即算法在执行过程中需要额外申请的空间(除了输入数据外),包括:
- 临时变量
- 数据结构(如数组、栈、队列等)
- 递归调用栈空间
常见的空间复杂度
空间复杂度通常以大 O 表示法来表示,和时间复杂度一样,用来描述算法所需要的内存空间随着输入数据规模的变化趋势。以下是一些常见的空间复杂度:
-
O(1):常数空间复杂度,表示算法只使用固定的内存量,与输入数据的大小无关。举个例子,简单的数值计算、交换两个数等操作通常是 O(1) 空间复杂度。
-
O(n):线性空间复杂度,表示算法的空间需求与输入数据的大小成线性关系。例如,复制一个数组或创建一个与输入数据大小相同的辅助数据结构(如数组或链表),通常需要 O(n) 的空间。
-
O(n²):平方空间复杂度,表示算法的空间需求与输入数据的平方成正比。例如,某些矩阵操作(如二维数组的存储)可能需要 O(n²) 的空间。
-
O(log n):对数空间复杂度,表示算法的空间需求与输入数据的对数成正比。例如,递归算法中,如果递归的深度为对数级别,那么它的空间复杂度通常为 O(log n)。
空间复杂度的影响因素
空间复杂度不仅仅取决于输入数据的大小,还与算法内部使用的数据结构、存储方式、递归的深度等因素有关。以下是影响空间复杂度的一些因素:
- 数据结构的使用:不同的数据结构占用的内存空间不同。例如,使用链表存储数据通常比数组占用更多的空间,因为链表需要额外存储指针,而数组只存储数据。
- 递归深度:递归算法会占用栈空间,递归的深度越大,空间复杂度也越大。例如,递归树的深度与输入数据的规模相关时,可能会导致 O(n) 或 O(log n) 的空间复杂度。
- 辅助数据存储:一些算法需要临时存储中间结果,这也会影响空间复杂度。例如,归并排序需要额外的数组来存储合并的中间结果,空间复杂度是 O(n)。
空间复杂度的计算方法
计算空间复杂度时,我们通常关心算法在执行过程中使用的额外空间,而不是输入数据所占用的空间。以下是几种常见的空间使用情形:
-
常数空间(O(1)):如果算法使用的空间数量不依赖于输入数据的大小。例如,算法只使用几个临时变量来进行计算。
示例:
def sum_array(arr): total = 0 # 常数空间
-
for num in arr: total += num return total
-
线性空间(O(n)):如果算法使用的数据结构的大小随着输入数据规模的增大而增大。例如,创建一个新的数组来存储输入数据的副本。
示例:
def copy_array(arr): new_arr = arr[:] # 新数组,空间复杂度为O(n) return new_arr
-
递归空间(O(n) 或 O(log n)):递归算法通常会占用栈空间,递归深度越大,空间复杂度越大。例如,深度为 n 的递归算法会消耗 O(n) 空间,而深度为 log n 的递归算法会消耗 O(log n) 空间。
示例:
def factorial(n): if n == 1: return 1 return n * factorial(n - 1) # 空间复杂度是O(n),因为递归深度为n
总结
空间复杂度是衡量算法执行过程中所需内存空间的量度。它关注的是算法在执行期间占用的额外内存(除了输入数据所占空间外)。对于任何一个算法,我们都应该关注其空间复杂度,尤其是在内存资源有限的环境中。常见的空间复杂度包括 O(1)、O(n)、O(n²) 等,不同的算法和数据结构会导致不同的空间复杂度,设计高效的算法时,需要权衡时间和空间的消耗。
动态扩容
动态数组的扩容是动态数据结构(如 ArrayList
、Vector
等)在其容量不足时自动增加空间以容纳更多元素的过程。与静态数组不同,动态数组的大小不是固定的,而是可以根据需要动态调整。在实际操作中,动态数组的扩容过程通常是通过重新分配更大的内存空间并将旧数据复制到新位置来实现的。
动态数组的基本操作
动态数组的核心特性是能够在不预先知道元素数量的情况下,动态调整数组的大小。大多数情况下,动态数组支持以下操作:
- 插入元素:动态数组可以随时向其尾部添加元素。
- 查找元素:可以根据索引快速访问数组中的元素。
- 删除元素:可以删除特定元素,虽然删除操作通常会涉及元素的位移。
- 扩容:当数组的大小不够时,动态数组会自动扩展其存储空间。
扩容的原理
动态数组扩容的目的是为了解决数组在插入新元素时可能遇到的容量不足问题。通常情况下,扩容是通过以下步骤实现的:
-
判断是否需要扩容:当动态数组的当前容量已满且再插入元素时,需要扩容。此时,通常会将数组的容量增加到原来的 2倍(或者有时是固定增量),以保持较高的空间利用率和操作效率。
-
重新分配内存:扩容时,动态数组会分配一个新的、更大的内存块,通常是原数组大小的两倍。
-
复制数据:将原数组中的所有元素复制到新的、更大的内存块中。
-
更新指针/引用:将新的数组指针(或者引用)赋给动态数组变量,原数组的内存空间就可以被垃圾回收(在一些语言中,如Java和Python)。
扩容的性能分析
-
扩容时的时间复杂度:
- 扩容操作本身需要 O(n) 时间,因为它涉及到将数组中的每个元素复制到新的内存空间。
- 但值得注意的是,扩容并不会在每次插入时都发生,而是周期性地发生。具体来说,扩容通常是每次数组容量不足时,扩容到原来的 2 倍,因此在进行
n
次插入时,扩容操作平均需要的时间为 O(1)(摊销时间复杂度)。
摊销分析:假设每次扩容操作都是将数组的大小翻倍,那么对于
n
次插入,扩容操作的总成本是:- 第一次扩容将数组大小从 1 扩展到 2(复制 1 个元素)
- 第二次扩容将数组大小从 2 扩展到 4(复制 2 个元素)
- 第三次扩容将数组大小从 4 扩展到 8(复制 4 个元素)
- ...
- 最后一轮扩容将数组大小从
2^k
扩展到2^(k+1)
(复制2^k
个元素)
因此,整个扩容操作的总成本是
1 + 2 + 4 + 8 + ... + n
,这可以简化为 O(n)。当将这个总成本摊销到n
次插入操作中时,平均每次插入的扩容时间复杂度为 O(1)。 -
空间复杂度:
- 动态数组的空间复杂度为 O(n),其中
n
是数组当前存储的元素个数。因为动态数组需要存储实际的元素以及动态扩容后的额外空间(通常会分配多余空间以提高效率),所以其空间复杂度是与元素个数成线性关系的。 - 如果扩容的策略是每次翻倍,可能会导致在某一时刻,数组的实际元素数小于数组的总容量,造成一定的空间浪费,但这种浪费通常是有限的。
- 动态数组的空间复杂度为 O(n),其中
何时扩容:容量和负载因子
扩容的时机和策略通常依赖于负载因子(load factor)。负载因子是指数组中已使用空间与数组总容量的比例,常见的负载因子约定为 0.75。这意味着,当数组的元素数量达到当前容量的 75% 时,就会触发扩容操作。
- 例如,如果动态数组当前的容量是 10,当其中的元素数量达到 7 时,负载因子达到 0.75,这时就会触发扩容,将容量增大为原来的 2 倍,变为 20。
均摊时间复杂度
**均摊时间复杂度**(Amortized Time Complexity)是对一系列操作在最坏情况下的平均时间复杂度的分析方法。它用于描述在一系列操作中,某个操作的平均成本,而不仅仅是单个操作的最坏情况。均摊分析通常应用于那些**偶尔有昂贵操作**(如扩容)但大多数操作是便宜的算法或数据结构。
### 1. **什么是均摊时间复杂度**
均摊时间复杂度是指通过将多个操作的总时间复杂度平均到每一个操作上,从而得出操作的平均成本。它帮助我们理解在长期内进行大量操作时,每个操作的“平均”成本,而不仅仅是单个操作的最坏情况。
举个例子,在 **动态数组扩容** 这样的场景中,扩容本身可能是一个昂贵的操作(O(n)),但是在多个操作中,如果扩容是偶尔发生的,它的影响会被“摊销”到其他便宜的操作上,使得每个操作的均摊复杂度变得更低。
### 2. **均摊时间复杂度的例子:动态数组扩容**
动态数组(如 `ArrayList` 或 `Vector`)的扩容机制通常会导致偶尔发生昂贵的操作——即当数组满时,必须将数据复制到更大的内存中。这种扩容操作的时间复杂度为 **O(n)**,但是这种情况并不是每次插入操作都会发生的。
假设动态数组的容量为 `n`,当它达到 `n` 个元素时,插入第 `n+1` 个元素会触发扩容。扩容操作将数组大小翻倍,并将所有 `n` 个元素复制到新数组中。
如果我们对 `n` 次插入操作进行分析,第一次扩容操作的时间复杂度是 **O(n)**,第二次扩容时的时间复杂度是 **O(2n)**,依此类推。看起来这很昂贵,但实际上,扩容操作并不会每次都发生,因此可以通过**摊销**计算均摊时间复杂度。
### 3. **均摊时间复杂度的计算**
考虑一个有 `n` 次插入操作的动态数组,扩容的发生规律是容量每次翻倍。虽然扩容的时间复杂度为 **O(n)**,但因为它是每次容量达到当前容量的上限时才发生一次,并且扩容的次数相对较少,所以我们可以用均摊时间复杂度来计算。
假设我们插入 `n` 个元素:
- **第一次扩容**:当插入第一个元素时,不需要扩容。当插入第 `n+1` 个元素时,需要扩容,复制 `n` 个元素到新的数组,时间复杂度为 **O(n)**。
- **第二次扩容**:当数组容量达到 `2n` 时,插入第 `2n+1` 个元素时,需要扩容,复制 `2n` 个元素,时间复杂度为 **O(2n)**。
- **以此类推**,每次扩容的时间复杂度为原来的一倍。
总的时间复杂度是:
$$
O(1) + O(1) + \cdots + O(1) + O(n) + O(2n) + O(4n) + \cdots
$$
这个和是一个几何级数,摊销下来,整个插入操作的总时间复杂度为 **O(n)**,而均摊到每次插入,时间复杂度为 **O(1)**。
### 4. **为什么要使用均摊分析**
- **反映真实性能**:均摊分析能够更准确地反映实际操作的性能,特别是对于那些偶尔需要昂贵操作的数据结构。例如,动态数组虽然扩容是昂贵的,但在插入大量元素时,大多数插入操作的时间复杂度是常数级别的 **O(1)**。
- **避免过度优化**:最坏情况分析往往很保守,无法准确描述实际的性能。在实际应用中,大多数操作都是常数时间复杂度的,如果过度关注最坏情况复杂度,可能会导致对性能的误判,均摊时间复杂度则能够提供更实际的视角。
- **分析复杂数据结构**:对于某些数据结构,如 `splay tree`、`hash table` 等,均摊分析能够有效计算实际操作的平均性能,帮助我们了解在多次操作下的表现。
### 5. **均摊分析的具体类型**
均摊时间复杂度通常有几种常见的分析方法,具体选择哪种方法取决于数据结构和算法的特性。
- **聚合方法(Aggregate Method)**:这种方法是最直接的,直接计算一系列操作的总时间,然后将总时间平均到每个操作上。适用于每个操作有相似的成本,或者对每个操作都能进行较为精确的成本估计。
- **会计方法(Accounting Method)**:假设每个操作有一个“实际成本”和“虚拟成本”,其中某些操作会提前“支付”额外的成本,提前为昂贵操作储备费用。通过这种方法,我们可以在某些操作上“存钱”来补偿未来的昂贵操作。
- **潜力方法(Potential Method)**:通过引入一个潜力函数,将数据结构的状态与其潜力(类似于虚拟成本)关联起来。潜力函数可以通过降低结构复杂度来降低未来操作的成本。
### 6. **总结**
均摊时间复杂度是对一系列操作的平均时间复杂度的分析方法,它帮助我们理解在长期操作中,每个操作的“平均”成本。通过均摊分析,复杂度较高的操作(如扩容)能够被“摊销”到其他便宜的操作上,从而得出更加准确的时间复杂度估计。它对于那些偶尔发生昂贵操作但大多数操作便宜的情况(例如动态数组的扩容)非常有用,能够更真实地反映程序的性能。
不要通过代码结构去判断复杂度
1,通常对于循环n次的for循环,如果再嵌套一个相同的for循环,时间复杂度会是n平方,但是有例外的,如
int n;
for (int i = 0; i < n; i++)
{for (int j = i; j < n; j += i);
}
时间复杂度就是n/1 + n/2 + n/3 +……+n/n,属于调和级数,是对数型的增长。
2,调和级数
要了解算法流程。
相关文章:

时间复杂度和空间复杂度 part2
一,空间复杂度 空间复杂度是衡量一个算法在执行过程中所需内存空间的量度。它反映了算法随着输入数据规模(通常是 nn)的增加,所消耗的内存量如何变化。空间复杂度是分析算法效率的一个重要方面,尤其是在内存资源有限的…...

【电机控制器】STC8H1K芯片——UART串口通信
【电机控制器】STC8H1K芯片——UART串口通信 文章目录 [TOC](文章目录) 前言一、UART1.串口初始化2.串口中断3.发送一个字节 二、实验1.原理图2.实验现象 三、参考资料总结 前言 提示:以下是本篇文章正文内容,下面案例可供参考 一、UART 1.串口初始化 …...
STM32移植RT-Thread---时钟管理
一RTT时钟节拍概念 RT-Thread的时钟节拍(Tick)是操作系统用于管理时间和任务调度的一个基本单位。它在实时操作系统中尤为关键,用于实现任务的延时、超时管理等功能。以下是关于RT-Thread时钟节拍的简单说明: 1.Tick定义&#x…...
Jasypt 实现 yml 配置加密
文章目录 前言一、集成 Jasypt1. pom 依赖2. yml 依赖 3. 加密工具类3. 使用二、常见问题1. application.yml 失效问题2. 配置热更新失败问题 前言 jasypt 官方地址:https://github.com/ulisesbocchio/jasypt-spring-boot Jasypt可以为Springboot加密的信息很多&a…...

uniapp—android原生插件开发(2原生插件开发)
本篇文章从实战角度出发,将UniApp集成新大陆PDA设备RFID的全过程分为四部曲,涵盖环境搭建、插件开发、AAR打包、项目引入和功能调试。通过这份教程,轻松应对安卓原生插件开发与打包需求! ***环境问题移步至:uniapp—an…...
NLP之ASR之moonshine:moonshine的简介、安装和使用方法、案例应用之详细攻略
NLP之ASR之moonshine:moonshine的简介、安装和使用方法、案例应用之详细攻略 目录 moonshine的简介 moonshine的安装和使用方法 1、安装 推荐使用uv管理Python环境 安装Moonshine包 Torch后端 TensorFlow后端 JAX后端 ONNX运行时 2、使用方法 0、测试 1…...

albert模型实现微信公众号虚假新闻分类
项目源码获取方式见文章末尾! 600多个深度学习项目资料,快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…...

OceanBase 应用实践:如何处理数据空洞,降低存储空间
问题描述 某保险行业客户的核心系统,从Oracle 迁移到OceanBase之后,发现数据存储空间出现膨胀问题,数据空间 datasize9857715.48M,实际存储占用空间17790702.00M。根据 required_mb - data_mb 值判断,数据空洞较为严重…...

计算机的错误计算(一百四十八)
摘要 本节探讨 MATLAB 中 附近数的正割函数与 附近数的余割函数的计算精度问题。 例1. 已知 计算 直接贴图吧: 另外,16位的正确值分别为 0.4105556037464873e9、0.3670813182326778e13、-0.2549029285657875e8 与 -0.1248777628817462e12&am…...

MySQL记录锁、间隙锁、临键锁(Next-Key Locks)详解
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。 应用在InnoDB存储引擎中。InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。 对于行…...

SLM401A系列42V商业照明线性恒流芯片 线性照明调光在LED模组及灯带智能球泡灯上应用
SLM401A系列型号选型: SLM401A10ED-7G:QFN1010-4 SLM401A15aa-7G:SOT23-3 SLM401A20aa-7G:SOT23-3 SLM401A20ED-7G:QFN1010-4 SLM401A25aa-7G:SOT23-3 SLM401A30aa-7G:SOT23-3 SLM401A40aa-7G:SOT23-3 SLM401A50aa-7G:SOT23-3 SLM401A6…...

京东零售推荐系统可解释能力详解
作者:智能平台 张颖 本文导读 本文将介绍可解释能力在京东零售推荐系统中的应用实践。主要内容包括以下几大部分:推荐系统可解释定义、系统架构、排序可解释、模型可解释、流量可解释。 推荐系统可解释定义 推荐系统可解释的核心包括三部分࿰…...

蓝桥杯 懒洋洋字符串--字符串读入
题目 代码 #include <iostream>using namespace std;int main(){int n;cin>>n;char s[210][4];int ans0;for(int i0;i<n;i){scanf("%s",s[i]);}for(int i0;i<n;i){char as[i][0];char bs[i][1];char cs[i][2];// cout<<a<< <<b…...

SDL打开YUV视频
文章目录 问题1:如何控制帧率?问题2:如何触发退出事件?问题3:如何实时调整视频窗口的大小问题4:YUV如何一次读取一帧的数据? 问题1:如何控制帧率? 单独用一个子线程给主线…...
微服务架构面试内容整理-Archaius
Archaius 是由 Netflix 开发的一个配置管理库,主要用于处理动态配置和环境配置。在微服务架构中,Archaius 允许开发者以灵活的方式管理配置,从而更好地应对变化的需求。以下是 Archaius 的主要特点、工作原理和使用场景: 主要特点 1. 动态配置: Archaius 支持动态更新配置…...
实现 Nuxt3 预览PDF文件
安装必要的库,这里使用PDF.js库 npm install pdfjs-dist --save 为了解决跨域问题,在server/api 下 创建一个请求api, downloadFileByProxy.ts import { defineEventHandler } from h3;export default defineEventHandler(async event >…...
udp为什么会比tcp 有更低的延迟
UDP(User Datagram Protocol,用户数据报协议)相比TCP(Transmission Control Protocol,传输控制协议)具有更低的延迟,这主要归因于UDP协议的设计特点和机制。以下是对UDP比TCP延迟低的原因的详细…...

基于java+SpringBoot+Vue的洗衣店订单管理系统设计与实现
项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: Springboot mybatis Maven mysql5.7或8.0等等组成&#x…...

HarmonyOS-消息推送
一. 服务简述 Push Kit(推送服务)是华为提供的消息推送平台,建立了从云端到终端的消息推送通道。所有HarmonyOS 应用可通过集成 Push Kit,实现向应用实时推送消息,使消息易见,构筑良好的用户关系࿰…...

数据分析:宏基因组DESeq2差异分析筛选差异物种
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍原理:计算步骤:结果:加载R包准备画图主题数据链接导入数据Differential abundance (No BP vs 2BP TA)构建`countData`矩阵过滤低丰度物种构建DESeq数据对象DESeq2差异分析画图Di…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...