【数据结构】空间复杂度

目录

一、引入空间复杂度的原因

二、空间复杂度的分析

❥ 2.1 程序运行时内存大小 ~ 程序本身大小

❥ 2.2 程序运行时内存大小 ~ 算法运行时内存大小

❥ 2.3 算法运行时内存大小

❥ 2.4 不考虑算法全部运行空间的原因

三、空间复杂度

❥ 3.1空间复杂度的定义

❥ 3.2 空间复杂度不能准确算出空间大小的原因

❥ 3.3 最关注差空间复杂度的原因

四、空间复杂度的计算

五、常见空间复杂度举例

❥ 5.1 常数阶

❥ 5.2 线性阶

❥ 5.3 平方阶

六、递归与非递归空间复杂度

❥ 6.1 常规函数空间复杂度不重点算栈空间大小的原因

❥ 6.2 递归函数空间复杂度需要算栈空间大小的原因

七、权衡时间与空间效率


一、引入空间复杂度的原因

  1. 我们知道,一个算法的好坏主要从算法的执行时间和所需要占用的存储空间这两个方面来进行衡量。
  2. 当一个算法在执行过程中存储数据所需要占用的内存空间的大小,就是空间复杂度。它和时间复杂度一样,是衡量算法性能的重要指标之一。
  3. 在开发程序之前,分析算法的空间复杂度有助于开发者提前预估程序运行时所需的内存空间,从而合理地规划硬件资源。

二、空间复杂度的分析

❥ 2.1 程序运行时内存大小 ~ 程序本身大小

首先我们先弄清楚什么是程序本身内存的大小,什么是程序运行时内存的大小?有什么关系?

  1. 程序本身内存的大小:指的是程序文件存储在磁盘等存储设备上所占用的存储空间大小。它主要取决于程序的源代码经过编译、链接等操作后生成的可执行文件所包含的内容,包括代码段、数据段等。
  2. 程序运行时内存的大小:指的是程序在计算机内存中运行时所占用的存储空间的量。它涉及到程序运行过程中各个方面对内存的使用,包括代码区、数据区(如全局变量、静态变量、常量)、栈区(存储函数调用信息和局部变量)和堆区(用于动态内存分配)等所占用的内存总和。

程序运行时的内存大小是包含程序本身的大小的。

  • 通俗来讲,程序本身的大小好比一颗种子,而程序运行的大小就像生长后的植株。
  • 程序本身就像一颗种子,其大小是固定的,蕴含着生长的潜力和信息。
  • 当种子种下并开始生长后,就如同程序开始运行。当种子长成大树后,会有粗壮的树干和茂密的枝叶,需要占据很大的空间。这就像程序运行时,会在系统中展开庞大的运行架构,占用大量的内存、CPU 等资源,其运行时占据的 “空间” 和要比程序本身所占用的大得多,也有可能会随着业务的发展和数据量的增加不断扩展。

❥ 2.2 程序运行时内存大小 ~ 算法运行时内存大小

程序运行时内存大小和算法运行时内存大小有什么关系?

程序是一个更为宽泛的概念,它由多个部分组成,算法只是程序实现特定功能的核心逻辑。程序运行时,其内存占用除了算法运行所需的内存外,还包括其他诸多方面。算法运行的内存主要用于存储算法执行过程中使用的数据结构、中间变量、递归调用栈等。而程序运行内存还包含程序代码本身占用的空间(代码段)、全局和静态变量占用的空间(数据段)、程序与外部交互的输入输出缓冲区、加载的库文件所占用的内存等。

在一些非常简单的程序中,如果程序的主要功能就是执行一个单一的算法,且没有其他复杂的功能模块、全局变量、输入输出操作等,那么算法运行的内存大小可能与程序运行内存大小非常接近。例如,一个简单的控制台程序,其唯一的功能就是实现一个简单的递归算法计算斐波那契数列,此时程序运行内存主要就是算法运行所需的内存。

但在大多数实际应用中,程序运行时内存的大小通常会大于算法运行时内存的大小。

❥ 2.3 算法运行时内存大小

算法运行时内存大小主要分为以下几个部分:

输入数据空间:存储算法的输入数据

输出数据空间:存储算法的输出数据

暂存数据空间:用于存储算法在运行过程中的变量、对象、函数上下文等数据

暂存数据空间又可以分为:

  • 暂存数据:保存算法运行过程中的各种常量、变量、对象等
  • 栈帧空间:保存调用函数的上下文数据
  • 指令空间:保存编译后的程序指令,但在统计空间复杂度时通常忽略不计

空间复杂度通常统计的是暂存数据栈帧空间

❥ 2.4 不考虑算法全部运行空间的原因

  • 输入数据空间:

输入数据所占用的空间通常不纳入空间复杂度的考量范围。因为输入数据是算法处理的对象,它的规模是由问题本身决定的,并非算法为完成任务而额外使用的空间。

例:对一个包含n个元素的数组进行排序,数组本身占用的存储空间与算法的空间使用效率并无直接关联,所以不包含在空间复杂度的计算中。

  • 程序代码空间:

程序代码本身所占用的存储空间也不影响算法的空间复杂度。代码大小是固定的,不随输入规模的变化而变化,它与算法在处理不同规模输入时的内存需求增长特性没有直接联系。无论输入规模如何,程序代码的空间占用都是恒定的,因此不将其纳入空间复杂度的计算。

  • 输出数据空间:

通常而言,输出数据一般不计入空间复杂度。像常规算法的单一返回值,或是排序、查找算法的输出结果,其占用空间固定或由输入决定,不反映额外开销,可不考虑。

但当输出规模与输入紧密相关、或优化目标是输出空间时,则可能计入。

因为空间复杂度更专注于算法本身的逻辑实现对存储空间的需求,即专注于为了完成算法任务,输入数据本身所占空间之外,算法额外需要的辅助空间大小。所以空间复杂度不考虑算法全部运行空间。

三、空间复杂度

❥ 3.1空间复杂度的定义

空间复杂度(Space Complexity)也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

❥ 3.2 空间复杂度不能准确算出空间大小的原因

  1. 空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,是一种渐近分析,使用大O表示法描述算法在问题规模趋于无穷大时,额外存储空间随问题规模增长的趋势而不是精确的内存使用量。
  2. 此外,程序运行时所占用的内存还受到很多与算法本身无关的因素影响,不同的计算机硬件环境(如内存架构、字长等)和软件环境(如操作系统的内存管理机制、编译器的优化程度等)会导致同一算法在不同环境下的实际内存占用有所不同。这会影响程序的实际内存使用,但空间复杂度分析通常不会考虑这些具体环境因素。

❥ 3.3 最关注差空间复杂度的原因

与时间复杂度不同的是,一般情况下,我们只关注最差空间复杂度,因为内存空间是有限的,我们要确保在所有输入数据下都有足够的内存空间。

四、空间复杂度的计算

空间复杂度直接计算变量个数即可,不需要程序占用了多少字节的空间。

计算变量个数的原因:

因为空间复杂度计算规则基本跟时间复杂度类似,也是使用大O渐进表示法来表示空间复杂度。

我们知道,大O渐进表示法表示的是估算值,而不是真实值,主要目的是为了算出空间复杂度属于哪个量级。

举例:

  • 一个int型变量,占用空间大小是4bit,4的大小是属于常数阶的,那么它的空间复杂度为O(1)
  • n个int型变量的数组,占用的空间大小是4nbit,4n的大小是属于线性阶的,那么它的空间复杂度为O(N)

所以说,为了方便计算空间复杂度的大小,我们可以直接计算变量个数。

五、常见空间复杂度举例

❥ 5.1 常数阶

常数阶的空间复杂度为:O(1)

  • 当空间复杂度为O(1)的情况下,也称算法为原地工作或者就地工作
  • 原地工作(就地工作):指的是算法的执行过程中不使用额外的存储空间,或者使用的额外空间相对输入数据量是常数。即所使用的额外空间量不随问题规模(即输入数据的大小)的变化而变化。

例题1:

//计算BubbleSort的空间复杂度
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}

这段冒泡排序的代码中,分别有end,i,exchange这3个新的变量出现,也就是为这3个变量开辟了空间,因为3是常数,所以冒泡排序的空间复杂度为:O(1)

例题2:

void fun()
{return 0;
}void tmp(int n)
{int i = 0;for (i = 0; i < n; i++){fun();}}

注意:在循环中初始化变量或调用函数而占用的内存,在进入下一循环后就会被释放,因此不会累积占用空间,空间复杂度仍为 𝑂(1)

❥ 5.2 线性阶

线性阶的空间复杂度为:O(N)

例题:

// 计算阶乘递归Factorial的空间复杂度
long long Fac(int N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}

Fac递归函数Fac用于计算阶乘。

函数递归调用的深度为N,开辟了N个栈帧,每个栈帧使用了常数个空间。所以空间复杂度为O(N)

❥ 5.3 平方阶

平方阶的空间复杂度为:O(N^2)

例题:

long long Fac(int N)
{if (N == 0)return 1;int arr[N];return Fac(N - 1) * N;
}

在每个递归函数中都初始化了一个数组,总长度为1+2+...+(N-1)+ N = N(N+1) / 2

因为空间复杂度算的是所处的量级,所以是O(N^2)

六、递归与非递归空间复杂度

❥ 6.1 常规函数空间复杂度不重点算栈空间大小的原因

编译阶段,编译器会根据函数的定义,分析函数中的参数、局部变量以及可能保存的寄存器信息等,从而确定函数调用时一个栈帧所需要的空间大小和布局。

例:


int add(int a, int b)
{int result;result = a + b;return result;
}

编译器知道需要为两个int类型的参数a,b,一个int类型的局部变量result,以及返回地址等分配空间。

这是对单个栈帧而言的,是一个固定的模式,不随输入规模或函数执行过程发生显著变化。在大O表示法这种渐进分析中,这部分固定大小的空间被视为常数项。

空间复杂度主要关注的是随着问题规模的增长,算法所需空间的增长趋势。对于常规函数,显式申请的额外空间,如动态分配的数组或对象等,才是随着问题规模可能呈线性、对数等不同趋势增长的部分,是影响空间复杂度的关键因素。

❥ 6.2 递归函数空间复杂度需要算栈空间大小的原因

栈在编译期间确定的是单个栈帧的空间布局,但在递归函数运行时会不断产生新的栈帧。

  • 在编译时,无法确定递归函数会被调用多少次,只有在程序运行时,随着递归函数的不断调用和返回,才会实际地在栈上创建和释放栈帧,从而动态地占用和释放栈空间。
  • 栈帧数量与递归深度直接相关,而递归深度往往与问题规模有关

例:在5.2的递归计算阶乘函数,递归深度与输入的n成正比,每一层递归都要占用一定栈空间存储当前层的参数、局部变量等,所以栈空间占用会随着递归深度增加而显著增加。

总言之,递归栈空间中单个栈帧的结构和大小在编译时期是可以确定的,但递归过程中栈空间的实际创建和使用是在运行时动态进行的,并不是完全在编译时期就确定好的。空间复杂度关注的是运行时内存的大小。

七、权衡时间与空间效率

  • 实际情况下,算法的时间效率和空间效率同时达到最优非常困难。
  • 通常情况下,我们不太关注算法的空间效率,更注重时间效率。
  • 但是,像互联网、嵌入式等行业对空间效率也是较为注重的。
  • 所以,选择优化时间复杂度或者空间复杂度取决于我们的侧重方面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/67137.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL--》深度解析InnoDB引擎的存储与事务机制

目录 InnoDB架构 事务原理 MVCC InnoDB架构 从MySQL5.5版本开始默认使用InnoDB存储引擎&#xff0c;它擅长进行事务处理&#xff0c;具有崩溃恢复的特性&#xff0c;在日常开发中使用非常广泛&#xff0c;其逻辑存储结构图如下所示&#xff0c; 下面是InnoDB架构图&#xf…

Redis高阶5-布隆过滤器

Redis布隆过滤器 ​ 由一个初始值都为零的bit数组和多个哈希函数构成&#xff0c;用来快速判断集合中是否存在某个元素 目的减少内存占用方式不保存数据信息&#xff0c;只是在内存中做一个是否存在的标记flag 布隆过滤器&#xff08;英语&#xff1a;Bloom Filter&#xff0…

DeepSeek学术题目选择效果怎么样?

论文选题 一篇出色的论文背后&#xff0c;必定有一个“智慧的选题”在撑腰。选题足够好文章就能顺利登上高水平期刊&#xff1b;选题不行再精彩的写作也只能“当花瓶”。然而许多宝子们常常忽视这个环节&#xff0c;把大量时间花在写作上&#xff0c;选题时却像抓阄一样随便挑一…

Docker核心命令与Yocto项目的高效应用

随着软件开发逐渐向分布式和容器化方向演进&#xff0c;Docker 已成为主流的容器化技术之一。它通过标准化的环境配置、资源隔离和高效的部署流程&#xff0c;大幅提高了开发和构建效率。Yocto 项目作为嵌入式 Linux 系统构建工具&#xff0c;与 Docker 的结合进一步增强了开发…

Qt 5.14.2 学习记录 —— 이십 QFile和多线程

文章目录 1、QFile1、打开2、读写3、关闭4、程序5、其它功能 2、多线程1、演示2、锁 3、条件变量和信号量 1、QFile Qt有自己的一套文件体系&#xff0c;不过Qt也可以使用C&#xff0c;C&#xff0c;Linux的文件操作。使用Qt的文件体系和Qt自己的一些类型更好配合。 管理写入读…

多版本并发控制:MVCC的作用和基本原理

多版本并发控制&#xff1a;MVCC的作用和基本原理 1、MVCC简介1.1 快照读与当前读的区别1.1.1 快照读1.1.2 当前读 1.2 数据库的读写问题1.3 MVCC的作用 2、MVCC实现原理之ReadView2.1 什么是ReadView2.2 ReadView的设计思路2.3 MVCC整体操作流程 1、MVCC简介 1.1 快照读与当前…

神经网络|(二)sigmoid神经元函数

【1】引言 在前序学习进程中&#xff0c;我们已经了解了基本的二元分类器和神经元的构成&#xff0c;文章学习链接为&#xff1a; 神经网络|(一)加权平均法&#xff0c;感知机和神经元-CSDN博客 在此基础上&#xff0c;我们认识到神经元本身在做二元分类&#xff0c;是一种非…

Qt中QVariant的使用

1.使用QVariant实现不同类型数据的相加 方法&#xff1a;通过type函数返回数值的类型&#xff0c;然后通过setValue来构造一个QVariant类型的返回值。 函数&#xff1a; QVariant mainPage::dataPlus(QVariant a, QVariant b) {QVariant ret;if ((a.type() QVariant::Int) &a…

BAHD酰基转移酶对紫草素的手性催化-文献精读105

Two BAHD Acyltransferases Catalyze the Last Step in the Shikonin/Alkannin Biosynthetic Pathway 两个BAHD酰基转移酶催化了紫草素/左旋紫草素生物合成途径中的最后一步 一个BAHD酰基转移酶专门催化紫草素的酰基化&#xff0c;而另一个BAHD酰基转移酶则仅催化紫草素的对映…

CLion开发Qt桌面

IDE&#xff1a;CLion Qt Qt版本&#xff1a;5.12 学习正点原子的嵌入式Linux开发板时&#xff0c;使用Qt Creator写代码不是很方便&#xff0c;遂尝试使用CLion搭建Qt开发环境。 一、CLion的Qt环境搭建 1&#xff0c;配置工具链 找到Qt的安装目录&#xff0c;此处为E:\Tools\…

【学术会议-第五届机械设计与仿真国际学术会议(MDS 2025) 】前端开发:技术与艺术的完美融合

重要信息 大会官网&#xff1a;www.icmds.net 大会时间&#xff1a;2025年02月28日-03月02日 大会地点&#xff1a;中国-大连 会议简介 2025年第五届机械设计与仿真国际学术会议&#xff08;MDS 2025) 将于2025年02月28-3月02日在中国大连召开。MDS 2025将围绕“机械设计”…

84,【8】BUUCTF WEB [羊城杯 2020]Blackcat

进入靶场 音乐硬控我3分钟 回去看源码 <?php // 检查 POST 请求中是否包含 Black-Cat-Sheriff 和 One-ear 字段 // 如果任意一个字段为空&#xff0c;则输出错误信息并终止脚本执行 if(empty($_POST[Black-Cat-Sheriff]) || empty($_POST[One-ear])){die(请提供 Black-C…

人工智能:从基础到前沿

目录 目录 1. 引言 2. 人工智能基础 2.1 什么是人工智能&#xff1f; 2.2 人工智能的历史 2.3 人工智能的分类 3. 机器学习 3.1 机器学习概述 3.2 监督学习 3.3 无监督学习 3.4 强化学习 4. 深度学习 4.1 深度学习概述 4.2 神经网络基础 4.3 卷积神经网络&#…

漏洞情报:为什么、要什么和怎么做

漏洞一直是网络攻防的焦点所在&#xff0c;因为漏洞直接或间接影响安全性的核心方面——权限。攻击者挖掘和利用漏洞&#xff0c;获取非授权的权限&#xff1b;防御方定位和消除漏洞&#xff0c;监测和阻断漏洞的利用&#xff0c;使攻击者无法利用漏洞达到其目的。漏洞信息本质…

leetcode——删除链表的倒数第N个节点(java)

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#xf…

正则表达式以及Qt中的使用

目录 一、正则表达式 1、基本匹配&#xff1a; 2、元字符&#xff1a; 2.1 .运算符&#xff1a; 2.2 字符集&#xff1a; 2.3 重复次数&#xff1a; 2.4 量词{} 2.5 特征标群() 2.6 或运算符 2.7 \反斜线转码特殊字符 2.8 锚点 3、简写字符 4、零宽度断言 4.1 正…

流行的开源高性能数据同步工具 - Apache SeaTunnel 整体架构运行原理

概述 背景 数据集成在现代企业的数据治理和决策支持中扮演着至关重要的角色。随着数据源的多样化和数据量的迅速增长&#xff0c;企业需要具备强大的数据集成能力来高效地处理和分析数据。SeaTunnel通过其高度可扩展和灵活的架构&#xff0c;帮助企业快速实现多源数据的采集、…

消息队列篇--原理篇--Pulsar(Namespace,BookKeeper,类似Kafka甚至更好的消息队列)

Apache Pulusar是一个分布式、多租户、高性能的发布/订阅&#xff08;Pub/Sub&#xff09;消息系统&#xff0c;最初由Yahoo开发并开源。它结合了Kafka和传统消息队列的优点&#xff0c;提供高吞吐量、低延迟、强一致性和可扩展的消息传递能力&#xff0c;适用于大规模分布式系…

VS Code i18n国际化组件代码code显示中文配置 i18n ally

VUE项目做i18n国际化之后&#xff0c;代码中的中文都变成了code这时的代码就会显得非常难读&#xff0c;如果有一个插件能把code转换成中文显示就好了 vscode插件搜索“i18n ally” 在项目根文件夹下创建文件&#xff1a;.vscode/settings.json settings.json 内容如下 {"…

图论汇总1

1.图论理论基础 图的基本概念 二维坐标中&#xff0c;两点可以连成线&#xff0c;多个点连成的线就构成了图。 当然图也可以就一个节点&#xff0c;甚至没有节点&#xff08;空图&#xff09; 图的种类 整体上一般分为 有向图 和 无向图。 有向图是指 图中边是有方向的&a…